Split HyperTwist coach closure policy repository family
This commit is contained in:
parent
677fbb5dbb
commit
ae625a8891
5 changed files with 1057 additions and 939 deletions
|
|
@ -24559,510 +24559,6 @@ FHyperTwistTrainingCoachSessionQueueExecutionSummary UHyperTwistTrainingReposito
|
|||
return Summary;
|
||||
}
|
||||
|
||||
namespace HyperTwistTrainingRepositoryLibraryInternal
|
||||
{
|
||||
FHyperTwistTrainingCoachActionClosureSummary DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& AnchorPlan,
|
||||
const FString& ReferenceUtc,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionClosureSummary Summary;
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan);
|
||||
if (!NormalizedAnchorPlan.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachActionSequenceSummary SequenceSummary =
|
||||
UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary(
|
||||
RepositoryState,
|
||||
NormalizedAnchorPlan,
|
||||
ReferenceUtc
|
||||
);
|
||||
if (!SequenceSummary.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = SequenceSummary.UserId;
|
||||
Summary.ReferenceUtc = SequenceSummary.ReferenceUtc;
|
||||
Summary.RootPlanId = SequenceSummary.RootPlanId;
|
||||
Summary.LatestPlanId = SequenceSummary.LatestPlanId;
|
||||
Summary.FocusDeckId = SequenceSummary.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = SequenceSummary.FocusMethodSegmentId;
|
||||
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> SequencePlans;
|
||||
const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans =
|
||||
UHyperTwistTrainingRepositoryLibrary::ListCoachActionPlansForUser(
|
||||
RepositoryState,
|
||||
NormalizedAnchorPlan.UserId
|
||||
);
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
|
||||
const FString CandidateRootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
|
||||
? NormalizedPlan.RootPlanId
|
||||
: NormalizedPlan.PlanId;
|
||||
if (CandidateRootPlanId == SequenceSummary.RootPlanId
|
||||
|| NormalizedPlan.PlanId == SequenceSummary.RootPlanId
|
||||
|| NormalizedPlan.ParentPlanId == SequenceSummary.RootPlanId)
|
||||
{
|
||||
SequencePlans.Add(NormalizedPlan);
|
||||
}
|
||||
}
|
||||
if (SequencePlans.Num() == 0)
|
||||
{
|
||||
SequencePlans.Add(NormalizedAnchorPlan);
|
||||
}
|
||||
const auto ResolvePlanSortUtc = [](const FHyperTwistTrainingCoachActionPlan& Plan) -> FString
|
||||
{
|
||||
return !Plan.CompletedAtUtc.IsEmpty()
|
||||
? Plan.CompletedAtUtc
|
||||
: (!Plan.StartedAtUtc.IsEmpty() ? Plan.StartedAtUtc : Plan.CreatedAtUtc);
|
||||
};
|
||||
SequencePlans.Sort(
|
||||
[&ResolvePlanSortUtc](const FHyperTwistTrainingCoachActionPlan& Left,
|
||||
const FHyperTwistTrainingCoachActionPlan& Right)
|
||||
{
|
||||
const FString LeftSortUtc = ResolvePlanSortUtc(Left);
|
||||
const FString RightSortUtc = ResolvePlanSortUtc(Right);
|
||||
const int32 CompareResult =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(LeftSortUtc, RightSortUtc);
|
||||
if (CompareResult != 0)
|
||||
{
|
||||
return CompareResult < 0;
|
||||
}
|
||||
if (Left.FollowUpDepth != Right.FollowUpDepth)
|
||||
{
|
||||
return Left.FollowUpDepth < Right.FollowUpDepth;
|
||||
}
|
||||
|
||||
return Left.PlanId < Right.PlanId;
|
||||
}
|
||||
);
|
||||
|
||||
TSet<FString> AllFocusCaseIds;
|
||||
TSet<FString> ResolvedCaseIds;
|
||||
TSet<FString> UnresolvedCaseIds;
|
||||
TMap<FString, int32> CaseAttemptCounts;
|
||||
TMap<FString, int32> CaseNonSuccessCounts;
|
||||
TMap<FString, int32> CaseSuccessCounts;
|
||||
bool bAnyRecognition = false;
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : SequencePlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlanOutcomeSummary OutcomeSummary =
|
||||
UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary(
|
||||
RepositoryState,
|
||||
Plan,
|
||||
SequenceSummary.ReferenceUtc
|
||||
);
|
||||
TSet<FString> PlanFocusCaseIds;
|
||||
for (const FString& FocusCaseId : Plan.FocusCaseIds)
|
||||
{
|
||||
if (!FocusCaseId.IsEmpty())
|
||||
{
|
||||
PlanFocusCaseIds.Add(FocusCaseId);
|
||||
AllFocusCaseIds.Add(FocusCaseId);
|
||||
}
|
||||
}
|
||||
for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : Plan.Entries)
|
||||
{
|
||||
if (!Entry.CaseId.IsEmpty())
|
||||
{
|
||||
PlanFocusCaseIds.Add(Entry.CaseId);
|
||||
AllFocusCaseIds.Add(Entry.CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FString> PlanOutstandingCaseIds;
|
||||
for (const FString& FollowUpCaseId : OutcomeSummary.FollowUpCaseIds)
|
||||
{
|
||||
if (!FollowUpCaseId.IsEmpty())
|
||||
{
|
||||
PlanOutstandingCaseIds.Add(FollowUpCaseId);
|
||||
UnresolvedCaseIds.Add(FollowUpCaseId);
|
||||
ResolvedCaseIds.Remove(FollowUpCaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& DeferredCaseId : OutcomeSummary.DeferredLaunchCaseIds)
|
||||
{
|
||||
if (!DeferredCaseId.IsEmpty())
|
||||
{
|
||||
PlanOutstandingCaseIds.Add(DeferredCaseId);
|
||||
AllFocusCaseIds.Add(DeferredCaseId);
|
||||
UnresolvedCaseIds.Add(DeferredCaseId);
|
||||
ResolvedCaseIds.Remove(DeferredCaseId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& FocusCaseId : PlanFocusCaseIds)
|
||||
{
|
||||
if (PlanOutstandingCaseIds.Contains(FocusCaseId))
|
||||
{
|
||||
UnresolvedCaseIds.Add(FocusCaseId);
|
||||
ResolvedCaseIds.Remove(FocusCaseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnresolvedCaseIds.Remove(FocusCaseId);
|
||||
ResolvedCaseIds.Add(FocusCaseId);
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunRecord RunRecord;
|
||||
if (!Plan.SourceSessionId.IsEmpty()
|
||||
&& UHyperTwistTrainingRepositoryLibrary::TryGetRunRecord(
|
||||
RepositoryState,
|
||||
Plan.SourceSessionId,
|
||||
RunRecord
|
||||
))
|
||||
{
|
||||
for (const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry : RunRecord.AttemptHistory)
|
||||
{
|
||||
if (!AllFocusCaseIds.Contains(AttemptHistoryEntry.CaseId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Summary.TotalAttemptCount += 1;
|
||||
CaseAttemptCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
|
||||
if (AttemptHistoryEntry.DeliveryMode == EHyperTwistTrainingDeliveryMode::RecognitionAssisted
|
||||
|| AttemptHistoryEntry.RecognitionTimeMs > 0)
|
||||
{
|
||||
bAnyRecognition = true;
|
||||
}
|
||||
|
||||
if (AttemptHistoryEntry.Result == EHyperTwistTrainingAttemptResult::Success)
|
||||
{
|
||||
CaseSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
CaseNonSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& UnresolvedCaseId : UnresolvedCaseIds)
|
||||
{
|
||||
ResolvedCaseIds.Remove(UnresolvedCaseId);
|
||||
}
|
||||
|
||||
TSet<FString> PersistentWeakCaseIds;
|
||||
for (const TPair<FString, int32>& Pair : CaseNonSuccessCounts)
|
||||
{
|
||||
const int32 SuccessCount = CaseSuccessCounts.FindRef(Pair.Key);
|
||||
if (Pair.Value >= 2
|
||||
|| (UnresolvedCaseIds.Contains(Pair.Key) && Pair.Value >= 1)
|
||||
|| (SuccessCount == 0 && CaseAttemptCounts.FindRef(Pair.Key) >= 2))
|
||||
{
|
||||
PersistentWeakCaseIds.Add(Pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
Summary.ResolvedCaseIds = ResolvedCaseIds.Array();
|
||||
Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array();
|
||||
Summary.UnresolvedCaseIds = UnresolvedCaseIds.Array();
|
||||
Summary.ResolvedCaseIds.Sort();
|
||||
Summary.PersistentWeakCaseIds.Sort();
|
||||
Summary.UnresolvedCaseIds.Sort();
|
||||
Summary.ResolvedCaseCount = Summary.ResolvedCaseIds.Num();
|
||||
Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num();
|
||||
Summary.UnresolvedCaseCount = Summary.UnresolvedCaseIds.Num();
|
||||
Summary.RepeatedFailureCaseCount = Summary.PersistentWeakCaseCount;
|
||||
|
||||
Summary.bSequenceClosed =
|
||||
!SequenceSummary.bHasOpenPlan
|
||||
&& !SequenceSummary.bNeedsMoreFollowUp
|
||||
&& Summary.UnresolvedCaseCount == 0
|
||||
&& SequenceSummary.CompletedPlanCount > 0
|
||||
&& SequenceSummary.AbortedPlanCount == 0;
|
||||
Summary.bNeedsEscalation =
|
||||
SequenceSummary.AbortedPlanCount > 0
|
||||
|| (SequenceSummary.MaxFollowUpDepth >= 2 && Summary.UnresolvedCaseCount > 0)
|
||||
|| Summary.PersistentWeakCaseCount >= 2
|
||||
|| SequenceSummary.AverageOutcomeScore < 30.0f;
|
||||
const bool bPrefersFollowUpLane =
|
||||
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
|
||||
|| CoachMemorySnapshot.FollowUpGuidanceContinueBias > CoachMemorySnapshot.RecommendedGuidanceContinueBias;
|
||||
const bool bPrefersRecommendedLane =
|
||||
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
|
||||
|| CoachMemorySnapshot.RecommendedGuidanceContinueBias > CoachMemorySnapshot.FollowUpGuidanceContinueBias;
|
||||
const bool bHasRawFollowUpPressure =
|
||||
Summary.UnresolvedCaseCount > 0 || Summary.PersistentWeakCaseCount > 0;
|
||||
Summary.bSpawnFollowUpFromSuppressionMemory =
|
||||
!bHasRawFollowUpPressure
|
||||
&& SequenceSummary.bNeedsMoreFollowUp
|
||||
&& SequenceSummary.CompletedPlanCount > 0
|
||||
&& !Summary.bNeedsEscalation
|
||||
&& bPrefersFollowUpLane
|
||||
&& CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount
|
||||
>= CoachMemorySnapshot.SuppressedFollowUpQueuePressureCount;
|
||||
Summary.bSuppressedBorderlineFollowUpByRecoveryHistory =
|
||||
bHasRawFollowUpPressure
|
||||
&& Summary.PersistentWeakCaseCount == 0
|
||||
&& Summary.UnresolvedCaseCount <= 1
|
||||
&& !SequenceSummary.bNeedsMoreFollowUp
|
||||
&& !Summary.bNeedsEscalation
|
||||
&& bPrefersRecommendedLane
|
||||
&& CoachMemorySnapshot.SuppressedFollowUpQueuePressureCount
|
||||
>= CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount + 2;
|
||||
Summary.bShouldSpawnFollowUp =
|
||||
!Summary.bSequenceClosed
|
||||
&& !SequenceSummary.bHasOpenPlan
|
||||
&& !Summary.bSuppressedBorderlineFollowUpByRecoveryHistory
|
||||
&& (bHasRawFollowUpPressure || Summary.bSpawnFollowUpFromSuppressionMemory);
|
||||
Summary.bShouldArchiveResolvedCases =
|
||||
Summary.ResolvedCaseCount > 0
|
||||
&& (Summary.bSequenceClosed || (!Summary.bShouldSpawnFollowUp && Summary.PersistentWeakCaseCount == 0));
|
||||
|
||||
if (Summary.bSequenceClosed)
|
||||
{
|
||||
Summary.ClosureLabel = TEXT("sequence-closed");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
}
|
||||
else if (Summary.bNeedsEscalation)
|
||||
{
|
||||
Summary.ClosureLabel = TEXT("needs-escalation");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed;
|
||||
}
|
||||
else if (Summary.bShouldSpawnFollowUp)
|
||||
{
|
||||
Summary.ClosureLabel = Summary.bSpawnFollowUpFromSuppressionMemory
|
||||
? TEXT("follow-up-memory-ready")
|
||||
: TEXT("follow-up-ready");
|
||||
Summary.RecommendedNextMode = bAnyRecognition
|
||||
? EHyperTwistTrainingDeliveryMode::RecognitionAssisted
|
||||
: EHyperTwistTrainingDeliveryMode::VirtualCube;
|
||||
}
|
||||
else if (SequenceSummary.bHasOpenPlan)
|
||||
{
|
||||
Summary.ClosureLabel = TEXT("open-active");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed;
|
||||
}
|
||||
else
|
||||
{
|
||||
Summary.ClosureLabel = Summary.bSuppressedBorderlineFollowUpByRecoveryHistory
|
||||
? TEXT("recommended-recovery-stable")
|
||||
: TEXT("sequence-stable");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
}
|
||||
|
||||
Summary.ClosureScore = FMath::Clamp(
|
||||
SequenceSummary.AverageOutcomeScore
|
||||
+ (static_cast<float>(Summary.ResolvedCaseCount) * 6.0f)
|
||||
- (static_cast<float>(Summary.PersistentWeakCaseCount) * 8.0f)
|
||||
- (static_cast<float>(Summary.UnresolvedCaseCount) * 10.0f)
|
||||
- (Summary.bNeedsEscalation ? 20.0f : 0.0f)
|
||||
- (Summary.bSpawnFollowUpFromSuppressionMemory ? 6.0f : 0.0f)
|
||||
+ (Summary.bSuppressedBorderlineFollowUpByRecoveryHistory ? 4.0f : 0.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
|
||||
return Summary;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionClosureSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionClosureSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& AnchorPlan,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan);
|
||||
if (!NormalizedAnchorPlan.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingCoachActionClosureSummary();
|
||||
}
|
||||
|
||||
const FString ResolvedReferenceUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
|
||||
DeriveCoachMemorySnapshot(RepositoryState, NormalizedAnchorPlan.UserId, ResolvedReferenceUtc);
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
RepositoryState,
|
||||
NormalizedAnchorPlan,
|
||||
ResolvedReferenceUtc,
|
||||
CoachMemorySnapshot
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachClosureMemorySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachClosureMemorySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachClosureMemorySummary Summary;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
|
||||
const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId);
|
||||
TMap<FString, FHyperTwistTrainingCoachActionPlan> LatestPlanByRootId;
|
||||
TMap<FString, FString> LatestSortUtcByRootId;
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
|
||||
? NormalizedPlan.RootPlanId
|
||||
: NormalizedPlan.PlanId;
|
||||
const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
|
||||
? NormalizedPlan.CompletedAtUtc
|
||||
: (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc);
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId);
|
||||
FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId);
|
||||
if (ExistingPlan == nullptr || ExistingSortUtc == nullptr)
|
||||
{
|
||||
LatestPlanByRootId.Add(RootPlanId, NormalizedPlan);
|
||||
LatestSortUtcByRootId.Add(RootPlanId, SortUtc);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 CompareResult = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc);
|
||||
if (CompareResult > 0
|
||||
|| (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth))
|
||||
{
|
||||
*ExistingPlan = NormalizedPlan;
|
||||
*ExistingSortUtc = SortUtc;
|
||||
}
|
||||
}
|
||||
|
||||
float ClosureScoreAccumulator = 0.0f;
|
||||
int32 FocusPressureScore = -1;
|
||||
FString FocusSortUtc;
|
||||
TSet<FString> ArchivedResolvedCaseIds;
|
||||
TSet<FString> PersistentWeakCaseIds;
|
||||
TSet<FString> EscalationCaseIds;
|
||||
const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveQueueRecoveryBiasSnapshot(
|
||||
RepositoryState,
|
||||
UserId,
|
||||
Summary.ReferenceUtc
|
||||
);
|
||||
|
||||
for (const TPair<FString, FHyperTwistTrainingCoachActionPlan>& Pair : LatestPlanByRootId)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
RepositoryState,
|
||||
Pair.Value,
|
||||
Summary.ReferenceUtc,
|
||||
CoachMemorySnapshot
|
||||
);
|
||||
if (!ClosureSummary.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Summary.SequenceCount += 1;
|
||||
Summary.ClosedSequenceCount += ClosureSummary.bSequenceClosed ? 1 : 0;
|
||||
Summary.EscalatedSequenceCount += ClosureSummary.bNeedsEscalation ? 1 : 0;
|
||||
Summary.FollowUpReadySequenceCount += ClosureSummary.bShouldSpawnFollowUp ? 1 : 0;
|
||||
Summary.RecoverySustainedFollowUpSequenceCount +=
|
||||
ClosureSummary.bSpawnFollowUpFromSuppressionMemory ? 1 : 0;
|
||||
Summary.RecoverySuppressedBorderlineFollowUpSequenceCount +=
|
||||
ClosureSummary.bSuppressedBorderlineFollowUpByRecoveryHistory ? 1 : 0;
|
||||
ClosureScoreAccumulator += ClosureSummary.ClosureScore;
|
||||
|
||||
if (ClosureSummary.bShouldArchiveResolvedCases)
|
||||
{
|
||||
for (const FString& CaseId : ClosureSummary.ResolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
ArchivedResolvedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
PersistentWeakCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
if (ClosureSummary.bNeedsEscalation)
|
||||
{
|
||||
for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int32 CurrentPressureScore =
|
||||
(ClosureSummary.bNeedsEscalation ? 300 : 0)
|
||||
+ (ClosureSummary.bShouldSpawnFollowUp ? 200 : 0)
|
||||
+ (ClosureSummary.PersistentWeakCaseCount * 10)
|
||||
+ ClosureSummary.UnresolvedCaseCount;
|
||||
const FString SortUtc = !ClosureSummary.ReferenceUtc.IsEmpty()
|
||||
? ClosureSummary.ReferenceUtc
|
||||
: Summary.ReferenceUtc;
|
||||
const int32 TimeCompare = FocusSortUtc.IsEmpty()
|
||||
? 1
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, FocusSortUtc);
|
||||
if (CurrentPressureScore > FocusPressureScore
|
||||
|| (CurrentPressureScore == FocusPressureScore && TimeCompare > 0))
|
||||
{
|
||||
FocusPressureScore = CurrentPressureScore;
|
||||
FocusSortUtc = SortUtc;
|
||||
Summary.FocusRootPlanId = ClosureSummary.RootPlanId;
|
||||
Summary.FocusDeckId = ClosureSummary.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = ClosureSummary.FocusMethodSegmentId;
|
||||
Summary.FocusClosureLabel = ClosureSummary.ClosureLabel;
|
||||
}
|
||||
}
|
||||
|
||||
if (Summary.SequenceCount == 0)
|
||||
{
|
||||
return FHyperTwistTrainingCoachClosureMemorySummary();
|
||||
}
|
||||
|
||||
Summary.ArchivedResolvedCaseIds = ArchivedResolvedCaseIds.Array();
|
||||
Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array();
|
||||
Summary.EscalationCaseIds = EscalationCaseIds.Array();
|
||||
Summary.ArchivedResolvedCaseIds.Sort();
|
||||
Summary.PersistentWeakCaseIds.Sort();
|
||||
Summary.EscalationCaseIds.Sort();
|
||||
Summary.ArchivedResolvedCaseCount = Summary.ArchivedResolvedCaseIds.Num();
|
||||
Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num();
|
||||
Summary.AverageClosureScore = ClosureScoreAccumulator / static_cast<float>(Summary.SequenceCount);
|
||||
Summary.bHasEscalationPressure = Summary.EscalatedSequenceCount > 0 || Summary.EscalationCaseIds.Num() > 0;
|
||||
Summary.bHasFollowUpPressure = Summary.FollowUpReadySequenceCount > 0;
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachRecommendationHistorySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
|
|
@ -30022,441 +29518,6 @@ FHyperTwistTrainingCoachRecommendationHistorySummary UHyperTwistTrainingReposito
|
|||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachCarryForwardPolicySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachCarryForwardPolicySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachCarryForwardPolicySummary Summary;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory =
|
||||
DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory =
|
||||
DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
if (!ClosureMemory.IsStructurallyValid() && !RecommendationHistory.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
Summary.FocusDeckId = !RecommendationHistory.FocusDeckId.IsEmpty()
|
||||
? RecommendationHistory.FocusDeckId
|
||||
: ClosureMemory.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = !RecommendationHistory.FocusMethodSegmentId.IsEmpty()
|
||||
? RecommendationHistory.FocusMethodSegmentId
|
||||
: ClosureMemory.FocusMethodSegmentId;
|
||||
Summary.DominantSuggestedMode = RecommendationHistory.IsStructurallyValid()
|
||||
? RecommendationHistory.DominantSuggestedMode
|
||||
: EHyperTwistTrainingDeliveryMode::Timer;
|
||||
Summary.DominantSelectionPolicy = RecommendationHistory.IsStructurallyValid()
|
||||
? RecommendationHistory.DominantSelectionPolicy
|
||||
: EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
TSet<FString> RecurringCarryForwardCaseIds;
|
||||
TSet<FString> EscalationCarryForwardCaseIds;
|
||||
TSet<FString> PinnedCarryForwardCaseIds;
|
||||
TSet<FString> SuppressedResolvedCaseIds;
|
||||
|
||||
for (const FString& CaseId : RecommendationHistory.RecurringCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
RecurringCarryForwardCaseIds.Add(CaseId);
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureMemory.EscalationCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCarryForwardCaseIds.Add(CaseId);
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : RecommendationHistory.EscalationCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCarryForwardCaseIds.Add(CaseId);
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureMemory.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureMemory.ArchivedResolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty() && !PinnedCarryForwardCaseIds.Contains(CaseId))
|
||||
{
|
||||
SuppressedResolvedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
Summary.RecurringCarryForwardCaseIds = RecurringCarryForwardCaseIds.Array();
|
||||
Summary.EscalationCarryForwardCaseIds = EscalationCarryForwardCaseIds.Array();
|
||||
Summary.PinnedCarryForwardCaseIds = PinnedCarryForwardCaseIds.Array();
|
||||
Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array();
|
||||
Summary.RecurringCarryForwardCaseIds.Sort();
|
||||
Summary.EscalationCarryForwardCaseIds.Sort();
|
||||
Summary.PinnedCarryForwardCaseIds.Sort();
|
||||
Summary.SuppressedResolvedCaseIds.Sort();
|
||||
Summary.RecurringCarryForwardCaseCount = Summary.RecurringCarryForwardCaseIds.Num();
|
||||
Summary.EscalationCarryForwardCaseCount = Summary.EscalationCarryForwardCaseIds.Num();
|
||||
Summary.PinnedCarryForwardCaseCount = Summary.PinnedCarryForwardCaseIds.Num();
|
||||
Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num();
|
||||
|
||||
Summary.bSuppressResolvedResurfacing = Summary.SuppressedResolvedCaseCount > 0;
|
||||
Summary.bPinRecurringCases = Summary.PinnedCarryForwardCaseCount > 0;
|
||||
Summary.bNeedsEscalationHandOff =
|
||||
ClosureMemory.bHasEscalationPressure || Summary.EscalationCarryForwardCaseCount > 0;
|
||||
Summary.bBiasCoachReviewed =
|
||||
Summary.bNeedsEscalationHandOff
|
||||
|| RecommendationHistory.bShouldBiasCoachReviewed
|
||||
|| (Summary.PinnedCarryForwardCaseCount > 0
|
||||
&& Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed);
|
||||
Summary.bHasActiveCarryForwardPressure =
|
||||
Summary.bNeedsEscalationHandOff
|
||||
|| Summary.bPinRecurringCases
|
||||
|| RecommendationHistory.bHasRepeatRecommendationPressure
|
||||
|| ClosureMemory.bHasFollowUpPressure;
|
||||
|
||||
Summary.CarryForwardIntensityScore = FMath::Clamp(
|
||||
(static_cast<float>(Summary.EscalationCarryForwardCaseCount) * 25.0f)
|
||||
+ (static_cast<float>(Summary.RecurringCarryForwardCaseCount) * 12.0f)
|
||||
+ (static_cast<float>(RecommendationHistory.RepeatRecommendationSequenceCount) * 8.0f)
|
||||
+ (ClosureMemory.bHasFollowUpPressure ? 10.0f : 0.0f)
|
||||
- (static_cast<float>(Summary.SuppressedResolvedCaseCount) * 2.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachArchivePolicySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachArchivePolicySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachArchivePolicySummary Summary;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory =
|
||||
DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory =
|
||||
DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachCarryForwardPolicySummary CarryForwardPolicy =
|
||||
DeriveCoachCarryForwardPolicySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
if (!ClosureMemory.IsStructurallyValid()
|
||||
&& !RecommendationHistory.IsStructurallyValid()
|
||||
&& !CarryForwardPolicy.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
Summary.FocusDeckId = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.FocusDeckId
|
||||
: (!RecommendationHistory.FocusDeckId.IsEmpty() ? RecommendationHistory.FocusDeckId : ClosureMemory.FocusDeckId);
|
||||
Summary.FocusMethodSegmentId = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.FocusMethodSegmentId
|
||||
: (!RecommendationHistory.FocusMethodSegmentId.IsEmpty()
|
||||
? RecommendationHistory.FocusMethodSegmentId
|
||||
: ClosureMemory.FocusMethodSegmentId);
|
||||
Summary.DominantSuggestedMode = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.DominantSuggestedMode
|
||||
: RecommendationHistory.DominantSuggestedMode;
|
||||
Summary.DominantSelectionPolicy = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.DominantSelectionPolicy
|
||||
: RecommendationHistory.DominantSelectionPolicy;
|
||||
|
||||
const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId);
|
||||
TMap<FString, FHyperTwistTrainingCoachActionPlan> LatestPlanByRootId;
|
||||
TMap<FString, FString> LatestSortUtcByRootId;
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
|
||||
? NormalizedPlan.RootPlanId
|
||||
: NormalizedPlan.PlanId;
|
||||
const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
|
||||
? NormalizedPlan.CompletedAtUtc
|
||||
: (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc);
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId);
|
||||
FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId);
|
||||
if (ExistingPlan == nullptr || ExistingSortUtc == nullptr)
|
||||
{
|
||||
LatestPlanByRootId.Add(RootPlanId, NormalizedPlan);
|
||||
LatestSortUtcByRootId.Add(RootPlanId, SortUtc);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 CompareResult = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc);
|
||||
if (CompareResult > 0
|
||||
|| (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth))
|
||||
{
|
||||
*ExistingPlan = NormalizedPlan;
|
||||
*ExistingSortUtc = SortUtc;
|
||||
}
|
||||
}
|
||||
|
||||
TMap<FString, int32> ResolvedArchiveHitCounts;
|
||||
TMap<FString, int32> OpenCarryForwardHitCounts;
|
||||
TMap<FString, int32> EscalationHitCounts;
|
||||
TMap<FString, int32> StableArchiveHitCounts;
|
||||
const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveQueueRecoveryBiasSnapshot(
|
||||
RepositoryState,
|
||||
UserId,
|
||||
Summary.ReferenceUtc
|
||||
);
|
||||
|
||||
for (const TPair<FString, FHyperTwistTrainingCoachActionPlan>& Pair : LatestPlanByRootId)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
RepositoryState,
|
||||
Pair.Value,
|
||||
Summary.ReferenceUtc,
|
||||
CoachMemorySnapshot
|
||||
);
|
||||
if (!ClosureSummary.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ClosureSummary.bShouldArchiveResolvedCases)
|
||||
{
|
||||
for (const FString& CaseId : ClosureSummary.ResolvedCaseIds)
|
||||
{
|
||||
if (CaseId.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ResolvedArchiveHitCounts.FindOrAdd(CaseId) += 1;
|
||||
if (ClosureSummary.bSequenceClosed
|
||||
&& !ClosureSummary.bNeedsEscalation
|
||||
&& !ClosureSummary.bShouldSpawnFollowUp)
|
||||
{
|
||||
StableArchiveHitCounts.FindOrAdd(CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1;
|
||||
if (ClosureSummary.bNeedsEscalation)
|
||||
{
|
||||
EscalationHitCounts.FindOrAdd(CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1;
|
||||
if (ClosureSummary.bNeedsEscalation)
|
||||
{
|
||||
EscalationHitCounts.FindOrAdd(CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FString> RecurringCaseIds;
|
||||
for (const FString& CaseId : RecommendationHistory.RecurringCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
RecurringCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FString> LongTermArchivedCaseIds;
|
||||
TSet<FString> ExpiredCarryForwardCaseIds;
|
||||
TSet<FString> RetainedCarryForwardCaseIds;
|
||||
TSet<FString> EscalationRetainedCaseIds;
|
||||
TSet<FString> SuppressedResolvedCaseIds;
|
||||
TSet<FString> StableLongTermArchivedCaseIds;
|
||||
TSet<FString> ProvisionalArchivedCaseIds;
|
||||
TSet<FString> ReopenRiskArchivedCaseIds;
|
||||
|
||||
for (const FString& CaseId : CarryForwardPolicy.SuppressedResolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
LongTermArchivedCaseIds.Add(CaseId);
|
||||
SuppressedResolvedCaseIds.Add(CaseId);
|
||||
|
||||
const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId);
|
||||
const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId);
|
||||
const bool bRecurringCase = RecurringCaseIds.Contains(CaseId);
|
||||
if (StableArchiveHits > 0 && OpenCarryForwardHits == 0 && !bRecurringCase)
|
||||
{
|
||||
StableLongTermArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProvisionalArchivedCaseIds.Add(CaseId);
|
||||
if (bRecurringCase || OpenCarryForwardHits > 0)
|
||||
{
|
||||
ReopenRiskArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool bStableClosureMemory =
|
||||
ClosureMemory.AverageClosureScore >= 70.0f
|
||||
&& !ClosureMemory.bHasEscalationPressure
|
||||
&& !ClosureMemory.bHasFollowUpPressure;
|
||||
|
||||
for (const FString& CaseId : CarryForwardPolicy.PinnedCarryForwardCaseIds)
|
||||
{
|
||||
if (CaseId.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 ResolvedArchiveHits = ResolvedArchiveHitCounts.FindRef(CaseId);
|
||||
const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId);
|
||||
const int32 EscalationHits = EscalationHitCounts.FindRef(CaseId);
|
||||
const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId);
|
||||
const bool bRecurringCase = RecurringCaseIds.Contains(CaseId);
|
||||
|
||||
const bool bCanExpireResolvedCarryForward =
|
||||
EscalationHits == 0
|
||||
&& ResolvedArchiveHits > 0
|
||||
&& OpenCarryForwardHits <= ResolvedArchiveHits
|
||||
&& (StableArchiveHits > 0
|
||||
|| bStableClosureMemory
|
||||
|| (ResolvedArchiveHits >= 2 && !bRecurringCase)
|
||||
|| (ResolvedArchiveHits > OpenCarryForwardHits
|
||||
&& RecommendationHistory.RepeatRecommendationSequenceCount <= 1));
|
||||
|
||||
if (bCanExpireResolvedCarryForward)
|
||||
{
|
||||
ExpiredCarryForwardCaseIds.Add(CaseId);
|
||||
LongTermArchivedCaseIds.Add(CaseId);
|
||||
if (StableArchiveHits > 0 && !bRecurringCase && OpenCarryForwardHits == 0)
|
||||
{
|
||||
StableLongTermArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProvisionalArchivedCaseIds.Add(CaseId);
|
||||
if (bRecurringCase
|
||||
|| RecommendationHistory.bHasRepeatRecommendationPressure
|
||||
|| OpenCarryForwardHits > 0)
|
||||
{
|
||||
ReopenRiskArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
RetainedCarryForwardCaseIds.Add(CaseId);
|
||||
if (EscalationHits > 0)
|
||||
{
|
||||
EscalationRetainedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
Summary.LongTermArchivedCaseIds = LongTermArchivedCaseIds.Array();
|
||||
Summary.ExpiredCarryForwardCaseIds = ExpiredCarryForwardCaseIds.Array();
|
||||
Summary.RetainedCarryForwardCaseIds = RetainedCarryForwardCaseIds.Array();
|
||||
Summary.EscalationRetainedCaseIds = EscalationRetainedCaseIds.Array();
|
||||
Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array();
|
||||
Summary.StableLongTermArchivedCaseIds = StableLongTermArchivedCaseIds.Array();
|
||||
Summary.ProvisionalArchivedCaseIds = ProvisionalArchivedCaseIds.Array();
|
||||
Summary.ReopenRiskArchivedCaseIds = ReopenRiskArchivedCaseIds.Array();
|
||||
Summary.LongTermArchivedCaseIds.Sort();
|
||||
Summary.ExpiredCarryForwardCaseIds.Sort();
|
||||
Summary.RetainedCarryForwardCaseIds.Sort();
|
||||
Summary.EscalationRetainedCaseIds.Sort();
|
||||
Summary.SuppressedResolvedCaseIds.Sort();
|
||||
Summary.StableLongTermArchivedCaseIds.Sort();
|
||||
Summary.ProvisionalArchivedCaseIds.Sort();
|
||||
Summary.ReopenRiskArchivedCaseIds.Sort();
|
||||
|
||||
Summary.LongTermArchivedCaseCount = Summary.LongTermArchivedCaseIds.Num();
|
||||
Summary.ExpiredCarryForwardCaseCount = Summary.ExpiredCarryForwardCaseIds.Num();
|
||||
Summary.RetainedCarryForwardCaseCount = Summary.RetainedCarryForwardCaseIds.Num();
|
||||
Summary.EscalationRetainedCaseCount = Summary.EscalationRetainedCaseIds.Num();
|
||||
Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num();
|
||||
Summary.StableLongTermArchivedCaseCount = Summary.StableLongTermArchivedCaseIds.Num();
|
||||
Summary.ProvisionalArchivedCaseCount = Summary.ProvisionalArchivedCaseIds.Num();
|
||||
Summary.ReopenRiskArchivedCaseCount = Summary.ReopenRiskArchivedCaseIds.Num();
|
||||
|
||||
Summary.bShouldExpireResolvedCarryForward = Summary.ExpiredCarryForwardCaseCount > 0;
|
||||
Summary.bShouldRetainEscalationCarryForward = Summary.EscalationRetainedCaseCount > 0;
|
||||
Summary.ArchiveStabilityScore = FMath::Clamp(
|
||||
ClosureMemory.AverageClosureScore
|
||||
+ (static_cast<float>(Summary.LongTermArchivedCaseCount) * 5.0f)
|
||||
+ (static_cast<float>(Summary.StableLongTermArchivedCaseCount) * 4.0f)
|
||||
+ (bStableClosureMemory ? 10.0f : 0.0f)
|
||||
- (static_cast<float>(Summary.ProvisionalArchivedCaseCount) * 3.0f)
|
||||
- (static_cast<float>(Summary.ReopenRiskArchivedCaseCount) * 8.0f)
|
||||
- (static_cast<float>(Summary.RetainedCarryForwardCaseCount) * 4.0f)
|
||||
- (static_cast<float>(Summary.EscalationRetainedCaseCount) * 10.0f)
|
||||
- (RecommendationHistory.bHasRepeatRecommendationPressure ? 6.0f : 0.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
Summary.ArchiveRetentionTuningScore = FMath::Clamp(
|
||||
(static_cast<float>(Summary.ReopenRiskArchivedCaseCount) * 18.0f)
|
||||
+ (static_cast<float>(Summary.ProvisionalArchivedCaseCount) * 9.0f)
|
||||
+ (RecommendationHistory.bHasRepeatRecommendationPressure ? 12.0f : 0.0f)
|
||||
+ (Summary.bShouldRetainEscalationCarryForward ? 14.0f : 0.0f)
|
||||
- (static_cast<float>(Summary.StableLongTermArchivedCaseCount) * 6.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
Summary.bHasStableArchiveMemory =
|
||||
Summary.LongTermArchivedCaseCount > 0
|
||||
&& Summary.ArchiveStabilityScore >= 55.0f
|
||||
&& Summary.EscalationRetainedCaseCount == 0;
|
||||
Summary.bNeedsArchiveRetentionTuning =
|
||||
Summary.ReopenRiskArchivedCaseCount > 0
|
||||
|| Summary.ArchiveRetentionTuningScore >= 25.0f
|
||||
|| (Summary.ProvisionalArchivedCaseCount > Summary.StableLongTermArchivedCaseCount
|
||||
&& Summary.LongTermArchivedCaseCount > 0);
|
||||
Summary.bHasActiveArchivePressure =
|
||||
Summary.RetainedCarryForwardCaseCount > 0
|
||||
|| Summary.EscalationRetainedCaseCount > 0
|
||||
|| Summary.bNeedsArchiveRetentionTuning;
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseRecommendation UHyperTwistTrainingRepositoryLibrary::MakeCaseRecommendation(
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
const FHyperTwistTrainingCaseLearnerState& LearnerState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,946 @@
|
|||
// Copyright HyperTwist, Inc. All Rights Reserved.
|
||||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
|
||||
#include "HyperTwistTrainingRepositoryLibraryInternal.h"
|
||||
|
||||
namespace HyperTwistTrainingRepositoryLibraryInternal
|
||||
{
|
||||
FHyperTwistTrainingCoachActionClosureSummary DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& AnchorPlan,
|
||||
const FString& ReferenceUtc,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionClosureSummary Summary;
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan);
|
||||
if (!NormalizedAnchorPlan.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachActionSequenceSummary SequenceSummary =
|
||||
UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary(
|
||||
RepositoryState,
|
||||
NormalizedAnchorPlan,
|
||||
ReferenceUtc
|
||||
);
|
||||
if (!SequenceSummary.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = SequenceSummary.UserId;
|
||||
Summary.ReferenceUtc = SequenceSummary.ReferenceUtc;
|
||||
Summary.RootPlanId = SequenceSummary.RootPlanId;
|
||||
Summary.LatestPlanId = SequenceSummary.LatestPlanId;
|
||||
Summary.FocusDeckId = SequenceSummary.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = SequenceSummary.FocusMethodSegmentId;
|
||||
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> SequencePlans;
|
||||
const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans =
|
||||
UHyperTwistTrainingRepositoryLibrary::ListCoachActionPlansForUser(
|
||||
RepositoryState,
|
||||
NormalizedAnchorPlan.UserId
|
||||
);
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
|
||||
const FString CandidateRootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
|
||||
? NormalizedPlan.RootPlanId
|
||||
: NormalizedPlan.PlanId;
|
||||
if (CandidateRootPlanId == SequenceSummary.RootPlanId
|
||||
|| NormalizedPlan.PlanId == SequenceSummary.RootPlanId
|
||||
|| NormalizedPlan.ParentPlanId == SequenceSummary.RootPlanId)
|
||||
{
|
||||
SequencePlans.Add(NormalizedPlan);
|
||||
}
|
||||
}
|
||||
if (SequencePlans.Num() == 0)
|
||||
{
|
||||
SequencePlans.Add(NormalizedAnchorPlan);
|
||||
}
|
||||
const auto ResolvePlanSortUtc = [](const FHyperTwistTrainingCoachActionPlan& Plan) -> FString
|
||||
{
|
||||
return !Plan.CompletedAtUtc.IsEmpty()
|
||||
? Plan.CompletedAtUtc
|
||||
: (!Plan.StartedAtUtc.IsEmpty() ? Plan.StartedAtUtc : Plan.CreatedAtUtc);
|
||||
};
|
||||
SequencePlans.Sort(
|
||||
[&ResolvePlanSortUtc](const FHyperTwistTrainingCoachActionPlan& Left,
|
||||
const FHyperTwistTrainingCoachActionPlan& Right)
|
||||
{
|
||||
const FString LeftSortUtc = ResolvePlanSortUtc(Left);
|
||||
const FString RightSortUtc = ResolvePlanSortUtc(Right);
|
||||
const int32 CompareResult =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(LeftSortUtc, RightSortUtc);
|
||||
if (CompareResult != 0)
|
||||
{
|
||||
return CompareResult < 0;
|
||||
}
|
||||
if (Left.FollowUpDepth != Right.FollowUpDepth)
|
||||
{
|
||||
return Left.FollowUpDepth < Right.FollowUpDepth;
|
||||
}
|
||||
|
||||
return Left.PlanId < Right.PlanId;
|
||||
}
|
||||
);
|
||||
|
||||
TSet<FString> AllFocusCaseIds;
|
||||
TSet<FString> ResolvedCaseIds;
|
||||
TSet<FString> UnresolvedCaseIds;
|
||||
TMap<FString, int32> CaseAttemptCounts;
|
||||
TMap<FString, int32> CaseNonSuccessCounts;
|
||||
TMap<FString, int32> CaseSuccessCounts;
|
||||
bool bAnyRecognition = false;
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : SequencePlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlanOutcomeSummary OutcomeSummary =
|
||||
UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary(
|
||||
RepositoryState,
|
||||
Plan,
|
||||
SequenceSummary.ReferenceUtc
|
||||
);
|
||||
TSet<FString> PlanFocusCaseIds;
|
||||
for (const FString& FocusCaseId : Plan.FocusCaseIds)
|
||||
{
|
||||
if (!FocusCaseId.IsEmpty())
|
||||
{
|
||||
PlanFocusCaseIds.Add(FocusCaseId);
|
||||
AllFocusCaseIds.Add(FocusCaseId);
|
||||
}
|
||||
}
|
||||
for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : Plan.Entries)
|
||||
{
|
||||
if (!Entry.CaseId.IsEmpty())
|
||||
{
|
||||
PlanFocusCaseIds.Add(Entry.CaseId);
|
||||
AllFocusCaseIds.Add(Entry.CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FString> PlanOutstandingCaseIds;
|
||||
for (const FString& FollowUpCaseId : OutcomeSummary.FollowUpCaseIds)
|
||||
{
|
||||
if (!FollowUpCaseId.IsEmpty())
|
||||
{
|
||||
PlanOutstandingCaseIds.Add(FollowUpCaseId);
|
||||
UnresolvedCaseIds.Add(FollowUpCaseId);
|
||||
ResolvedCaseIds.Remove(FollowUpCaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& DeferredCaseId : OutcomeSummary.DeferredLaunchCaseIds)
|
||||
{
|
||||
if (!DeferredCaseId.IsEmpty())
|
||||
{
|
||||
PlanOutstandingCaseIds.Add(DeferredCaseId);
|
||||
AllFocusCaseIds.Add(DeferredCaseId);
|
||||
UnresolvedCaseIds.Add(DeferredCaseId);
|
||||
ResolvedCaseIds.Remove(DeferredCaseId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& FocusCaseId : PlanFocusCaseIds)
|
||||
{
|
||||
if (PlanOutstandingCaseIds.Contains(FocusCaseId))
|
||||
{
|
||||
UnresolvedCaseIds.Add(FocusCaseId);
|
||||
ResolvedCaseIds.Remove(FocusCaseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnresolvedCaseIds.Remove(FocusCaseId);
|
||||
ResolvedCaseIds.Add(FocusCaseId);
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunRecord RunRecord;
|
||||
if (!Plan.SourceSessionId.IsEmpty()
|
||||
&& UHyperTwistTrainingRepositoryLibrary::TryGetRunRecord(
|
||||
RepositoryState,
|
||||
Plan.SourceSessionId,
|
||||
RunRecord
|
||||
))
|
||||
{
|
||||
for (const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry : RunRecord.AttemptHistory)
|
||||
{
|
||||
if (!AllFocusCaseIds.Contains(AttemptHistoryEntry.CaseId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Summary.TotalAttemptCount += 1;
|
||||
CaseAttemptCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
|
||||
if (AttemptHistoryEntry.DeliveryMode == EHyperTwistTrainingDeliveryMode::RecognitionAssisted
|
||||
|| AttemptHistoryEntry.RecognitionTimeMs > 0)
|
||||
{
|
||||
bAnyRecognition = true;
|
||||
}
|
||||
|
||||
if (AttemptHistoryEntry.Result == EHyperTwistTrainingAttemptResult::Success)
|
||||
{
|
||||
CaseSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
CaseNonSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& UnresolvedCaseId : UnresolvedCaseIds)
|
||||
{
|
||||
ResolvedCaseIds.Remove(UnresolvedCaseId);
|
||||
}
|
||||
|
||||
TSet<FString> PersistentWeakCaseIds;
|
||||
for (const TPair<FString, int32>& Pair : CaseNonSuccessCounts)
|
||||
{
|
||||
const int32 SuccessCount = CaseSuccessCounts.FindRef(Pair.Key);
|
||||
if (Pair.Value >= 2
|
||||
|| (UnresolvedCaseIds.Contains(Pair.Key) && Pair.Value >= 1)
|
||||
|| (SuccessCount == 0 && CaseAttemptCounts.FindRef(Pair.Key) >= 2))
|
||||
{
|
||||
PersistentWeakCaseIds.Add(Pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
Summary.ResolvedCaseIds = ResolvedCaseIds.Array();
|
||||
Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array();
|
||||
Summary.UnresolvedCaseIds = UnresolvedCaseIds.Array();
|
||||
Summary.ResolvedCaseIds.Sort();
|
||||
Summary.PersistentWeakCaseIds.Sort();
|
||||
Summary.UnresolvedCaseIds.Sort();
|
||||
Summary.ResolvedCaseCount = Summary.ResolvedCaseIds.Num();
|
||||
Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num();
|
||||
Summary.UnresolvedCaseCount = Summary.UnresolvedCaseIds.Num();
|
||||
Summary.RepeatedFailureCaseCount = Summary.PersistentWeakCaseCount;
|
||||
|
||||
Summary.bSequenceClosed =
|
||||
!SequenceSummary.bHasOpenPlan
|
||||
&& !SequenceSummary.bNeedsMoreFollowUp
|
||||
&& Summary.UnresolvedCaseCount == 0
|
||||
&& SequenceSummary.CompletedPlanCount > 0
|
||||
&& SequenceSummary.AbortedPlanCount == 0;
|
||||
Summary.bNeedsEscalation =
|
||||
SequenceSummary.AbortedPlanCount > 0
|
||||
|| (SequenceSummary.MaxFollowUpDepth >= 2 && Summary.UnresolvedCaseCount > 0)
|
||||
|| Summary.PersistentWeakCaseCount >= 2
|
||||
|| SequenceSummary.AverageOutcomeScore < 30.0f;
|
||||
const bool bPrefersFollowUpLane =
|
||||
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
|
||||
|| CoachMemorySnapshot.FollowUpGuidanceContinueBias > CoachMemorySnapshot.RecommendedGuidanceContinueBias;
|
||||
const bool bPrefersRecommendedLane =
|
||||
CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
|
||||
|| CoachMemorySnapshot.RecommendedGuidanceContinueBias > CoachMemorySnapshot.FollowUpGuidanceContinueBias;
|
||||
const bool bHasRawFollowUpPressure =
|
||||
Summary.UnresolvedCaseCount > 0 || Summary.PersistentWeakCaseCount > 0;
|
||||
Summary.bSpawnFollowUpFromSuppressionMemory =
|
||||
!bHasRawFollowUpPressure
|
||||
&& SequenceSummary.bNeedsMoreFollowUp
|
||||
&& SequenceSummary.CompletedPlanCount > 0
|
||||
&& !Summary.bNeedsEscalation
|
||||
&& bPrefersFollowUpLane
|
||||
&& CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount
|
||||
>= CoachMemorySnapshot.SuppressedFollowUpQueuePressureCount;
|
||||
Summary.bSuppressedBorderlineFollowUpByRecoveryHistory =
|
||||
bHasRawFollowUpPressure
|
||||
&& Summary.PersistentWeakCaseCount == 0
|
||||
&& Summary.UnresolvedCaseCount <= 1
|
||||
&& !SequenceSummary.bNeedsMoreFollowUp
|
||||
&& !Summary.bNeedsEscalation
|
||||
&& bPrefersRecommendedLane
|
||||
&& CoachMemorySnapshot.SuppressedFollowUpQueuePressureCount
|
||||
>= CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount + 2;
|
||||
Summary.bShouldSpawnFollowUp =
|
||||
!Summary.bSequenceClosed
|
||||
&& !SequenceSummary.bHasOpenPlan
|
||||
&& !Summary.bSuppressedBorderlineFollowUpByRecoveryHistory
|
||||
&& (bHasRawFollowUpPressure || Summary.bSpawnFollowUpFromSuppressionMemory);
|
||||
Summary.bShouldArchiveResolvedCases =
|
||||
Summary.ResolvedCaseCount > 0
|
||||
&& (Summary.bSequenceClosed || (!Summary.bShouldSpawnFollowUp && Summary.PersistentWeakCaseCount == 0));
|
||||
|
||||
if (Summary.bSequenceClosed)
|
||||
{
|
||||
Summary.ClosureLabel = TEXT("sequence-closed");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
}
|
||||
else if (Summary.bNeedsEscalation)
|
||||
{
|
||||
Summary.ClosureLabel = TEXT("needs-escalation");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed;
|
||||
}
|
||||
else if (Summary.bShouldSpawnFollowUp)
|
||||
{
|
||||
Summary.ClosureLabel = Summary.bSpawnFollowUpFromSuppressionMemory
|
||||
? TEXT("follow-up-memory-ready")
|
||||
: TEXT("follow-up-ready");
|
||||
Summary.RecommendedNextMode = bAnyRecognition
|
||||
? EHyperTwistTrainingDeliveryMode::RecognitionAssisted
|
||||
: EHyperTwistTrainingDeliveryMode::VirtualCube;
|
||||
}
|
||||
else if (SequenceSummary.bHasOpenPlan)
|
||||
{
|
||||
Summary.ClosureLabel = TEXT("open-active");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed;
|
||||
}
|
||||
else
|
||||
{
|
||||
Summary.ClosureLabel = Summary.bSuppressedBorderlineFollowUpByRecoveryHistory
|
||||
? TEXT("recommended-recovery-stable")
|
||||
: TEXT("sequence-stable");
|
||||
Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
}
|
||||
|
||||
Summary.ClosureScore = FMath::Clamp(
|
||||
SequenceSummary.AverageOutcomeScore
|
||||
+ (static_cast<float>(Summary.ResolvedCaseCount) * 6.0f)
|
||||
- (static_cast<float>(Summary.PersistentWeakCaseCount) * 8.0f)
|
||||
- (static_cast<float>(Summary.UnresolvedCaseCount) * 10.0f)
|
||||
- (Summary.bNeedsEscalation ? 20.0f : 0.0f)
|
||||
- (Summary.bSpawnFollowUpFromSuppressionMemory ? 6.0f : 0.0f)
|
||||
+ (Summary.bSuppressedBorderlineFollowUpByRecoveryHistory ? 4.0f : 0.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
|
||||
return Summary;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionClosureSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionClosureSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& AnchorPlan,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan);
|
||||
if (!NormalizedAnchorPlan.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingCoachActionClosureSummary();
|
||||
}
|
||||
|
||||
const FString ResolvedReferenceUtc =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
|
||||
DeriveCoachMemorySnapshot(RepositoryState, NormalizedAnchorPlan.UserId, ResolvedReferenceUtc);
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
RepositoryState,
|
||||
NormalizedAnchorPlan,
|
||||
ResolvedReferenceUtc,
|
||||
CoachMemorySnapshot
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachClosureMemorySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachClosureMemorySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachClosureMemorySummary Summary;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
|
||||
const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId);
|
||||
TMap<FString, FHyperTwistTrainingCoachActionPlan> LatestPlanByRootId;
|
||||
TMap<FString, FString> LatestSortUtcByRootId;
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
|
||||
? NormalizedPlan.RootPlanId
|
||||
: NormalizedPlan.PlanId;
|
||||
const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
|
||||
? NormalizedPlan.CompletedAtUtc
|
||||
: (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc);
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId);
|
||||
FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId);
|
||||
if (ExistingPlan == nullptr || ExistingSortUtc == nullptr)
|
||||
{
|
||||
LatestPlanByRootId.Add(RootPlanId, NormalizedPlan);
|
||||
LatestSortUtcByRootId.Add(RootPlanId, SortUtc);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 CompareResult =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc);
|
||||
if (CompareResult > 0
|
||||
|| (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth))
|
||||
{
|
||||
*ExistingPlan = NormalizedPlan;
|
||||
*ExistingSortUtc = SortUtc;
|
||||
}
|
||||
}
|
||||
|
||||
float ClosureScoreAccumulator = 0.0f;
|
||||
int32 FocusPressureScore = -1;
|
||||
FString FocusSortUtc;
|
||||
TSet<FString> ArchivedResolvedCaseIds;
|
||||
TSet<FString> PersistentWeakCaseIds;
|
||||
TSet<FString> EscalationCaseIds;
|
||||
const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveQueueRecoveryBiasSnapshot(
|
||||
RepositoryState,
|
||||
UserId,
|
||||
Summary.ReferenceUtc
|
||||
);
|
||||
|
||||
for (const TPair<FString, FHyperTwistTrainingCoachActionPlan>& Pair : LatestPlanByRootId)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
RepositoryState,
|
||||
Pair.Value,
|
||||
Summary.ReferenceUtc,
|
||||
CoachMemorySnapshot
|
||||
);
|
||||
if (!ClosureSummary.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Summary.SequenceCount += 1;
|
||||
Summary.ClosedSequenceCount += ClosureSummary.bSequenceClosed ? 1 : 0;
|
||||
Summary.EscalatedSequenceCount += ClosureSummary.bNeedsEscalation ? 1 : 0;
|
||||
Summary.FollowUpReadySequenceCount += ClosureSummary.bShouldSpawnFollowUp ? 1 : 0;
|
||||
Summary.RecoverySustainedFollowUpSequenceCount +=
|
||||
ClosureSummary.bSpawnFollowUpFromSuppressionMemory ? 1 : 0;
|
||||
Summary.RecoverySuppressedBorderlineFollowUpSequenceCount +=
|
||||
ClosureSummary.bSuppressedBorderlineFollowUpByRecoveryHistory ? 1 : 0;
|
||||
ClosureScoreAccumulator += ClosureSummary.ClosureScore;
|
||||
|
||||
if (ClosureSummary.bShouldArchiveResolvedCases)
|
||||
{
|
||||
for (const FString& CaseId : ClosureSummary.ResolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
ArchivedResolvedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
PersistentWeakCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
if (ClosureSummary.bNeedsEscalation)
|
||||
{
|
||||
for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int32 CurrentPressureScore =
|
||||
(ClosureSummary.bNeedsEscalation ? 300 : 0)
|
||||
+ (ClosureSummary.bShouldSpawnFollowUp ? 200 : 0)
|
||||
+ (ClosureSummary.PersistentWeakCaseCount * 10)
|
||||
+ ClosureSummary.UnresolvedCaseCount;
|
||||
const FString SortUtc = !ClosureSummary.ReferenceUtc.IsEmpty()
|
||||
? ClosureSummary.ReferenceUtc
|
||||
: Summary.ReferenceUtc;
|
||||
const int32 TimeCompare = FocusSortUtc.IsEmpty()
|
||||
? 1
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, FocusSortUtc);
|
||||
if (CurrentPressureScore > FocusPressureScore
|
||||
|| (CurrentPressureScore == FocusPressureScore && TimeCompare > 0))
|
||||
{
|
||||
FocusPressureScore = CurrentPressureScore;
|
||||
FocusSortUtc = SortUtc;
|
||||
Summary.FocusRootPlanId = ClosureSummary.RootPlanId;
|
||||
Summary.FocusDeckId = ClosureSummary.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = ClosureSummary.FocusMethodSegmentId;
|
||||
Summary.FocusClosureLabel = ClosureSummary.ClosureLabel;
|
||||
}
|
||||
}
|
||||
|
||||
if (Summary.SequenceCount == 0)
|
||||
{
|
||||
return FHyperTwistTrainingCoachClosureMemorySummary();
|
||||
}
|
||||
|
||||
Summary.ArchivedResolvedCaseIds = ArchivedResolvedCaseIds.Array();
|
||||
Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array();
|
||||
Summary.EscalationCaseIds = EscalationCaseIds.Array();
|
||||
Summary.ArchivedResolvedCaseIds.Sort();
|
||||
Summary.PersistentWeakCaseIds.Sort();
|
||||
Summary.EscalationCaseIds.Sort();
|
||||
Summary.ArchivedResolvedCaseCount = Summary.ArchivedResolvedCaseIds.Num();
|
||||
Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num();
|
||||
Summary.AverageClosureScore = ClosureScoreAccumulator / static_cast<float>(Summary.SequenceCount);
|
||||
Summary.bHasEscalationPressure = Summary.EscalatedSequenceCount > 0 || Summary.EscalationCaseIds.Num() > 0;
|
||||
Summary.bHasFollowUpPressure = Summary.FollowUpReadySequenceCount > 0;
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachCarryForwardPolicySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachCarryForwardPolicySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachCarryForwardPolicySummary Summary;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory =
|
||||
DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory =
|
||||
DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
if (!ClosureMemory.IsStructurallyValid() && !RecommendationHistory.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
Summary.FocusDeckId = !RecommendationHistory.FocusDeckId.IsEmpty()
|
||||
? RecommendationHistory.FocusDeckId
|
||||
: ClosureMemory.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = !RecommendationHistory.FocusMethodSegmentId.IsEmpty()
|
||||
? RecommendationHistory.FocusMethodSegmentId
|
||||
: ClosureMemory.FocusMethodSegmentId;
|
||||
Summary.DominantSuggestedMode = RecommendationHistory.IsStructurallyValid()
|
||||
? RecommendationHistory.DominantSuggestedMode
|
||||
: EHyperTwistTrainingDeliveryMode::Timer;
|
||||
Summary.DominantSelectionPolicy = RecommendationHistory.IsStructurallyValid()
|
||||
? RecommendationHistory.DominantSelectionPolicy
|
||||
: EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
TSet<FString> RecurringCarryForwardCaseIds;
|
||||
TSet<FString> EscalationCarryForwardCaseIds;
|
||||
TSet<FString> PinnedCarryForwardCaseIds;
|
||||
TSet<FString> SuppressedResolvedCaseIds;
|
||||
|
||||
for (const FString& CaseId : RecommendationHistory.RecurringCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
RecurringCarryForwardCaseIds.Add(CaseId);
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureMemory.EscalationCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCarryForwardCaseIds.Add(CaseId);
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : RecommendationHistory.EscalationCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
EscalationCarryForwardCaseIds.Add(CaseId);
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureMemory.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
PinnedCarryForwardCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureMemory.ArchivedResolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty() && !PinnedCarryForwardCaseIds.Contains(CaseId))
|
||||
{
|
||||
SuppressedResolvedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
Summary.RecurringCarryForwardCaseIds = RecurringCarryForwardCaseIds.Array();
|
||||
Summary.EscalationCarryForwardCaseIds = EscalationCarryForwardCaseIds.Array();
|
||||
Summary.PinnedCarryForwardCaseIds = PinnedCarryForwardCaseIds.Array();
|
||||
Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array();
|
||||
Summary.RecurringCarryForwardCaseIds.Sort();
|
||||
Summary.EscalationCarryForwardCaseIds.Sort();
|
||||
Summary.PinnedCarryForwardCaseIds.Sort();
|
||||
Summary.SuppressedResolvedCaseIds.Sort();
|
||||
Summary.RecurringCarryForwardCaseCount = Summary.RecurringCarryForwardCaseIds.Num();
|
||||
Summary.EscalationCarryForwardCaseCount = Summary.EscalationCarryForwardCaseIds.Num();
|
||||
Summary.PinnedCarryForwardCaseCount = Summary.PinnedCarryForwardCaseIds.Num();
|
||||
Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num();
|
||||
|
||||
Summary.bSuppressResolvedResurfacing = Summary.SuppressedResolvedCaseCount > 0;
|
||||
Summary.bPinRecurringCases = Summary.PinnedCarryForwardCaseCount > 0;
|
||||
Summary.bNeedsEscalationHandOff =
|
||||
ClosureMemory.bHasEscalationPressure || Summary.EscalationCarryForwardCaseCount > 0;
|
||||
Summary.bBiasCoachReviewed =
|
||||
Summary.bNeedsEscalationHandOff
|
||||
|| RecommendationHistory.bShouldBiasCoachReviewed
|
||||
|| (Summary.PinnedCarryForwardCaseCount > 0
|
||||
&& Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed);
|
||||
Summary.bHasActiveCarryForwardPressure =
|
||||
Summary.bNeedsEscalationHandOff
|
||||
|| Summary.bPinRecurringCases
|
||||
|| RecommendationHistory.bHasRepeatRecommendationPressure
|
||||
|| ClosureMemory.bHasFollowUpPressure;
|
||||
|
||||
Summary.CarryForwardIntensityScore = FMath::Clamp(
|
||||
(static_cast<float>(Summary.EscalationCarryForwardCaseCount) * 25.0f)
|
||||
+ (static_cast<float>(Summary.RecurringCarryForwardCaseCount) * 12.0f)
|
||||
+ (static_cast<float>(RecommendationHistory.RepeatRecommendationSequenceCount) * 8.0f)
|
||||
+ (ClosureMemory.bHasFollowUpPressure ? 10.0f : 0.0f)
|
||||
- (static_cast<float>(Summary.SuppressedResolvedCaseCount) * 2.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachArchivePolicySummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachArchivePolicySummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachArchivePolicySummary Summary;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory =
|
||||
DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory =
|
||||
DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
const FHyperTwistTrainingCoachCarryForwardPolicySummary CarryForwardPolicy =
|
||||
DeriveCoachCarryForwardPolicySummary(RepositoryState, UserId, ReferenceUtc);
|
||||
if (!ClosureMemory.IsStructurallyValid()
|
||||
&& !RecommendationHistory.IsStructurallyValid()
|
||||
&& !CarryForwardPolicy.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
Summary.FocusDeckId = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.FocusDeckId
|
||||
: (!RecommendationHistory.FocusDeckId.IsEmpty() ? RecommendationHistory.FocusDeckId : ClosureMemory.FocusDeckId);
|
||||
Summary.FocusMethodSegmentId = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.FocusMethodSegmentId
|
||||
: (!RecommendationHistory.FocusMethodSegmentId.IsEmpty()
|
||||
? RecommendationHistory.FocusMethodSegmentId
|
||||
: ClosureMemory.FocusMethodSegmentId);
|
||||
Summary.DominantSuggestedMode = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.DominantSuggestedMode
|
||||
: RecommendationHistory.DominantSuggestedMode;
|
||||
Summary.DominantSelectionPolicy = CarryForwardPolicy.IsStructurallyValid()
|
||||
? CarryForwardPolicy.DominantSelectionPolicy
|
||||
: RecommendationHistory.DominantSelectionPolicy;
|
||||
|
||||
const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId);
|
||||
TMap<FString, FHyperTwistTrainingCoachActionPlan> LatestPlanByRootId;
|
||||
TMap<FString, FString> LatestSortUtcByRootId;
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
|
||||
? NormalizedPlan.RootPlanId
|
||||
: NormalizedPlan.PlanId;
|
||||
const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
|
||||
? NormalizedPlan.CompletedAtUtc
|
||||
: (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc);
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId);
|
||||
FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId);
|
||||
if (ExistingPlan == nullptr || ExistingSortUtc == nullptr)
|
||||
{
|
||||
LatestPlanByRootId.Add(RootPlanId, NormalizedPlan);
|
||||
LatestSortUtcByRootId.Add(RootPlanId, SortUtc);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 CompareResult =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc);
|
||||
if (CompareResult > 0
|
||||
|| (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth))
|
||||
{
|
||||
*ExistingPlan = NormalizedPlan;
|
||||
*ExistingSortUtc = SortUtc;
|
||||
}
|
||||
}
|
||||
|
||||
TMap<FString, int32> ResolvedArchiveHitCounts;
|
||||
TMap<FString, int32> OpenCarryForwardHitCounts;
|
||||
TMap<FString, int32> EscalationHitCounts;
|
||||
TMap<FString, int32> StableArchiveHitCounts;
|
||||
const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveQueueRecoveryBiasSnapshot(
|
||||
RepositoryState,
|
||||
UserId,
|
||||
Summary.ReferenceUtc
|
||||
);
|
||||
|
||||
for (const TPair<FString, FHyperTwistTrainingCoachActionPlan>& Pair : LatestPlanByRootId)
|
||||
{
|
||||
const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
RepositoryState,
|
||||
Pair.Value,
|
||||
Summary.ReferenceUtc,
|
||||
CoachMemorySnapshot
|
||||
);
|
||||
if (!ClosureSummary.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ClosureSummary.bShouldArchiveResolvedCases)
|
||||
{
|
||||
for (const FString& CaseId : ClosureSummary.ResolvedCaseIds)
|
||||
{
|
||||
if (CaseId.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ResolvedArchiveHitCounts.FindOrAdd(CaseId) += 1;
|
||||
if (ClosureSummary.bSequenceClosed
|
||||
&& !ClosureSummary.bNeedsEscalation
|
||||
&& !ClosureSummary.bShouldSpawnFollowUp)
|
||||
{
|
||||
StableArchiveHitCounts.FindOrAdd(CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1;
|
||||
if (ClosureSummary.bNeedsEscalation)
|
||||
{
|
||||
EscalationHitCounts.FindOrAdd(CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1;
|
||||
if (ClosureSummary.bNeedsEscalation)
|
||||
{
|
||||
EscalationHitCounts.FindOrAdd(CaseId) += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FString> RecurringCaseIds;
|
||||
for (const FString& CaseId : RecommendationHistory.RecurringCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
RecurringCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FString> LongTermArchivedCaseIds;
|
||||
TSet<FString> ExpiredCarryForwardCaseIds;
|
||||
TSet<FString> RetainedCarryForwardCaseIds;
|
||||
TSet<FString> EscalationRetainedCaseIds;
|
||||
TSet<FString> SuppressedResolvedCaseIds;
|
||||
TSet<FString> StableLongTermArchivedCaseIds;
|
||||
TSet<FString> ProvisionalArchivedCaseIds;
|
||||
TSet<FString> ReopenRiskArchivedCaseIds;
|
||||
|
||||
for (const FString& CaseId : CarryForwardPolicy.SuppressedResolvedCaseIds)
|
||||
{
|
||||
if (!CaseId.IsEmpty())
|
||||
{
|
||||
LongTermArchivedCaseIds.Add(CaseId);
|
||||
SuppressedResolvedCaseIds.Add(CaseId);
|
||||
|
||||
const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId);
|
||||
const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId);
|
||||
const bool bRecurringCase = RecurringCaseIds.Contains(CaseId);
|
||||
if (StableArchiveHits > 0 && OpenCarryForwardHits == 0 && !bRecurringCase)
|
||||
{
|
||||
StableLongTermArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProvisionalArchivedCaseIds.Add(CaseId);
|
||||
if (bRecurringCase || OpenCarryForwardHits > 0)
|
||||
{
|
||||
ReopenRiskArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool bStableClosureMemory =
|
||||
ClosureMemory.AverageClosureScore >= 70.0f
|
||||
&& !ClosureMemory.bHasEscalationPressure
|
||||
&& !ClosureMemory.bHasFollowUpPressure;
|
||||
|
||||
for (const FString& CaseId : CarryForwardPolicy.PinnedCarryForwardCaseIds)
|
||||
{
|
||||
if (CaseId.IsEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32 ResolvedArchiveHits = ResolvedArchiveHitCounts.FindRef(CaseId);
|
||||
const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId);
|
||||
const int32 EscalationHits = EscalationHitCounts.FindRef(CaseId);
|
||||
const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId);
|
||||
const bool bRecurringCase = RecurringCaseIds.Contains(CaseId);
|
||||
|
||||
const bool bCanExpireResolvedCarryForward =
|
||||
EscalationHits == 0
|
||||
&& ResolvedArchiveHits > 0
|
||||
&& OpenCarryForwardHits <= ResolvedArchiveHits
|
||||
&& (StableArchiveHits > 0
|
||||
|| bStableClosureMemory
|
||||
|| (ResolvedArchiveHits >= 2 && !bRecurringCase)
|
||||
|| (ResolvedArchiveHits > OpenCarryForwardHits
|
||||
&& RecommendationHistory.RepeatRecommendationSequenceCount <= 1));
|
||||
|
||||
if (bCanExpireResolvedCarryForward)
|
||||
{
|
||||
ExpiredCarryForwardCaseIds.Add(CaseId);
|
||||
LongTermArchivedCaseIds.Add(CaseId);
|
||||
if (StableArchiveHits > 0 && !bRecurringCase && OpenCarryForwardHits == 0)
|
||||
{
|
||||
StableLongTermArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProvisionalArchivedCaseIds.Add(CaseId);
|
||||
if (bRecurringCase
|
||||
|| RecommendationHistory.bHasRepeatRecommendationPressure
|
||||
|| OpenCarryForwardHits > 0)
|
||||
{
|
||||
ReopenRiskArchivedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
RetainedCarryForwardCaseIds.Add(CaseId);
|
||||
if (EscalationHits > 0)
|
||||
{
|
||||
EscalationRetainedCaseIds.Add(CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
Summary.LongTermArchivedCaseIds = LongTermArchivedCaseIds.Array();
|
||||
Summary.ExpiredCarryForwardCaseIds = ExpiredCarryForwardCaseIds.Array();
|
||||
Summary.RetainedCarryForwardCaseIds = RetainedCarryForwardCaseIds.Array();
|
||||
Summary.EscalationRetainedCaseIds = EscalationRetainedCaseIds.Array();
|
||||
Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array();
|
||||
Summary.StableLongTermArchivedCaseIds = StableLongTermArchivedCaseIds.Array();
|
||||
Summary.ProvisionalArchivedCaseIds = ProvisionalArchivedCaseIds.Array();
|
||||
Summary.ReopenRiskArchivedCaseIds = ReopenRiskArchivedCaseIds.Array();
|
||||
Summary.LongTermArchivedCaseIds.Sort();
|
||||
Summary.ExpiredCarryForwardCaseIds.Sort();
|
||||
Summary.RetainedCarryForwardCaseIds.Sort();
|
||||
Summary.EscalationRetainedCaseIds.Sort();
|
||||
Summary.SuppressedResolvedCaseIds.Sort();
|
||||
Summary.StableLongTermArchivedCaseIds.Sort();
|
||||
Summary.ProvisionalArchivedCaseIds.Sort();
|
||||
Summary.ReopenRiskArchivedCaseIds.Sort();
|
||||
|
||||
Summary.LongTermArchivedCaseCount = Summary.LongTermArchivedCaseIds.Num();
|
||||
Summary.ExpiredCarryForwardCaseCount = Summary.ExpiredCarryForwardCaseIds.Num();
|
||||
Summary.RetainedCarryForwardCaseCount = Summary.RetainedCarryForwardCaseIds.Num();
|
||||
Summary.EscalationRetainedCaseCount = Summary.EscalationRetainedCaseIds.Num();
|
||||
Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num();
|
||||
Summary.StableLongTermArchivedCaseCount = Summary.StableLongTermArchivedCaseIds.Num();
|
||||
Summary.ProvisionalArchivedCaseCount = Summary.ProvisionalArchivedCaseIds.Num();
|
||||
Summary.ReopenRiskArchivedCaseCount = Summary.ReopenRiskArchivedCaseIds.Num();
|
||||
|
||||
Summary.bShouldExpireResolvedCarryForward = Summary.ExpiredCarryForwardCaseCount > 0;
|
||||
Summary.bShouldRetainEscalationCarryForward = Summary.EscalationRetainedCaseCount > 0;
|
||||
Summary.ArchiveStabilityScore = FMath::Clamp(
|
||||
ClosureMemory.AverageClosureScore
|
||||
+ (static_cast<float>(Summary.LongTermArchivedCaseCount) * 5.0f)
|
||||
+ (static_cast<float>(Summary.StableLongTermArchivedCaseCount) * 4.0f)
|
||||
+ (bStableClosureMemory ? 10.0f : 0.0f)
|
||||
- (static_cast<float>(Summary.ProvisionalArchivedCaseCount) * 3.0f)
|
||||
- (static_cast<float>(Summary.ReopenRiskArchivedCaseCount) * 8.0f)
|
||||
- (static_cast<float>(Summary.RetainedCarryForwardCaseCount) * 4.0f)
|
||||
- (static_cast<float>(Summary.EscalationRetainedCaseCount) * 10.0f)
|
||||
- (RecommendationHistory.bHasRepeatRecommendationPressure ? 6.0f : 0.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
Summary.ArchiveRetentionTuningScore = FMath::Clamp(
|
||||
(static_cast<float>(Summary.ReopenRiskArchivedCaseCount) * 18.0f)
|
||||
+ (static_cast<float>(Summary.ProvisionalArchivedCaseCount) * 9.0f)
|
||||
+ (RecommendationHistory.bHasRepeatRecommendationPressure ? 12.0f : 0.0f)
|
||||
+ (Summary.bShouldRetainEscalationCarryForward ? 14.0f : 0.0f)
|
||||
- (static_cast<float>(Summary.StableLongTermArchivedCaseCount) * 6.0f),
|
||||
0.0f,
|
||||
100.0f
|
||||
);
|
||||
Summary.bHasStableArchiveMemory =
|
||||
Summary.LongTermArchivedCaseCount > 0
|
||||
&& Summary.ArchiveStabilityScore >= 55.0f
|
||||
&& Summary.EscalationRetainedCaseCount == 0;
|
||||
Summary.bNeedsArchiveRetentionTuning =
|
||||
Summary.ReopenRiskArchivedCaseCount > 0
|
||||
|| Summary.ArchiveRetentionTuningScore >= 25.0f
|
||||
|| (Summary.ProvisionalArchivedCaseCount > Summary.StableLongTermArchivedCaseCount
|
||||
&& Summary.LongTermArchivedCaseCount > 0);
|
||||
Summary.bHasActiveArchivePressure =
|
||||
Summary.RetainedCarryForwardCaseCount > 0
|
||||
|| Summary.EscalationRetainedCaseCount > 0
|
||||
|| Summary.bNeedsArchiveRetentionTuning;
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
|
@ -12,6 +12,12 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
|
||||
int32 ComputeElapsedMilliseconds(const FString& OlderUtc, const FString& NewerUtc);
|
||||
|
||||
FHyperTwistTrainingCoachMemorySnapshot DeriveQueueRecoveryBiasSnapshot(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
int32 ComputePlanDurationMs(
|
||||
const FHyperTwistTrainingReviewPlanState& ReviewPlan,
|
||||
const FString& ReferenceUtc
|
||||
|
|
@ -69,4 +75,11 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
const FHyperTwistTrainingCoachSessionQueueStateEntry& CurrentEntry,
|
||||
const FHyperTwistTrainingCoachSessionQueueStateEntry* PreviousEntry
|
||||
);
|
||||
|
||||
FHyperTwistTrainingCoachActionClosureSummary DeriveCoachActionClosureSummaryWithCoachMemory(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& AnchorPlan,
|
||||
const FString& ReferenceUtc,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1721,3 +1721,52 @@ Latest native repository coach-summary split follow-up later on `2026-06-29`:
|
|||
- the next worthwhile continuation remains another bounded
|
||||
training-repository or validation-library seam, not fresh product-family
|
||||
widening
|
||||
|
||||
Latest native repository coach-closure/policy split follow-up later on `2026-06-29`:
|
||||
|
||||
- the next same-family continuation then narrowed the remaining
|
||||
training-repository hotspot again without widening simulator or website
|
||||
scope:
|
||||
- moved the coach action-closure helper plus coach closure-memory,
|
||||
carry-forward policy, and archive policy family out of
|
||||
`HyperTwistTrainingRepositoryLibrary.cpp`
|
||||
- kept the much larger recommendation-history family in the main repository
|
||||
unit on purpose so this packet stayed bounded instead of mixing two hotspot
|
||||
seams at once
|
||||
- the extracted implementation family now lives at
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryCoachPolicies.cpp`
|
||||
- the existing private helper declaration seam in
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryInternal.h`
|
||||
was widened by two bounded declarations so the split policy family can
|
||||
reuse only the queue-recovery bias snapshot and the shared closure helper
|
||||
it actually needs
|
||||
- the main repository file now stands at `29,532` lines and the extracted
|
||||
coach-policy unit stands at `946` lines
|
||||
- the HyperTwist-owned structural loop stayed healthy on that exact native
|
||||
state:
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh`
|
||||
- `Quality: 6234`
|
||||
- all `7` rules pass
|
||||
- `scripts/run-hypertwist-sentrux-gate.sh`
|
||||
- comparison result:
|
||||
- `Quality: 6234 -> 6234`
|
||||
- `Coupling: 0.15 -> 0.15`
|
||||
- `Cycles: 0 -> 0`
|
||||
- `God files: 1 -> 1`
|
||||
- `No degradation detected`
|
||||
- the canonical Windows Unreal proof for that native split then passed through
|
||||
the maintained reverse-SSH lane:
|
||||
- `scripts/run-hypertwist-remote-unreal-build.sh --worktree-root 'C:\htpp'`
|
||||
- `Target is up to date`
|
||||
- `Result: Succeeded`
|
||||
- UnrealBuildTool `Total execution time: 5.96 seconds`
|
||||
- current truthful reading after this native follow-up:
|
||||
- the coach-closure and policy extraction is now build-proven on the
|
||||
canonical Windows lane
|
||||
- vanilla refactoring still is not fully complete because the training
|
||||
repository library still holds the oversized recommendation-history hotspot
|
||||
and the validation benchmark library remains another real native cleanup
|
||||
target
|
||||
- the next worthwhile continuation remains another bounded
|
||||
training-repository or validation-library seam rather than fresh
|
||||
product-topology or renderer widening
|
||||
|
|
|
|||
|
|
@ -2544,3 +2544,52 @@ Latest same-family responsive public-route expansion follow-up on `2026-06-29`:
|
|||
- the next worthwhile continuation remains another bounded
|
||||
training-repository or validation-library seam instead of fresh
|
||||
product-topology widening
|
||||
|
||||
## Latest native repository coach-closure/policy split follow-up later on `2026-06-29`
|
||||
|
||||
- the next same-family continuation then narrowed the remaining
|
||||
training-repository hotspot again without widening simulator or website
|
||||
scope:
|
||||
- the coach action-closure helper plus coach closure-memory,
|
||||
carry-forward policy, and archive policy family was moved out of
|
||||
`HyperTwistTrainingRepositoryLibrary.cpp`
|
||||
- the much larger recommendation-history family was intentionally kept in
|
||||
the main repository unit so this packet stayed bounded instead of mixing
|
||||
two hotspot seams at once
|
||||
- the extracted implementation family now lives at
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryCoachPolicies.cpp`
|
||||
- the existing private helper declaration seam in
|
||||
`UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibraryInternal.h`
|
||||
was widened by two bounded declarations so the split policy family can
|
||||
reuse only the queue-recovery bias snapshot and the shared closure helper
|
||||
it actually needs
|
||||
- the main repository file now stands at `29,532` lines and the extracted
|
||||
coach-policy unit stands at `946` lines
|
||||
- the HyperTwist-owned structural loop stayed healthy on that exact native
|
||||
state:
|
||||
- `scripts/run-hypertwist-sentrux-source-only.sh`
|
||||
- `Quality: 6234`
|
||||
- all `7` rules pass
|
||||
- `scripts/run-hypertwist-sentrux-gate.sh`
|
||||
- comparison result:
|
||||
- `Quality: 6234 -> 6234`
|
||||
- `Coupling: 0.15 -> 0.15`
|
||||
- `Cycles: 0 -> 0`
|
||||
- `God files: 1 -> 1`
|
||||
- `No degradation detected`
|
||||
- the canonical Windows Unreal proof for that native split then passed through
|
||||
the maintained reverse-SSH lane:
|
||||
- `scripts/run-hypertwist-remote-unreal-build.sh --worktree-root 'C:\htpp'`
|
||||
- `Target is up to date`
|
||||
- `Result: Succeeded`
|
||||
- UnrealBuildTool `Total execution time: 5.96 seconds`
|
||||
- current truthful reading after this native follow-up:
|
||||
- the coach-closure and policy extraction is now build-proven on the
|
||||
canonical Windows lane
|
||||
- vanilla refactoring still is not fully complete because the training
|
||||
repository library still holds the oversized recommendation-history hotspot
|
||||
and the validation benchmark library remains another real native cleanup
|
||||
target
|
||||
- the next worthwhile continuation remains another bounded
|
||||
training-repository or validation-library seam instead of fresh
|
||||
product-topology widening
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue