Add replay-driven coaching analytics and follow-ups
This commit is contained in:
parent
0bc79a8173
commit
840a8e5d0b
15 changed files with 3429 additions and 1 deletions
|
|
@ -1,6 +1,9 @@
|
|||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionReplayLibrary.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayReviewLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
|
@ -616,6 +619,15 @@ FHyperTwistTrainingRepositoryState UHyperTwistContractLibrary::MakeSampleTrainin
|
|||
);
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachActionPlan CoachActionPlan = MakeSampleTrainingCoachActionPlan();
|
||||
if (CoachActionPlan.IsStructurallyValid())
|
||||
{
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
|
||||
RepositoryState,
|
||||
CoachActionPlan
|
||||
);
|
||||
}
|
||||
|
||||
return RepositoryState;
|
||||
}
|
||||
|
||||
|
|
@ -666,6 +678,14 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistContractLibrary::MakeSam
|
|||
}
|
||||
RepositoryState.StoredMethodDrillBatches.Add(FHyperTwistTrainingMethodDrillBatch());
|
||||
|
||||
const FHyperTwistTrainingCoachActionPlan CoachActionPlan = MakeSampleTrainingCoachActionPlan();
|
||||
if (CoachActionPlan.IsStructurallyValid())
|
||||
{
|
||||
RepositoryState.StoredCoachActionPlans.Add(CoachActionPlan);
|
||||
RepositoryState.StoredCoachActionPlans.Add(CoachActionPlan);
|
||||
}
|
||||
RepositoryState.StoredCoachActionPlans.Add(FHyperTwistTrainingCoachActionPlan());
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(RepositoryState);
|
||||
}
|
||||
|
||||
|
|
@ -1018,6 +1038,90 @@ FHyperTwistTrainingReviewProgramSummary UHyperTwistContractLibrary::MakeSampleTr
|
|||
);
|
||||
}
|
||||
|
||||
FHyperTwistReplayReviewAnalytics UHyperTwistContractLibrary::MakeSampleReplayReviewAnalytics()
|
||||
{
|
||||
return UHyperTwistReplayReviewLibrary::DeriveReplayReviewAnalytics(
|
||||
MakeSampleRuntimeReplayPacket()
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistRecognitionReplaySummary UHyperTwistContractLibrary::MakeSampleRecognitionReplaySummary()
|
||||
{
|
||||
return UHyperTwistRecognitionReplayLibrary::DeriveRecognitionReplaySummary(
|
||||
MakeSampleRecognitionReplayPacket()
|
||||
);
|
||||
}
|
||||
|
||||
TArray<FHyperTwistCoachSignal> UHyperTwistContractLibrary::MakeSampleTrainingCoachSignals()
|
||||
{
|
||||
return UHyperTwistTrainingCoachLibrary::DeriveCoachSignals(
|
||||
MakeSampleTrainingRunSummary(),
|
||||
MakeSampleTrainingDeckScopedStats(),
|
||||
MakeSampleTrainingLearnerDeckStateSummary(),
|
||||
MakeSampleTrainingLearnerProfile(),
|
||||
MakeSampleTrainingCoachMemorySnapshot(),
|
||||
MakeSampleTrainingReviewProgramSummary(),
|
||||
MakeSampleReplayReviewAnalytics(),
|
||||
MakeSampleRecognitionReplaySummary()
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistContractLibrary::MakeSampleTrainingCoachBrief()
|
||||
{
|
||||
return UHyperTwistTrainingCoachLibrary::DeriveCoachBrief(
|
||||
MakeSampleTrainingRunSummary(),
|
||||
MakeSampleTrainingDeckScopedStats(),
|
||||
MakeSampleTrainingLearnerDeckStateSummary(),
|
||||
MakeSampleTrainingLearnerProfile(),
|
||||
MakeSampleTrainingCoachMemorySnapshot(),
|
||||
MakeSampleTrainingReviewProgramSummary(),
|
||||
MakeSampleReplayReviewAnalytics(),
|
||||
MakeSampleRecognitionReplaySummary()
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistContractLibrary::MakeSampleTrainingCoachActionPlan()
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
MakeSampleTrainingCoachBrief(),
|
||||
TEXT("coach_plan_training_session_01"),
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanSummary UHyperTwistContractLibrary::MakeSampleTrainingCoachActionPlanSummary()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
if (!StepResult.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingCoachActionPlanSummary();
|
||||
}
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanSummary(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
StepResult.UpdatedRunState.Session.UserId,
|
||||
TEXT("2026-04-28T12:05:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistContractLibrary::MakeSampleTrainingCoachActionPlanOutcomeSummary()
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary(
|
||||
MakeSampleTrainingRepositoryState(),
|
||||
MakeSampleTrainingCoachActionPlan(),
|
||||
TEXT("2026-04-28T12:12:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistContractLibrary::MakeSampleTrainingCoachFollowUpBrief()
|
||||
{
|
||||
return UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief(
|
||||
MakeSampleTrainingCoachBrief(),
|
||||
MakeSampleTrainingCoachActionPlan(),
|
||||
MakeSampleTrainingCoachActionPlanOutcomeSummary()
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistContractLibrary::IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State)
|
||||
{
|
||||
return State.IsStructurallyValid();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,235 @@
|
|||
#include "HyperTwistRecognition/HyperTwistRecognitionReplayLibrary.h"
|
||||
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
namespace HyperTwistRecognitionReplayLibraryInternal
|
||||
{
|
||||
template <typename TStruct>
|
||||
bool DeserializePayload(const FString& Json, TStruct& OutValue)
|
||||
{
|
||||
if (Json.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return FJsonObjectConverter::JsonObjectStringToUStruct(Json, &OutValue, 0, 0);
|
||||
}
|
||||
|
||||
FString ResolveRecognitionStage(const FHyperTwistReplayRecognitionPayload& Payload)
|
||||
{
|
||||
if (!Payload.ScanStage.IsEmpty())
|
||||
{
|
||||
return Payload.ScanStage;
|
||||
}
|
||||
|
||||
return Payload.CommittedFaceOrStage;
|
||||
}
|
||||
|
||||
void StartEpisodeFromEvent(
|
||||
FHyperTwistRecognitionReplayEpisode& Episode,
|
||||
const FHyperTwistReplayEvent& Event,
|
||||
const FHyperTwistReplayRecognitionPayload& Payload,
|
||||
int32 EpisodeOrdinal)
|
||||
{
|
||||
Episode = FHyperTwistRecognitionReplayEpisode();
|
||||
Episode.EpisodeId = FString::Printf(TEXT("rec_ep_%03d"), EpisodeOrdinal);
|
||||
Episode.Stage = ResolveRecognitionStage(Payload);
|
||||
Episode.StartedAtMs = Event.TimeMs;
|
||||
}
|
||||
|
||||
void FoldConfidence(FHyperTwistRecognitionReplayEpisode& Episode, const float Confidence, int32& InOutConfidenceCount, float& InOutConfidenceSum)
|
||||
{
|
||||
InOutConfidenceSum += Confidence;
|
||||
++InOutConfidenceCount;
|
||||
|
||||
if (Episode.PeakConfidence < 0.0f)
|
||||
{
|
||||
Episode.PeakConfidence = Confidence;
|
||||
}
|
||||
else
|
||||
{
|
||||
Episode.PeakConfidence = FMath::Max(Episode.PeakConfidence, Confidence);
|
||||
}
|
||||
}
|
||||
|
||||
void FinalizeEpisode(
|
||||
FHyperTwistRecognitionReplayEpisode& Episode,
|
||||
const int32 ClosedAtMs,
|
||||
const bool bCompleted,
|
||||
TArray<FHyperTwistRecognitionReplayEpisode>& OutEpisodes)
|
||||
{
|
||||
Episode.CompletedAtMs = ClosedAtMs;
|
||||
Episode.DurationMs = FMath::Max(0, Episode.CompletedAtMs - Episode.StartedAtMs);
|
||||
Episode.bCompleted = bCompleted;
|
||||
Episode.bAmbiguous = Episode.CorrectionCount > 0 || !Episode.bCompleted;
|
||||
OutEpisodes.Add(Episode);
|
||||
Episode = FHyperTwistRecognitionReplayEpisode();
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FHyperTwistRecognitionReplayEpisode> UHyperTwistRecognitionReplayLibrary::CollectRecognitionReplayEpisodes(const FHyperTwistReplayPacket& Packet)
|
||||
{
|
||||
TArray<FHyperTwistRecognitionReplayEpisode> Episodes;
|
||||
FHyperTwistRecognitionReplayEpisode OpenEpisode;
|
||||
bool bHasOpenEpisode = false;
|
||||
int32 EpisodeOrdinal = 1;
|
||||
int32 ConfidenceCount = 0;
|
||||
float ConfidenceSum = 0.0f;
|
||||
|
||||
for (const FHyperTwistReplayEvent& Event : Packet.Events)
|
||||
{
|
||||
switch (Event.EventType)
|
||||
{
|
||||
case EHyperTwistReplayEventType::RecognitionPreview:
|
||||
case EHyperTwistReplayEventType::RecognitionScan:
|
||||
case EHyperTwistReplayEventType::RecognitionCommit:
|
||||
case EHyperTwistReplayEventType::RecognitionCorrection:
|
||||
{
|
||||
FHyperTwistReplayRecognitionPayload Payload;
|
||||
if (!HyperTwistRecognitionReplayLibraryInternal::DeserializePayload(Event.PayloadJson, Payload))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const FString EventStage = HyperTwistRecognitionReplayLibraryInternal::ResolveRecognitionStage(Payload);
|
||||
if (!bHasOpenEpisode)
|
||||
{
|
||||
HyperTwistRecognitionReplayLibraryInternal::StartEpisodeFromEvent(OpenEpisode, Event, Payload, EpisodeOrdinal++);
|
||||
bHasOpenEpisode = true;
|
||||
ConfidenceCount = 0;
|
||||
ConfidenceSum = 0.0f;
|
||||
}
|
||||
else if (!EventStage.IsEmpty() && !OpenEpisode.Stage.IsEmpty() && OpenEpisode.Stage != EventStage)
|
||||
{
|
||||
OpenEpisode.AverageConfidence = ConfidenceCount > 0 ? ConfidenceSum / static_cast<float>(ConfidenceCount) : -1.0f;
|
||||
HyperTwistRecognitionReplayLibraryInternal::FinalizeEpisode(OpenEpisode, Event.TimeMs, false, Episodes);
|
||||
HyperTwistRecognitionReplayLibraryInternal::StartEpisodeFromEvent(OpenEpisode, Event, Payload, EpisodeOrdinal++);
|
||||
ConfidenceCount = 0;
|
||||
ConfidenceSum = 0.0f;
|
||||
}
|
||||
else if (OpenEpisode.Stage.IsEmpty())
|
||||
{
|
||||
OpenEpisode.Stage = EventStage;
|
||||
}
|
||||
|
||||
HyperTwistRecognitionReplayLibraryInternal::FoldConfidence(OpenEpisode, Payload.Confidence, ConfidenceCount, ConfidenceSum);
|
||||
|
||||
if (Event.EventType == EHyperTwistReplayEventType::RecognitionPreview)
|
||||
{
|
||||
++OpenEpisode.PreviewCount;
|
||||
}
|
||||
else if (Event.EventType == EHyperTwistReplayEventType::RecognitionScan)
|
||||
{
|
||||
++OpenEpisode.ScanCount;
|
||||
}
|
||||
else if (Event.EventType == EHyperTwistReplayEventType::RecognitionCorrection)
|
||||
{
|
||||
++OpenEpisode.CorrectionCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenEpisode.FinalCommitConfidence = Payload.Confidence;
|
||||
OpenEpisode.FinalCommittedUnit = Payload.CommittedFaceOrStage;
|
||||
OpenEpisode.AverageConfidence = ConfidenceCount > 0 ? ConfidenceSum / static_cast<float>(ConfidenceCount) : -1.0f;
|
||||
HyperTwistRecognitionReplayLibraryInternal::FinalizeEpisode(OpenEpisode, Event.TimeMs, true, Episodes);
|
||||
bHasOpenEpisode = false;
|
||||
ConfidenceCount = 0;
|
||||
ConfidenceSum = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasOpenEpisode)
|
||||
{
|
||||
const int32 ClosedAtMs = Packet.Events.Num() > 0 ? Packet.Events.Last().TimeMs : OpenEpisode.StartedAtMs;
|
||||
OpenEpisode.AverageConfidence = ConfidenceCount > 0 ? ConfidenceSum / static_cast<float>(ConfidenceCount) : -1.0f;
|
||||
HyperTwistRecognitionReplayLibraryInternal::FinalizeEpisode(OpenEpisode, ClosedAtMs, false, Episodes);
|
||||
}
|
||||
|
||||
return Episodes;
|
||||
}
|
||||
|
||||
FHyperTwistRecognitionReplaySummary UHyperTwistRecognitionReplayLibrary::DeriveRecognitionReplaySummary(const FHyperTwistReplayPacket& Packet)
|
||||
{
|
||||
FHyperTwistRecognitionReplaySummary Summary;
|
||||
Summary.Episodes = CollectRecognitionReplayEpisodes(Packet);
|
||||
Summary.EpisodeCount = Summary.Episodes.Num();
|
||||
|
||||
TSet<FString> CoveredStages;
|
||||
int32 DurationSum = 0;
|
||||
int32 CompletedDurationCount = 0;
|
||||
float CommitConfidenceSum = 0.0f;
|
||||
int32 CommitConfidenceCount = 0;
|
||||
|
||||
for (const FHyperTwistReplayEvent& Event : Packet.Events)
|
||||
{
|
||||
switch (Event.EventType)
|
||||
{
|
||||
case EHyperTwistReplayEventType::RecognitionPreview:
|
||||
++Summary.TotalRecognitionEventCount;
|
||||
++Summary.TotalPreviewCount;
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::RecognitionScan:
|
||||
++Summary.TotalRecognitionEventCount;
|
||||
++Summary.TotalScanCount;
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::RecognitionCommit:
|
||||
++Summary.TotalRecognitionEventCount;
|
||||
++Summary.TotalCommitCount;
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::RecognitionCorrection:
|
||||
++Summary.TotalRecognitionEventCount;
|
||||
++Summary.TotalCorrectionCount;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const FHyperTwistRecognitionReplayEpisode& Episode : Summary.Episodes)
|
||||
{
|
||||
if (!Episode.Stage.IsEmpty())
|
||||
{
|
||||
CoveredStages.Add(Episode.Stage);
|
||||
}
|
||||
|
||||
if (Episode.bCompleted)
|
||||
{
|
||||
++Summary.CompletedEpisodeCount;
|
||||
DurationSum += Episode.DurationMs;
|
||||
++CompletedDurationCount;
|
||||
|
||||
if (Episode.FinalCommitConfidence >= 0.0f)
|
||||
{
|
||||
CommitConfidenceSum += Episode.FinalCommitConfidence;
|
||||
++CommitConfidenceCount;
|
||||
}
|
||||
|
||||
Summary.LastCommittedUnit = Episode.FinalCommittedUnit;
|
||||
}
|
||||
|
||||
if (Episode.bAmbiguous)
|
||||
{
|
||||
++Summary.AmbiguousEpisodeCount;
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& CoveredStage : CoveredStages)
|
||||
{
|
||||
Summary.CoveredStages.Add(CoveredStage);
|
||||
}
|
||||
Summary.CoveredStages.Sort();
|
||||
Summary.AverageEpisodeDurationMs = CompletedDurationCount > 0 ? DurationSum / CompletedDurationCount : 0;
|
||||
Summary.AverageCommitConfidence = CommitConfidenceCount > 0 ? CommitConfidenceSum / static_cast<float>(CommitConfidenceCount) : -1.0f;
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
|
@ -0,0 +1,344 @@
|
|||
#include "HyperTwistReplay/HyperTwistReplayReviewLibrary.h"
|
||||
|
||||
#include "HyperTwistReplay/HyperTwistReplayLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
namespace HyperTwistReplayReviewLibraryInternal
|
||||
{
|
||||
template <typename TStruct>
|
||||
bool DeserializePayload(const FString& Json, TStruct& OutValue)
|
||||
{
|
||||
if (Json.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return FJsonObjectConverter::JsonObjectStringToUStruct(Json, &OutValue, 0, 0);
|
||||
}
|
||||
|
||||
FHyperTwistReplayReviewAnchor MakeAnchor(
|
||||
const FHyperTwistReplayEvent& Event,
|
||||
const EHyperTwistReplayReviewAnchorKind AnchorKind,
|
||||
const FString& Label)
|
||||
{
|
||||
FHyperTwistReplayReviewAnchor Anchor;
|
||||
Anchor.AnchorKind = AnchorKind;
|
||||
Anchor.EventId = Event.EventId;
|
||||
Anchor.Sequence = Event.Sequence;
|
||||
Anchor.TimeMs = Event.TimeMs;
|
||||
Anchor.Label = Label;
|
||||
return Anchor;
|
||||
}
|
||||
|
||||
void AddPhaseAnchor(
|
||||
TArray<FHyperTwistReplayReviewAnchor>& Anchors,
|
||||
const FHyperTwistReplayEvent& Event,
|
||||
const FHyperTwistReplayPhaseMarkPayload& PhasePayload)
|
||||
{
|
||||
const FString Action = PhasePayload.PhaseAction.ToLower();
|
||||
const bool bMarksCompletion = (Action == TEXT("end") || Action == TEXT("complete"));
|
||||
const FString EffectiveAction = bMarksCompletion ? TEXT("complete") : TEXT("start");
|
||||
const FString EffectiveLabel = PhasePayload.PhaseKind.IsEmpty()
|
||||
? FString::Printf(TEXT("phase %s"), *EffectiveAction)
|
||||
: FString::Printf(TEXT("%s %s"), *PhasePayload.PhaseKind, *EffectiveAction);
|
||||
|
||||
FHyperTwistReplayReviewAnchor Anchor = MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::PhaseBoundary,
|
||||
EffectiveLabel);
|
||||
Anchor.PhaseId = PhasePayload.PhaseId;
|
||||
Anchor.PhaseKind = PhasePayload.PhaseKind;
|
||||
Anchor.bMarksCompletion = bMarksCompletion;
|
||||
Anchors.Add(Anchor);
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FHyperTwistReplayReviewAnchor> UHyperTwistReplayReviewLibrary::CollectReplayReviewAnchors(const FHyperTwistReplayPacket& Packet)
|
||||
{
|
||||
TArray<FHyperTwistReplayReviewAnchor> Anchors;
|
||||
bool bFirstMoveAnchorAdded = false;
|
||||
|
||||
for (const FHyperTwistReplayEvent& Event : Packet.Events)
|
||||
{
|
||||
switch (Event.EventType)
|
||||
{
|
||||
case EHyperTwistReplayEventType::InspectionStart:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::InspectionStart,
|
||||
TEXT("inspection start")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::InspectionEnd:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::InspectionEnd,
|
||||
TEXT("inspection end")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::TimerStart:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::TimerStart,
|
||||
TEXT("timer start")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::Move:
|
||||
if (!bFirstMoveAnchorAdded)
|
||||
{
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::FirstMove,
|
||||
TEXT("first move")));
|
||||
bFirstMoveAnchorAdded = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::PhaseMark:
|
||||
{
|
||||
FHyperTwistReplayPhaseMarkPayload PhasePayload;
|
||||
if (HyperTwistReplayReviewLibraryInternal::DeserializePayload(Event.PayloadJson, PhasePayload)
|
||||
&& !PhasePayload.PhaseId.IsEmpty())
|
||||
{
|
||||
HyperTwistReplayReviewLibraryInternal::AddPhaseAnchor(Anchors, Event, PhasePayload);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EHyperTwistReplayEventType::RecognitionCommit:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::RecognitionCommit,
|
||||
TEXT("recognition commit")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::RecognitionCorrection:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::RecognitionCorrection,
|
||||
TEXT("recognition correction")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::Pause:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::Pause,
|
||||
TEXT("pause")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::Resume:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::Resume,
|
||||
TEXT("resume")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::StateSnapshot:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::StateSnapshot,
|
||||
TEXT("state snapshot")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::DeviceSync:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::DeviceSync,
|
||||
TEXT("device sync")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::Annotation:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::Annotation,
|
||||
TEXT("annotation")));
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::SolveEnd:
|
||||
Anchors.Add(HyperTwistReplayReviewLibraryInternal::MakeAnchor(
|
||||
Event,
|
||||
EHyperTwistReplayReviewAnchorKind::SolveEnd,
|
||||
TEXT("solve end")));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Anchors;
|
||||
}
|
||||
|
||||
FHyperTwistReplayReviewAnalytics UHyperTwistReplayReviewLibrary::DeriveReplayReviewAnalytics(const FHyperTwistReplayPacket& Packet)
|
||||
{
|
||||
FHyperTwistReplayReviewAnalytics Analytics;
|
||||
Analytics.EventCount = Packet.Events.Num();
|
||||
Analytics.bHasImportedSource = !Packet.SourceImportFormat.IsEmpty();
|
||||
Analytics.ReviewAnchors = CollectReplayReviewAnchors(Packet);
|
||||
|
||||
if (Packet.Events.Num() == 0)
|
||||
{
|
||||
return Analytics;
|
||||
}
|
||||
|
||||
const FHyperTwistDerivedReplaySummary Summary = Packet.DerivedSummary.TotalTimeMs > 0 || Packet.DerivedSummary.MoveCount > 0
|
||||
? Packet.DerivedSummary
|
||||
: UHyperTwistReplayLibrary::DeriveReplaySummary(Packet);
|
||||
|
||||
int32 LastEventTimeMs = Packet.Events[0].TimeMs;
|
||||
int32 PreviousMoveAtMs = INDEX_NONE;
|
||||
int32 MoveGapCount = 0;
|
||||
int32 MoveGapSumMs = 0;
|
||||
int32 PauseStartedAtMs = INDEX_NONE;
|
||||
float ConfidenceSum = 0.0f;
|
||||
int32 ConfidenceCount = 0;
|
||||
TSet<FString> UniquePhaseIds;
|
||||
|
||||
Analytics.TimerStartAtMs = INDEX_NONE;
|
||||
Analytics.SolveEndAtMs = INDEX_NONE;
|
||||
Analytics.FirstMoveAtMs = INDEX_NONE;
|
||||
Analytics.LastMoveAtMs = INDEX_NONE;
|
||||
|
||||
for (const FHyperTwistReplayEvent& Event : Packet.Events)
|
||||
{
|
||||
Analytics.LongestIdleGapMs = FMath::Max(Analytics.LongestIdleGapMs, Event.TimeMs - LastEventTimeMs);
|
||||
LastEventTimeMs = Event.TimeMs;
|
||||
|
||||
switch (Event.EventType)
|
||||
{
|
||||
case EHyperTwistReplayEventType::TimerStart:
|
||||
if (Analytics.TimerStartAtMs == INDEX_NONE)
|
||||
{
|
||||
Analytics.TimerStartAtMs = Event.TimeMs;
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::Move:
|
||||
if (Analytics.FirstMoveAtMs == INDEX_NONE)
|
||||
{
|
||||
Analytics.FirstMoveAtMs = Event.TimeMs;
|
||||
}
|
||||
|
||||
if (PreviousMoveAtMs != INDEX_NONE)
|
||||
{
|
||||
MoveGapSumMs += Event.TimeMs - PreviousMoveAtMs;
|
||||
++MoveGapCount;
|
||||
}
|
||||
|
||||
PreviousMoveAtMs = Event.TimeMs;
|
||||
Analytics.LastMoveAtMs = Event.TimeMs;
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::RecognitionPreview:
|
||||
case EHyperTwistReplayEventType::RecognitionScan:
|
||||
case EHyperTwistReplayEventType::RecognitionCommit:
|
||||
case EHyperTwistReplayEventType::RecognitionCorrection:
|
||||
{
|
||||
FHyperTwistReplayRecognitionPayload RecognitionPayload;
|
||||
if (HyperTwistReplayReviewLibraryInternal::DeserializePayload(Event.PayloadJson, RecognitionPayload))
|
||||
{
|
||||
Analytics.bHasRecognitionData = true;
|
||||
ConfidenceSum += RecognitionPayload.Confidence;
|
||||
++ConfidenceCount;
|
||||
|
||||
if (Analytics.LowestRecognitionConfidence < 0.0f)
|
||||
{
|
||||
Analytics.LowestRecognitionConfidence = RecognitionPayload.Confidence;
|
||||
Analytics.HighestRecognitionConfidence = RecognitionPayload.Confidence;
|
||||
}
|
||||
else
|
||||
{
|
||||
Analytics.LowestRecognitionConfidence = FMath::Min(Analytics.LowestRecognitionConfidence, RecognitionPayload.Confidence);
|
||||
Analytics.HighestRecognitionConfidence = FMath::Max(Analytics.HighestRecognitionConfidence, RecognitionPayload.Confidence);
|
||||
}
|
||||
}
|
||||
|
||||
if (Event.EventType == EHyperTwistReplayEventType::RecognitionPreview
|
||||
|| Event.EventType == EHyperTwistReplayEventType::RecognitionScan)
|
||||
{
|
||||
++Analytics.RecognitionPreviewCount;
|
||||
}
|
||||
else if (Event.EventType == EHyperTwistReplayEventType::RecognitionCommit)
|
||||
{
|
||||
++Analytics.RecognitionCommitCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
++Analytics.RecognitionCorrectionCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EHyperTwistReplayEventType::PhaseMark:
|
||||
{
|
||||
FHyperTwistReplayPhaseMarkPayload PhasePayload;
|
||||
if (HyperTwistReplayReviewLibraryInternal::DeserializePayload(Event.PayloadJson, PhasePayload)
|
||||
&& !PhasePayload.PhaseId.IsEmpty())
|
||||
{
|
||||
UniquePhaseIds.Add(PhasePayload.PhaseId);
|
||||
++Analytics.PhaseBoundaryCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EHyperTwistReplayEventType::Pause:
|
||||
++Analytics.PauseCount;
|
||||
if (PauseStartedAtMs == INDEX_NONE)
|
||||
{
|
||||
PauseStartedAtMs = Event.TimeMs;
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::Resume:
|
||||
if (PauseStartedAtMs != INDEX_NONE)
|
||||
{
|
||||
Analytics.TotalPausedMs += FMath::Max(0, Event.TimeMs - PauseStartedAtMs);
|
||||
PauseStartedAtMs = INDEX_NONE;
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::DeviceSync:
|
||||
++Analytics.DeviceSyncCount;
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::StateSnapshot:
|
||||
++Analytics.StateSnapshotCount;
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::Annotation:
|
||||
++Analytics.AnnotationCount;
|
||||
break;
|
||||
|
||||
case EHyperTwistReplayEventType::SolveEnd:
|
||||
Analytics.SolveEndAtMs = Event.TimeMs;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (PauseStartedAtMs != INDEX_NONE)
|
||||
{
|
||||
const int32 PauseClosedAtMs = Analytics.SolveEndAtMs != INDEX_NONE ? Analytics.SolveEndAtMs : LastEventTimeMs;
|
||||
Analytics.TotalPausedMs += FMath::Max(0, PauseClosedAtMs - PauseStartedAtMs);
|
||||
}
|
||||
|
||||
Analytics.UniquePhaseCount = UniquePhaseIds.Num();
|
||||
Analytics.DurationAfterTimerStartMs = Analytics.TimerStartAtMs != INDEX_NONE
|
||||
? FMath::Max(0, (Analytics.SolveEndAtMs != INDEX_NONE ? Analytics.SolveEndAtMs : LastEventTimeMs) - Analytics.TimerStartAtMs)
|
||||
: 0;
|
||||
Analytics.AverageMoveGapMs = MoveGapCount > 0 ? MoveGapSumMs / MoveGapCount : 0;
|
||||
Analytics.AverageRecognitionConfidence = ConfidenceCount > 0 ? ConfidenceSum / static_cast<float>(ConfidenceCount) : -1.0f;
|
||||
|
||||
for (const FHyperTwistPhaseTiming& PhaseTiming : Summary.PhaseTimings)
|
||||
{
|
||||
if (PhaseTiming.DurationMs > Analytics.LongestPhaseDurationMs)
|
||||
{
|
||||
Analytics.LongestPhaseDurationMs = PhaseTiming.DurationMs;
|
||||
Analytics.LongestPhaseKind = PhaseTiming.PhaseKind;
|
||||
}
|
||||
}
|
||||
|
||||
return Analytics;
|
||||
}
|
||||
|
|
@ -0,0 +1,471 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
|
||||
namespace HyperTwistTrainingCoachLibraryInternal
|
||||
{
|
||||
EHyperTwistCoachPriority PriorityFromScore(const float Score)
|
||||
{
|
||||
if (Score >= 85.0f)
|
||||
{
|
||||
return EHyperTwistCoachPriority::Critical;
|
||||
}
|
||||
if (Score >= 65.0f)
|
||||
{
|
||||
return EHyperTwistCoachPriority::Elevated;
|
||||
}
|
||||
if (Score >= 40.0f)
|
||||
{
|
||||
return EHyperTwistCoachPriority::Advisory;
|
||||
}
|
||||
|
||||
return EHyperTwistCoachPriority::Informational;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSignal MakeSignal(
|
||||
const FString& SignalId,
|
||||
const EHyperTwistCoachFocusLane FocusLane,
|
||||
const float Score,
|
||||
const FString& Headline,
|
||||
const FString& Detail,
|
||||
const EHyperTwistTrainingDeliveryMode SuggestedMode,
|
||||
const EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy)
|
||||
{
|
||||
FHyperTwistCoachSignal Signal;
|
||||
Signal.SignalId = SignalId;
|
||||
Signal.FocusLane = FocusLane;
|
||||
Signal.Score = Score;
|
||||
Signal.Priority = PriorityFromScore(Score);
|
||||
Signal.Headline = Headline;
|
||||
Signal.Detail = Detail;
|
||||
Signal.SuggestedMode = SuggestedMode;
|
||||
Signal.SuggestedSelectionPolicy = SuggestedSelectionPolicy;
|
||||
Signal.bNeedsImmediateAttention = Signal.Priority == EHyperTwistCoachPriority::Critical;
|
||||
return Signal;
|
||||
}
|
||||
|
||||
FString BuildCountDetail(const FString& Prefix, const int32 PrimaryCount, const int32 SecondaryCount)
|
||||
{
|
||||
return FString::Printf(TEXT("%s: %d / %d"), *Prefix, PrimaryCount, SecondaryCount);
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FHyperTwistCoachSignal> UHyperTwistTrainingCoachLibrary::DeriveCoachSignals(
|
||||
const FHyperTwistTrainingSessionSummary& SessionSummary,
|
||||
const FHyperTwistTrainingDeckScopedStats& DeckScopedStats,
|
||||
const FHyperTwistTrainingLearnerDeckStateSummary& LearnerDeckStateSummary,
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FHyperTwistTrainingReviewProgramSummary& ReviewProgramSummary,
|
||||
const FHyperTwistReplayReviewAnalytics& ReplayReviewAnalytics,
|
||||
const FHyperTwistRecognitionReplaySummary& RecognitionReplaySummary)
|
||||
{
|
||||
TArray<FHyperTwistCoachSignal> Signals;
|
||||
|
||||
if (CoachMemorySnapshot.bNeedsCoachIntervention
|
||||
|| ReviewProgramSummary.bNeedsCoachIntervention
|
||||
|| LearnerDeckStateSummary.OverdueCaseCount >= 3)
|
||||
{
|
||||
const float Score = 90.0f
|
||||
+ FMath::Min(static_cast<float>(LearnerDeckStateSummary.OverdueCaseCount) * 2.0f, 8.0f)
|
||||
+ FMath::Min(static_cast<float>(ReviewProgramSummary.CarryoverCaseCount), 6.0f);
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
TEXT("review_backlog"),
|
||||
EHyperTwistCoachFocusLane::ReviewBacklog,
|
||||
Score,
|
||||
TEXT("Review backlog needs intervention"),
|
||||
FString::Printf(
|
||||
TEXT("overdue=%d, carryover=%d, pendingPlans=%d"),
|
||||
LearnerDeckStateSummary.OverdueCaseCount,
|
||||
ReviewProgramSummary.CarryoverCaseCount,
|
||||
ReviewProgramSummary.PendingPlanCount),
|
||||
EHyperTwistTrainingDeliveryMode::CoachReviewed,
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted);
|
||||
Signal.SuggestedDeckId = !CoachMemorySnapshot.FocusDeckId.IsEmpty()
|
||||
? CoachMemorySnapshot.FocusDeckId
|
||||
: ReviewProgramSummary.FocusDeckId;
|
||||
Signal.SuggestedMethodSegmentId = !CoachMemorySnapshot.FocusMethodSegmentId.IsEmpty()
|
||||
? CoachMemorySnapshot.FocusMethodSegmentId
|
||||
: ReviewProgramSummary.FocusMethodSegmentId;
|
||||
Signal.SuggestedCaseIds = CoachMemorySnapshot.OverdueCaseIds.Num() > 0
|
||||
? CoachMemorySnapshot.OverdueCaseIds
|
||||
: CoachMemorySnapshot.DueCaseIds;
|
||||
Signals.Add(Signal);
|
||||
}
|
||||
|
||||
if (CoachMemorySnapshot.bNeedsCadenceRecovery || LearnerProfile.RecentSevenDaySessionCount == 0)
|
||||
{
|
||||
const float Score = CoachMemorySnapshot.bNeedsCadenceRecovery ? 72.0f : 48.0f;
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
TEXT("cadence_recovery"),
|
||||
EHyperTwistCoachFocusLane::Cadence,
|
||||
Score,
|
||||
TEXT("Cadence recovery recommended"),
|
||||
FString::Printf(
|
||||
TEXT("streak=%d, recent7d=%d"),
|
||||
LearnerProfile.ActiveTrainingDayStreak,
|
||||
LearnerProfile.RecentSevenDaySessionCount),
|
||||
EHyperTwistTrainingDeliveryMode::Timer,
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted);
|
||||
Signal.SuggestedDeckId = CoachMemorySnapshot.FocusDeckId;
|
||||
Signal.SuggestedMethodSegmentId = CoachMemorySnapshot.FocusMethodSegmentId;
|
||||
Signal.SuggestedCaseIds = CoachMemorySnapshot.WeakCaseIds;
|
||||
Signals.Add(Signal);
|
||||
}
|
||||
|
||||
if (CoachMemorySnapshot.bNeedsVarietyRotation)
|
||||
{
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
TEXT("variety_rotation"),
|
||||
EHyperTwistCoachFocusLane::Variety,
|
||||
46.0f,
|
||||
TEXT("Variety rotation advised"),
|
||||
FString::Printf(
|
||||
TEXT("favoriteDeckShare=%.2f, recoveryDecks=%d"),
|
||||
LearnerProfile.FavoriteDeckSessionShare,
|
||||
CoachMemorySnapshot.RecoveryDeckIds.Num()),
|
||||
EHyperTwistTrainingDeliveryMode::VirtualCube,
|
||||
EHyperTwistTrainingSelectionPolicy::Uniform);
|
||||
if (CoachMemorySnapshot.RecoveryDeckIds.Num() > 0)
|
||||
{
|
||||
Signal.SuggestedDeckId = CoachMemorySnapshot.RecoveryDeckIds[0];
|
||||
}
|
||||
if (CoachMemorySnapshot.RecoveryMethodSegmentIds.Num() > 0)
|
||||
{
|
||||
Signal.SuggestedMethodSegmentId = CoachMemorySnapshot.RecoveryMethodSegmentIds[0];
|
||||
}
|
||||
Signals.Add(Signal);
|
||||
}
|
||||
|
||||
if (RecognitionReplaySummary.EpisodeCount > 0
|
||||
&& (RecognitionReplaySummary.AmbiguousEpisodeCount > 0
|
||||
|| ReplayReviewAnalytics.RecognitionCorrectionCount > 0
|
||||
|| (RecognitionReplaySummary.AverageCommitConfidence >= 0.0f
|
||||
&& RecognitionReplaySummary.AverageCommitConfidence < 0.7f)))
|
||||
{
|
||||
const float ConfidencePenalty = RecognitionReplaySummary.AverageCommitConfidence >= 0.0f
|
||||
? (1.0f - RecognitionReplaySummary.AverageCommitConfidence) * 25.0f
|
||||
: 0.0f;
|
||||
const float Score = 68.0f
|
||||
+ static_cast<float>(RecognitionReplaySummary.AmbiguousEpisodeCount * 5)
|
||||
+ static_cast<float>(ReplayReviewAnalytics.RecognitionCorrectionCount * 4)
|
||||
+ ConfidencePenalty;
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
TEXT("recognition_clarity"),
|
||||
EHyperTwistCoachFocusLane::Recognition,
|
||||
Score,
|
||||
TEXT("Recognition clarity needs work"),
|
||||
FString::Printf(
|
||||
TEXT("episodes=%d, ambiguous=%d, avgCommit=%.2f"),
|
||||
RecognitionReplaySummary.EpisodeCount,
|
||||
RecognitionReplaySummary.AmbiguousEpisodeCount,
|
||||
RecognitionReplaySummary.AverageCommitConfidence),
|
||||
EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted);
|
||||
Signal.SuggestedDeckId = CoachMemorySnapshot.FocusDeckId;
|
||||
Signal.SuggestedMethodSegmentId = CoachMemorySnapshot.FocusMethodSegmentId;
|
||||
Signal.SuggestedCaseIds = CoachMemorySnapshot.WeakCaseIds;
|
||||
Signals.Add(Signal);
|
||||
}
|
||||
|
||||
if ((SessionSummary.TotalMistakes > 0 && SessionSummary.CompletedAttemptCount > 0)
|
||||
|| DeckScopedStats.MistakeAttemptCount > 0
|
||||
|| ReplayReviewAnalytics.LongestIdleGapMs >= 2500)
|
||||
{
|
||||
const float MistakePressure = SessionSummary.CompletedAttemptCount > 0
|
||||
? (static_cast<float>(SessionSummary.TotalMistakes) / SessionSummary.CompletedAttemptCount) * 18.0f
|
||||
: 0.0f;
|
||||
const float Score = 52.0f
|
||||
+ MistakePressure
|
||||
+ FMath::Min(static_cast<float>(ReplayReviewAnalytics.LongestIdleGapMs) / 150.0f, 18.0f);
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
TEXT("execution_flow"),
|
||||
EHyperTwistCoachFocusLane::Execution,
|
||||
Score,
|
||||
TEXT("Execution flow is uneven"),
|
||||
FString::Printf(
|
||||
TEXT("mistakes=%d, idleGapMs=%d, longestPhase=%s"),
|
||||
SessionSummary.TotalMistakes,
|
||||
ReplayReviewAnalytics.LongestIdleGapMs,
|
||||
*ReplayReviewAnalytics.LongestPhaseKind),
|
||||
EHyperTwistTrainingDeliveryMode::VirtualCube,
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted);
|
||||
Signal.SuggestedDeckId = SessionSummary.DeckId;
|
||||
Signal.SuggestedCaseIds = CoachMemorySnapshot.WeakCaseIds;
|
||||
Signals.Add(Signal);
|
||||
}
|
||||
|
||||
if ((DeckScopedStats.AttemptCount > 0 && DeckScopedStats.SuccessCount < DeckScopedStats.FailureCount)
|
||||
|| LearnerDeckStateSummary.AverageDifficultyScore >= 0.7f)
|
||||
{
|
||||
const float FailureBias = DeckScopedStats.AttemptCount > 0
|
||||
? (static_cast<float>(DeckScopedStats.FailureCount) / DeckScopedStats.AttemptCount) * 25.0f
|
||||
: 0.0f;
|
||||
const float Score = 44.0f
|
||||
+ FailureBias
|
||||
+ (LearnerDeckStateSummary.AverageDifficultyScore * 15.0f);
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
TEXT("accuracy_rebuild"),
|
||||
EHyperTwistCoachFocusLane::Accuracy,
|
||||
Score,
|
||||
TEXT("Accuracy rebuild recommended"),
|
||||
FString::Printf(
|
||||
TEXT("failures=%d, success=%d, avgDifficulty=%.2f"),
|
||||
DeckScopedStats.FailureCount,
|
||||
DeckScopedStats.SuccessCount,
|
||||
LearnerDeckStateSummary.AverageDifficultyScore),
|
||||
EHyperTwistTrainingDeliveryMode::Timer,
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted);
|
||||
Signal.SuggestedDeckId = SessionSummary.DeckId;
|
||||
Signal.SuggestedCaseIds = CoachMemorySnapshot.WeakCaseIds;
|
||||
Signals.Add(Signal);
|
||||
}
|
||||
|
||||
if (LearnerDeckStateSummary.DueCaseCount > 0
|
||||
|| LearnerDeckStateSummary.OverdueCaseCount > 0
|
||||
|| ReviewProgramSummary.PendingPlanCount > 0)
|
||||
{
|
||||
const float Score = 38.0f
|
||||
+ FMath::Min(static_cast<float>(LearnerDeckStateSummary.DueCaseCount), 8.0f)
|
||||
+ FMath::Min(static_cast<float>(LearnerDeckStateSummary.OverdueCaseCount) * 2.0f, 12.0f);
|
||||
FHyperTwistCoachSignal Signal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
TEXT("stability_maintenance"),
|
||||
EHyperTwistCoachFocusLane::Stability,
|
||||
Score,
|
||||
TEXT("Stability maintenance is due"),
|
||||
FString::Printf(
|
||||
TEXT("due=%d, overdue=%d, pendingPlans=%d"),
|
||||
LearnerDeckStateSummary.DueCaseCount,
|
||||
LearnerDeckStateSummary.OverdueCaseCount,
|
||||
ReviewProgramSummary.PendingPlanCount),
|
||||
EHyperTwistTrainingDeliveryMode::Timer,
|
||||
EHyperTwistTrainingSelectionPolicy::Spaced);
|
||||
Signal.SuggestedDeckId = SessionSummary.DeckId;
|
||||
Signal.SuggestedCaseIds = CoachMemorySnapshot.DueCaseIds;
|
||||
Signals.Add(Signal);
|
||||
}
|
||||
|
||||
Signals.Sort([](const FHyperTwistCoachSignal& Left, const FHyperTwistCoachSignal& Right)
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(Left.Score, Right.Score))
|
||||
{
|
||||
return Left.Score > Right.Score;
|
||||
}
|
||||
|
||||
return Left.SignalId < Right.SignalId;
|
||||
});
|
||||
|
||||
return Signals;
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachBrief(
|
||||
const FHyperTwistTrainingSessionSummary& SessionSummary,
|
||||
const FHyperTwistTrainingDeckScopedStats& DeckScopedStats,
|
||||
const FHyperTwistTrainingLearnerDeckStateSummary& LearnerDeckStateSummary,
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FHyperTwistTrainingReviewProgramSummary& ReviewProgramSummary,
|
||||
const FHyperTwistReplayReviewAnalytics& ReplayReviewAnalytics,
|
||||
const FHyperTwistRecognitionReplaySummary& RecognitionReplaySummary)
|
||||
{
|
||||
FHyperTwistCoachBrief Brief;
|
||||
Brief.UserId = !SessionSummary.UserId.IsEmpty() ? SessionSummary.UserId : LearnerProfile.UserId;
|
||||
Brief.ReferenceUtc = !CoachMemorySnapshot.ReferenceUtc.IsEmpty()
|
||||
? CoachMemorySnapshot.ReferenceUtc
|
||||
: LearnerProfile.ReferenceUtc;
|
||||
Brief.TrainingSessionId = SessionSummary.TrainingSessionId;
|
||||
Brief.SourceReviewPlanId = !ReviewProgramSummary.CurrentPlanId.IsEmpty()
|
||||
? ReviewProgramSummary.CurrentPlanId
|
||||
: ReviewProgramSummary.NextPlanId;
|
||||
Brief.FocusDeckId = !CoachMemorySnapshot.FocusDeckId.IsEmpty()
|
||||
? CoachMemorySnapshot.FocusDeckId
|
||||
: SessionSummary.DeckId;
|
||||
Brief.FocusMethodSegmentId = !CoachMemorySnapshot.FocusMethodSegmentId.IsEmpty()
|
||||
? CoachMemorySnapshot.FocusMethodSegmentId
|
||||
: LearnerProfile.FavoriteMethodSegmentId;
|
||||
Brief.bNeedsCoachReview = CoachMemorySnapshot.bNeedsCoachIntervention || ReviewProgramSummary.bNeedsCoachIntervention;
|
||||
Brief.bNeedsCadenceRecovery = CoachMemorySnapshot.bNeedsCadenceRecovery;
|
||||
Brief.bNeedsVarietyRotation = CoachMemorySnapshot.bNeedsVarietyRotation;
|
||||
Brief.bPreferRecognitionReplayReview = RecognitionReplaySummary.EpisodeCount > 0
|
||||
&& (RecognitionReplaySummary.AmbiguousEpisodeCount > 0
|
||||
|| ReplayReviewAnalytics.RecognitionCorrectionCount > 0);
|
||||
|
||||
Brief.Signals = DeriveCoachSignals(
|
||||
SessionSummary,
|
||||
DeckScopedStats,
|
||||
LearnerDeckStateSummary,
|
||||
LearnerProfile,
|
||||
CoachMemorySnapshot,
|
||||
ReviewProgramSummary,
|
||||
ReplayReviewAnalytics,
|
||||
RecognitionReplaySummary);
|
||||
|
||||
if (Brief.Signals.Num() == 0)
|
||||
{
|
||||
Brief.Priority = EHyperTwistCoachPriority::Informational;
|
||||
Brief.FocusLane = EHyperTwistCoachFocusLane::Stability;
|
||||
Brief.PriorityScore = 0.0f;
|
||||
Brief.Headline = TEXT("Training state is stable");
|
||||
Brief.PrimaryActionLabel = TEXT("maintain-rhythm");
|
||||
Brief.SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
return Brief;
|
||||
}
|
||||
|
||||
const FHyperTwistCoachSignal& PrimarySignal = Brief.Signals[0];
|
||||
Brief.Priority = PrimarySignal.Priority;
|
||||
Brief.FocusLane = PrimarySignal.FocusLane;
|
||||
Brief.PriorityScore = PrimarySignal.Score;
|
||||
Brief.Headline = PrimarySignal.Headline;
|
||||
Brief.PrimaryActionLabel = PrimarySignal.SignalId;
|
||||
Brief.SuggestedMode = PrimarySignal.SuggestedMode;
|
||||
Brief.SuggestedSelectionPolicy = PrimarySignal.SuggestedSelectionPolicy;
|
||||
if (!PrimarySignal.SuggestedDeckId.IsEmpty())
|
||||
{
|
||||
Brief.FocusDeckId = PrimarySignal.SuggestedDeckId;
|
||||
}
|
||||
if (!PrimarySignal.SuggestedMethodSegmentId.IsEmpty())
|
||||
{
|
||||
Brief.FocusMethodSegmentId = PrimarySignal.SuggestedMethodSegmentId;
|
||||
}
|
||||
Brief.FocusCaseIds = PrimarySignal.SuggestedCaseIds;
|
||||
|
||||
if (Brief.FocusCaseIds.Num() == 0)
|
||||
{
|
||||
if (Brief.bNeedsCoachReview && CoachMemorySnapshot.OverdueCaseIds.Num() > 0)
|
||||
{
|
||||
Brief.FocusCaseIds = CoachMemorySnapshot.OverdueCaseIds;
|
||||
}
|
||||
else if (Brief.bPreferRecognitionReplayReview && CoachMemorySnapshot.WeakCaseIds.Num() > 0)
|
||||
{
|
||||
Brief.FocusCaseIds = CoachMemorySnapshot.WeakCaseIds;
|
||||
}
|
||||
else
|
||||
{
|
||||
Brief.FocusCaseIds = CoachMemorySnapshot.DueCaseIds;
|
||||
}
|
||||
}
|
||||
|
||||
return Brief;
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief(
|
||||
const FHyperTwistCoachBrief& CurrentBrief,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FHyperTwistTrainingCoachActionPlanOutcomeSummary& OutcomeSummary)
|
||||
{
|
||||
FHyperTwistCoachBrief Brief;
|
||||
if (!ActionPlan.IsStructurallyValid() || !OutcomeSummary.IsStructurallyValid())
|
||||
{
|
||||
return Brief;
|
||||
}
|
||||
|
||||
Brief.UserId = OutcomeSummary.UserId;
|
||||
Brief.ReferenceUtc = !OutcomeSummary.ReferenceUtc.IsEmpty()
|
||||
? OutcomeSummary.ReferenceUtc
|
||||
: ActionPlan.ReferenceUtc;
|
||||
Brief.TrainingSessionId = OutcomeSummary.SourceSessionId;
|
||||
Brief.SourceReviewPlanId = CurrentBrief.SourceReviewPlanId;
|
||||
Brief.FocusDeckId = !OutcomeSummary.FocusDeckId.IsEmpty()
|
||||
? OutcomeSummary.FocusDeckId
|
||||
: ActionPlan.FocusDeckId;
|
||||
Brief.FocusMethodSegmentId = !OutcomeSummary.FocusMethodSegmentId.IsEmpty()
|
||||
? OutcomeSummary.FocusMethodSegmentId
|
||||
: ActionPlan.FocusMethodSegmentId;
|
||||
Brief.bNeedsCoachReview = ActionPlan.bNeedsCoachReview
|
||||
|| OutcomeSummary.AbortedCount > 0
|
||||
|| (ActionPlan.bPreferRecognitionReplayReview && OutcomeSummary.ReplayBackedAttemptCount == 0);
|
||||
Brief.bNeedsCadenceRecovery = ActionPlan.bNeedsCadenceRecovery;
|
||||
Brief.bNeedsVarietyRotation = ActionPlan.bNeedsVarietyRotation;
|
||||
Brief.bPreferRecognitionReplayReview = ActionPlan.bPreferRecognitionReplayReview
|
||||
|| OutcomeSummary.RecognitionAttemptCount > 0;
|
||||
Brief.FocusCaseIds = OutcomeSummary.FollowUpCaseIds.Num() > 0
|
||||
? OutcomeSummary.FollowUpCaseIds
|
||||
: ActionPlan.FocusCaseIds;
|
||||
|
||||
if (!OutcomeSummary.bNeedsFollowUpPlan && Brief.FocusCaseIds.Num() == 0)
|
||||
{
|
||||
return FHyperTwistCoachBrief();
|
||||
}
|
||||
|
||||
Brief.PriorityScore = FMath::Clamp(
|
||||
(100.0f - OutcomeSummary.OutcomeScore)
|
||||
+ (static_cast<float>(OutcomeSummary.UntouchedFocusCaseCount) * 4.0f)
|
||||
+ (static_cast<float>(OutcomeSummary.FailureCount) * 3.0f)
|
||||
+ (static_cast<float>(OutcomeSummary.TimeoutCount) * 4.0f)
|
||||
+ (static_cast<float>(OutcomeSummary.DNFCount) * 5.0f)
|
||||
+ (static_cast<float>(OutcomeSummary.AbortedCount) * 6.0f),
|
||||
20.0f,
|
||||
100.0f
|
||||
);
|
||||
Brief.Priority = HyperTwistTrainingCoachLibraryInternal::PriorityFromScore(Brief.PriorityScore);
|
||||
|
||||
if (OutcomeSummary.AbortedCount > 0)
|
||||
{
|
||||
Brief.FocusLane = EHyperTwistCoachFocusLane::Cadence;
|
||||
Brief.Headline = TEXT("Resume the interrupted coaching sequence");
|
||||
Brief.PrimaryActionLabel = TEXT("resume_aborted_plan");
|
||||
Brief.SuggestedMode = EHyperTwistTrainingDeliveryMode::VirtualCube;
|
||||
Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Scripted;
|
||||
}
|
||||
else if (OutcomeSummary.TimeoutCount > 0 || OutcomeSummary.DNFCount > 0)
|
||||
{
|
||||
Brief.FocusLane = EHyperTwistCoachFocusLane::Execution;
|
||||
Brief.Headline = TEXT("Stabilize unfinished focus cases");
|
||||
Brief.PrimaryActionLabel = TEXT("stabilize_unfinished_cases");
|
||||
Brief.SuggestedMode = EHyperTwistTrainingDeliveryMode::VirtualCube;
|
||||
Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Scripted;
|
||||
}
|
||||
else if (OutcomeSummary.FailureCount > 0)
|
||||
{
|
||||
Brief.FocusLane = EHyperTwistCoachFocusLane::Accuracy;
|
||||
Brief.Headline = TEXT("Rebuild accuracy on coach-selected cases");
|
||||
Brief.PrimaryActionLabel = TEXT("rebuild_accuracy");
|
||||
Brief.SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
}
|
||||
else if (OutcomeSummary.UntouchedFocusCaseCount > 0)
|
||||
{
|
||||
Brief.FocusLane = EHyperTwistCoachFocusLane::ReviewBacklog;
|
||||
Brief.Headline = TEXT("Complete the remaining coach-selected cases");
|
||||
Brief.PrimaryActionLabel = TEXT("complete_focus_cases");
|
||||
Brief.SuggestedMode = ActionPlan.SuggestedMode;
|
||||
Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Scripted;
|
||||
}
|
||||
else if (Brief.bPreferRecognitionReplayReview)
|
||||
{
|
||||
Brief.FocusLane = EHyperTwistCoachFocusLane::Recognition;
|
||||
Brief.Headline = TEXT("Run recognition-focused follow-up review");
|
||||
Brief.PrimaryActionLabel = TEXT("recognition_follow_up");
|
||||
Brief.SuggestedMode = EHyperTwistTrainingDeliveryMode::RecognitionAssisted;
|
||||
Brief.SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
}
|
||||
else
|
||||
{
|
||||
Brief.FocusLane = CurrentBrief.FocusLane;
|
||||
Brief.Headline = TEXT("Reinforce the prior coach focus set");
|
||||
Brief.PrimaryActionLabel = TEXT("reinforce_focus_cases");
|
||||
Brief.SuggestedMode = ActionPlan.SuggestedMode;
|
||||
Brief.SuggestedSelectionPolicy = ActionPlan.SuggestedSelectionPolicy;
|
||||
}
|
||||
|
||||
FHyperTwistCoachSignal FollowUpSignal = HyperTwistTrainingCoachLibraryInternal::MakeSignal(
|
||||
Brief.PrimaryActionLabel,
|
||||
Brief.FocusLane,
|
||||
Brief.PriorityScore,
|
||||
Brief.Headline,
|
||||
FString::Printf(
|
||||
TEXT("followUpCases=%d, success=%d, failure=%d, timeout=%d, dnf=%d"),
|
||||
Brief.FocusCaseIds.Num(),
|
||||
OutcomeSummary.SuccessCount,
|
||||
OutcomeSummary.FailureCount,
|
||||
OutcomeSummary.TimeoutCount,
|
||||
OutcomeSummary.DNFCount),
|
||||
Brief.SuggestedMode,
|
||||
Brief.SuggestedSelectionPolicy
|
||||
);
|
||||
FollowUpSignal.SuggestedDeckId = Brief.FocusDeckId;
|
||||
FollowUpSignal.SuggestedMethodSegmentId = Brief.FocusMethodSegmentId;
|
||||
FollowUpSignal.SuggestedCaseIds = Brief.FocusCaseIds;
|
||||
Brief.Signals = { FollowUpSignal };
|
||||
|
||||
return Brief;
|
||||
}
|
||||
|
|
@ -296,6 +296,11 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
return UserId + CaseKeySeparator + BatchId;
|
||||
}
|
||||
|
||||
FString MakeCoachActionPlanKey(const FString& UserId, const FString& PlanId)
|
||||
{
|
||||
return UserId + CaseKeySeparator + PlanId;
|
||||
}
|
||||
|
||||
TArray<FString> DeduplicateCaseIds(const TArray<FString>& CaseIds)
|
||||
{
|
||||
TArray<FString> UniqueCaseIds;
|
||||
|
|
@ -395,6 +400,67 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
return NormalizedBatch;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan NormalizeCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan NormalizedPlan = ActionPlan;
|
||||
NormalizedPlan.ReferenceUtc = ResolveReferenceUtc(NormalizedPlan.ReferenceUtc);
|
||||
NormalizedPlan.FocusCaseIds = DeduplicateTemplateCaseIds(NormalizedPlan.FocusCaseIds);
|
||||
if (NormalizedPlan.CreatedAtUtc.IsEmpty())
|
||||
{
|
||||
NormalizedPlan.CreatedAtUtc = NormalizedPlan.ReferenceUtc;
|
||||
}
|
||||
|
||||
TMap<FString, int32> EntryIndexById;
|
||||
TArray<FHyperTwistTrainingCoachActionPlanEntry> NormalizedEntries;
|
||||
for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : ActionPlan.Entries)
|
||||
{
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const int32* ExistingIndex = EntryIndexById.Find(Entry.ActionId))
|
||||
{
|
||||
NormalizedEntries[*ExistingIndex] = Entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
EntryIndexById.Add(Entry.ActionId, NormalizedEntries.Add(Entry));
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedEntries.Sort([](
|
||||
const FHyperTwistTrainingCoachActionPlanEntry& Left,
|
||||
const FHyperTwistTrainingCoachActionPlanEntry& Right
|
||||
)
|
||||
{
|
||||
if (!FMath::IsNearlyEqual(Left.PriorityScore, Right.PriorityScore))
|
||||
{
|
||||
return Left.PriorityScore > Right.PriorityScore;
|
||||
}
|
||||
|
||||
return Left.ActionId < Right.ActionId;
|
||||
});
|
||||
NormalizedPlan.Entries = NormalizedEntries;
|
||||
|
||||
if (NormalizedPlan.FocusDeckId.IsEmpty() && NormalizedPlan.Entries.Num() > 0)
|
||||
{
|
||||
NormalizedPlan.FocusDeckId = NormalizedPlan.Entries[0].DeckId;
|
||||
}
|
||||
if (NormalizedPlan.FocusMethodSegmentId.IsEmpty() && NormalizedPlan.Entries.Num() > 0)
|
||||
{
|
||||
NormalizedPlan.FocusMethodSegmentId = NormalizedPlan.Entries[0].MethodSegmentId;
|
||||
}
|
||||
if (NormalizedPlan.Headline.IsEmpty())
|
||||
{
|
||||
NormalizedPlan.Headline = NormalizedPlan.PrimaryActionLabel;
|
||||
}
|
||||
|
||||
return NormalizedPlan;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseLearnerState DeriveLearnerStateFromReviewHistory(
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
|
|
@ -2577,6 +2643,29 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
SeenMethodDrillBatchKeys.Add(BatchKey);
|
||||
}
|
||||
|
||||
Report.TotalCoachActionPlanCount = RepositoryState.StoredCoachActionPlans.Num();
|
||||
TSet<FString> SeenCoachActionPlanKeys;
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
|
||||
{
|
||||
if (!ActionPlan.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidCoachActionPlanCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString ActionPlanKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachActionPlanKey(
|
||||
ActionPlan.UserId,
|
||||
ActionPlan.PlanId
|
||||
);
|
||||
if (SeenCoachActionPlanKeys.Contains(ActionPlanKey))
|
||||
{
|
||||
Report.DuplicateCoachActionPlanCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenCoachActionPlanKeys.Add(ActionPlanKey);
|
||||
}
|
||||
|
||||
if (Report.InvalidRunRecordCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
|
|
@ -2664,6 +2753,20 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
|
|||
TEXT("normalize-and-deduplicate-method-drill-batches")
|
||||
);
|
||||
}
|
||||
if (Report.InvalidCoachActionPlanCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("drop-invalid-coach-action-plans")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateCoachActionPlanCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("normalize-and-deduplicate-coach-action-plans")
|
||||
);
|
||||
}
|
||||
|
||||
return Report;
|
||||
}
|
||||
|
|
@ -2918,6 +3021,60 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairR
|
|||
return Left.BatchId < Right.BatchId;
|
||||
});
|
||||
|
||||
TMap<FString, int32> CoachActionPlanIndexByKey;
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
|
||||
{
|
||||
if (!ActionPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString ActionPlanKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCoachActionPlanKey(
|
||||
NormalizedPlan.UserId,
|
||||
NormalizedPlan.PlanId
|
||||
);
|
||||
if (const int32* ExistingIndex = CoachActionPlanIndexByKey.Find(ActionPlanKey))
|
||||
{
|
||||
RepairedState.StoredCoachActionPlans[*ExistingIndex] = NormalizedPlan;
|
||||
}
|
||||
else
|
||||
{
|
||||
CoachActionPlanIndexByKey.Add(
|
||||
ActionPlanKey,
|
||||
RepairedState.StoredCoachActionPlans.Add(NormalizedPlan)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RepairedState.StoredCoachActionPlans.Sort([](
|
||||
const FHyperTwistTrainingCoachActionPlan& Left,
|
||||
const FHyperTwistTrainingCoachActionPlan& Right
|
||||
)
|
||||
{
|
||||
if (Left.UserId != Right.UserId)
|
||||
{
|
||||
return Left.UserId < Right.UserId;
|
||||
}
|
||||
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.CreatedAtUtc,
|
||||
Right.CreatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.PlanId < Right.PlanId;
|
||||
});
|
||||
|
||||
return RepairedState;
|
||||
}
|
||||
|
||||
|
|
@ -4328,6 +4485,637 @@ TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingRepositoryLibrary:
|
|||
return EffectiveTemplates;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
const FHyperTwistCoachBrief& CoachBrief,
|
||||
const FString& PlanId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan ActionPlan;
|
||||
if (CoachBrief.UserId.IsEmpty() || PlanId.IsEmpty())
|
||||
{
|
||||
return ActionPlan;
|
||||
}
|
||||
|
||||
ActionPlan.PlanId = PlanId;
|
||||
ActionPlan.UserId = CoachBrief.UserId;
|
||||
ActionPlan.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
!ReferenceUtc.IsEmpty() ? ReferenceUtc : CoachBrief.ReferenceUtc
|
||||
);
|
||||
ActionPlan.SourceSessionId = CoachBrief.TrainingSessionId;
|
||||
ActionPlan.SourceReviewPlanId = CoachBrief.SourceReviewPlanId;
|
||||
ActionPlan.FocusDeckId = CoachBrief.FocusDeckId;
|
||||
ActionPlan.FocusMethodSegmentId = CoachBrief.FocusMethodSegmentId;
|
||||
ActionPlan.FocusLaneLabel = StaticEnum<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
|
||||
static_cast<int64>(CoachBrief.FocusLane)
|
||||
);
|
||||
ActionPlan.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
|
||||
static_cast<int64>(CoachBrief.Priority)
|
||||
);
|
||||
ActionPlan.PriorityScore = CoachBrief.PriorityScore;
|
||||
ActionPlan.Headline = CoachBrief.Headline;
|
||||
ActionPlan.PrimaryActionLabel = CoachBrief.PrimaryActionLabel;
|
||||
ActionPlan.SuggestedMode = CoachBrief.SuggestedMode;
|
||||
ActionPlan.SuggestedSelectionPolicy = CoachBrief.SuggestedSelectionPolicy;
|
||||
ActionPlan.FocusCaseIds = CoachBrief.FocusCaseIds;
|
||||
ActionPlan.PlanState = EHyperTwistTrainingCoachActionPlanState::Draft;
|
||||
ActionPlan.CreatedAtUtc = ActionPlan.ReferenceUtc;
|
||||
ActionPlan.bNeedsCoachReview = CoachBrief.bNeedsCoachReview;
|
||||
ActionPlan.bNeedsCadenceRecovery = CoachBrief.bNeedsCadenceRecovery;
|
||||
ActionPlan.bNeedsVarietyRotation = CoachBrief.bNeedsVarietyRotation;
|
||||
ActionPlan.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
|
||||
for (int32 Index = 0; Index < CoachBrief.FocusCaseIds.Num(); ++Index)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanEntry Entry;
|
||||
Entry.ActionId = FString::Printf(TEXT("%s_entry_%02d"), *PlanId, Index + 1);
|
||||
Entry.CaseId = CoachBrief.FocusCaseIds[Index];
|
||||
Entry.DeckId = CoachBrief.FocusDeckId;
|
||||
Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
|
||||
Entry.ActionLabel = CoachBrief.PrimaryActionLabel;
|
||||
Entry.Headline = CoachBrief.Headline;
|
||||
Entry.PriorityScore = CoachBrief.PriorityScore;
|
||||
Entry.SuggestedMode = CoachBrief.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = CoachBrief.SuggestedSelectionPolicy;
|
||||
Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
if (Entry.IsStructurallyValid())
|
||||
{
|
||||
ActionPlan.Entries.Add(Entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (ActionPlan.Entries.Num() == 0)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanEntry Entry;
|
||||
Entry.ActionId = FString::Printf(TEXT("%s_entry_01"), *PlanId);
|
||||
Entry.CaseId = TEXT("coach-generic");
|
||||
Entry.DeckId = CoachBrief.FocusDeckId;
|
||||
Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
|
||||
Entry.ActionLabel = CoachBrief.PrimaryActionLabel.IsEmpty()
|
||||
? TEXT("coach-focus")
|
||||
: CoachBrief.PrimaryActionLabel;
|
||||
Entry.Headline = CoachBrief.Headline;
|
||||
Entry.PriorityScore = CoachBrief.PriorityScore;
|
||||
Entry.SuggestedMode = CoachBrief.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = CoachBrief.SuggestedSelectionPolicy;
|
||||
Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
|
||||
ActionPlan.Entries.Add(Entry);
|
||||
}
|
||||
|
||||
return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::ActivateCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan UpdatedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!UpdatedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
UpdatedPlan.PlanState = EHyperTwistTrainingCoachActionPlanState::Active;
|
||||
UpdatedPlan.SourceSessionId = !TrainingSessionId.IsEmpty() ? TrainingSessionId : UpdatedPlan.SourceSessionId;
|
||||
UpdatedPlan.StartedAtUtc = !StartedAtUtc.IsEmpty()
|
||||
? StartedAtUtc
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::FinalizeCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
bool bAbortPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlan UpdatedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!UpdatedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
UpdatedPlan.SourceSessionId = !Session.TrainingSessionId.IsEmpty()
|
||||
? Session.TrainingSessionId
|
||||
: UpdatedPlan.SourceSessionId;
|
||||
UpdatedPlan.CompletedAtUtc = !Session.EndedAtUtc.IsEmpty()
|
||||
? Session.EndedAtUtc
|
||||
: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
|
||||
UpdatedPlan.PlanState = bAbortPlan || Session.SessionState == EHyperTwistTrainingSessionState::Aborted
|
||||
? EHyperTwistTrainingCoachActionPlanState::Aborted
|
||||
: EHyperTwistTrainingCoachActionPlanState::Completed;
|
||||
if (UpdatedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed)
|
||||
{
|
||||
for (FHyperTwistTrainingCoachActionPlanEntry& Entry : UpdatedPlan.Entries)
|
||||
{
|
||||
Entry.bCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return UpdatedPlan;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> UHyperTwistTrainingRepositoryLibrary::ListCoachActionPlansForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> ActionPlans;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return ActionPlans;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
|
||||
{
|
||||
if (ActionPlan.UserId == UserId && ActionPlan.IsStructurallyValid())
|
||||
{
|
||||
ActionPlans.Add(ActionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
ActionPlans.Sort([](const FHyperTwistTrainingCoachActionPlan& Left, const FHyperTwistTrainingCoachActionPlan& Right)
|
||||
{
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.CreatedAtUtc,
|
||||
Right.CreatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.PlanId < Right.PlanId;
|
||||
});
|
||||
return ActionPlans;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRepositoryLibrary::TryGetCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
)
|
||||
{
|
||||
OutActionPlan = FHyperTwistTrainingCoachActionPlan();
|
||||
if (PlanId.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingCoachActionPlan* MatchingPlan =
|
||||
RepositoryState.StoredCoachActionPlans.FindByPredicate(
|
||||
[&PlanId](const FHyperTwistTrainingCoachActionPlan& Candidate)
|
||||
{
|
||||
return Candidate.PlanId == PlanId && Candidate.IsStructurallyValid();
|
||||
}
|
||||
);
|
||||
if (MatchingPlan == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutActionPlan = *MatchingPlan;
|
||||
return true;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan* ExistingPlan =
|
||||
UpdatedRepositoryState.StoredCoachActionPlans.FindByPredicate(
|
||||
[&NormalizedPlan](const FHyperTwistTrainingCoachActionPlan& Candidate)
|
||||
{
|
||||
return Candidate.UserId == NormalizedPlan.UserId
|
||||
&& Candidate.PlanId == NormalizedPlan.PlanId;
|
||||
}
|
||||
);
|
||||
if (ExistingPlan != nullptr)
|
||||
{
|
||||
*ExistingPlan = NormalizedPlan;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatedRepositoryState.StoredCoachActionPlans.Add(NormalizedPlan);
|
||||
}
|
||||
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanSummary Summary;
|
||||
if (UserId.IsEmpty())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
|
||||
|
||||
const TArray<FHyperTwistTrainingCoachActionPlan> ActionPlans = ListCoachActionPlansForUser(
|
||||
RepositoryState,
|
||||
UserId
|
||||
);
|
||||
if (ActionPlans.Num() == 0)
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.TotalPlanCount = ActionPlans.Num();
|
||||
int32 SuggestedModeCounts[5] = {0, 0, 0, 0, 0};
|
||||
TMap<FString, int32> FocusLaneCounts;
|
||||
TMap<FString, int32> ActionLabelCounts;
|
||||
int32 CompletedPlanDurationAccumulator = 0;
|
||||
int32 CompletedPlanDurationCount = 0;
|
||||
float PriorityScoreAccumulator = 0.0f;
|
||||
FString LatestOpenPlanUtc;
|
||||
FString LatestCompletedUtc;
|
||||
|
||||
for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : ActionPlans)
|
||||
{
|
||||
PriorityScoreAccumulator += ActionPlan.PriorityScore;
|
||||
Summary.TotalEntryCount += ActionPlan.Entries.Num();
|
||||
FocusLaneCounts.FindOrAdd(ActionPlan.FocusLaneLabel) += 1;
|
||||
ActionLabelCounts.FindOrAdd(ActionPlan.PrimaryActionLabel) += 1;
|
||||
|
||||
const int32 SuggestedModeIndex = static_cast<int32>(ActionPlan.SuggestedMode);
|
||||
if (SuggestedModeIndex >= 0 && SuggestedModeIndex < UE_ARRAY_COUNT(SuggestedModeCounts))
|
||||
{
|
||||
SuggestedModeCounts[SuggestedModeIndex] += 1;
|
||||
}
|
||||
|
||||
int32 LocalCompletedEntries = 0;
|
||||
for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : ActionPlan.Entries)
|
||||
{
|
||||
if (Entry.bCompleted)
|
||||
{
|
||||
LocalCompletedEntries += 1;
|
||||
}
|
||||
if (Entry.bPreferRecognitionReplayReview)
|
||||
{
|
||||
Summary.bHasRecognitionBiasedPlans = true;
|
||||
}
|
||||
}
|
||||
|
||||
Summary.CompletedEntryCount += LocalCompletedEntries;
|
||||
Summary.OpenEntryCount += ActionPlan.Entries.Num() - LocalCompletedEntries;
|
||||
Summary.bHasRecognitionBiasedPlans = Summary.bHasRecognitionBiasedPlans || ActionPlan.bPreferRecognitionReplayReview;
|
||||
|
||||
switch (ActionPlan.PlanState)
|
||||
{
|
||||
case EHyperTwistTrainingCoachActionPlanState::Draft:
|
||||
Summary.DraftPlanCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachActionPlanState::Active:
|
||||
Summary.ActivePlanCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachActionPlanState::Completed:
|
||||
Summary.CompletedPlanCount += 1;
|
||||
break;
|
||||
case EHyperTwistTrainingCoachActionPlanState::Aborted:
|
||||
Summary.AbortedPlanCount += 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Active
|
||||
|| ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Draft)
|
||||
{
|
||||
const FString SortUtc = !ActionPlan.StartedAtUtc.IsEmpty()
|
||||
? ActionPlan.StartedAtUtc
|
||||
: ActionPlan.CreatedAtUtc;
|
||||
if (Summary.CurrentPlanId.IsEmpty()
|
||||
|| HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestOpenPlanUtc) > 0)
|
||||
{
|
||||
Summary.CurrentPlanId = ActionPlan.PlanId;
|
||||
Summary.FocusDeckId = ActionPlan.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = ActionPlan.FocusMethodSegmentId;
|
||||
LatestOpenPlanUtc = SortUtc;
|
||||
}
|
||||
}
|
||||
|
||||
if (ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed
|
||||
&& !ActionPlan.CompletedAtUtc.IsEmpty())
|
||||
{
|
||||
const int32 DurationMs = HyperTwistTrainingRepositoryLibraryInternal::ComputeElapsedMilliseconds(
|
||||
ActionPlan.CreatedAtUtc,
|
||||
ActionPlan.CompletedAtUtc
|
||||
);
|
||||
if (DurationMs > 0)
|
||||
{
|
||||
CompletedPlanDurationAccumulator += DurationMs;
|
||||
CompletedPlanDurationCount += 1;
|
||||
if (Summary.BestCompletedPlanDurationMs == 0 || DurationMs < Summary.BestCompletedPlanDurationMs)
|
||||
{
|
||||
Summary.BestCompletedPlanDurationMs = DurationMs;
|
||||
}
|
||||
}
|
||||
|
||||
if (Summary.LastCompletedPlanId.IsEmpty()
|
||||
|| HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(ActionPlan.CompletedAtUtc, LatestCompletedUtc) > 0)
|
||||
{
|
||||
Summary.LastCompletedPlanId = ActionPlan.PlanId;
|
||||
LatestCompletedUtc = ActionPlan.CompletedAtUtc;
|
||||
if (Summary.CurrentPlanId.IsEmpty())
|
||||
{
|
||||
Summary.FocusDeckId = ActionPlan.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = ActionPlan.FocusMethodSegmentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ActionPlan.bNeedsCoachReview
|
||||
|| ActionPlan.bNeedsCadenceRecovery
|
||||
|| ActionPlan.bNeedsVarietyRotation
|
||||
|| ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Aborted)
|
||||
{
|
||||
Summary.bNeedsCoachIntervention = true;
|
||||
}
|
||||
}
|
||||
|
||||
Summary.AveragePriorityScore = PriorityScoreAccumulator / static_cast<float>(Summary.TotalPlanCount);
|
||||
if (CompletedPlanDurationCount > 0)
|
||||
{
|
||||
Summary.AverageCompletedPlanDurationMs =
|
||||
CompletedPlanDurationAccumulator / CompletedPlanDurationCount;
|
||||
}
|
||||
|
||||
int32 BestModeCount = 0;
|
||||
for (int32 Index = 0; Index < UE_ARRAY_COUNT(SuggestedModeCounts); ++Index)
|
||||
{
|
||||
if (SuggestedModeCounts[Index] > BestModeCount)
|
||||
{
|
||||
BestModeCount = SuggestedModeCounts[Index];
|
||||
Summary.DominantSuggestedMode = static_cast<EHyperTwistTrainingDeliveryMode>(Index);
|
||||
}
|
||||
}
|
||||
|
||||
int32 BestFocusLaneCount = 0;
|
||||
for (const TPair<FString, int32>& Pair : FocusLaneCounts)
|
||||
{
|
||||
if (Pair.Value > BestFocusLaneCount)
|
||||
{
|
||||
BestFocusLaneCount = Pair.Value;
|
||||
Summary.DominantFocusLaneLabel = Pair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
int32 BestActionLabelCount = 0;
|
||||
for (const TPair<FString, int32>& Pair : ActionLabelCounts)
|
||||
{
|
||||
if (Pair.Value > BestActionLabelCount)
|
||||
{
|
||||
BestActionLabelCount = Pair.Value;
|
||||
Summary.DominantActionLabel = Pair.Key;
|
||||
}
|
||||
}
|
||||
|
||||
Summary.ActionPressureScore =
|
||||
static_cast<float>(Summary.ActivePlanCount * 3)
|
||||
+ static_cast<float>(Summary.DraftPlanCount * 2)
|
||||
+ static_cast<float>(Summary.AbortedPlanCount * 2)
|
||||
+ (static_cast<float>(Summary.OpenEntryCount) * 0.5f)
|
||||
+ (Summary.bNeedsCoachIntervention ? 5.0f : 0.0f);
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FString& ReferenceUtc
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingCoachActionPlanOutcomeSummary Summary;
|
||||
const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
|
||||
if (!NormalizedPlan.IsStructurallyValid())
|
||||
{
|
||||
return Summary;
|
||||
}
|
||||
|
||||
Summary.UserId = NormalizedPlan.UserId;
|
||||
Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
|
||||
!ReferenceUtc.IsEmpty() ? ReferenceUtc : NormalizedPlan.ReferenceUtc
|
||||
);
|
||||
Summary.PlanId = NormalizedPlan.PlanId;
|
||||
Summary.SourceSessionId = NormalizedPlan.SourceSessionId;
|
||||
Summary.FocusDeckId = NormalizedPlan.FocusDeckId;
|
||||
Summary.FocusMethodSegmentId = NormalizedPlan.FocusMethodSegmentId;
|
||||
Summary.PrimaryActionLabel = NormalizedPlan.PrimaryActionLabel;
|
||||
Summary.PlanState = NormalizedPlan.PlanState;
|
||||
Summary.SuggestedMode = NormalizedPlan.SuggestedMode;
|
||||
|
||||
TSet<FString> FocusCaseIds;
|
||||
for (const FString& FocusCaseId : NormalizedPlan.FocusCaseIds)
|
||||
{
|
||||
if (!FocusCaseId.IsEmpty())
|
||||
{
|
||||
FocusCaseIds.Add(FocusCaseId);
|
||||
}
|
||||
}
|
||||
|
||||
TSet<FString> CompletedCaseIds;
|
||||
for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : NormalizedPlan.Entries)
|
||||
{
|
||||
if (!Entry.CaseId.IsEmpty() && FocusCaseIds.Num() == 0)
|
||||
{
|
||||
FocusCaseIds.Add(Entry.CaseId);
|
||||
}
|
||||
|
||||
if (Entry.bCompleted && !Entry.CaseId.IsEmpty())
|
||||
{
|
||||
CompletedCaseIds.Add(Entry.CaseId);
|
||||
}
|
||||
}
|
||||
|
||||
Summary.FocusCaseCount = FocusCaseIds.Num();
|
||||
|
||||
FHyperTwistTrainingRunRecord RunRecord;
|
||||
if (!NormalizedPlan.SourceSessionId.IsEmpty()
|
||||
&& TryGetRunRecord(RepositoryState, NormalizedPlan.SourceSessionId, RunRecord))
|
||||
{
|
||||
TSet<FString> TouchedCaseIds;
|
||||
int32 TotalTimeAccumulator = 0;
|
||||
int32 TotalTimeSampleCount = 0;
|
||||
int32 RecognitionTimeAccumulator = 0;
|
||||
int32 RecognitionTimeSampleCount = 0;
|
||||
|
||||
for (const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry : RunRecord.AttemptHistory)
|
||||
{
|
||||
if (FocusCaseIds.Num() > 0 && !FocusCaseIds.Contains(AttemptHistoryEntry.CaseId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Summary.AttemptCount += 1;
|
||||
TouchedCaseIds.Add(AttemptHistoryEntry.CaseId);
|
||||
|
||||
if (!AttemptHistoryEntry.ReplayId.IsEmpty())
|
||||
{
|
||||
Summary.ReplayBackedAttemptCount += 1;
|
||||
}
|
||||
|
||||
if (AttemptHistoryEntry.DeliveryMode == EHyperTwistTrainingDeliveryMode::RecognitionAssisted
|
||||
|| AttemptHistoryEntry.RecognitionTimeMs > 0)
|
||||
{
|
||||
Summary.RecognitionAttemptCount += 1;
|
||||
}
|
||||
|
||||
if (AttemptHistoryEntry.TotalTimeMs > 0)
|
||||
{
|
||||
TotalTimeAccumulator += AttemptHistoryEntry.TotalTimeMs;
|
||||
TotalTimeSampleCount += 1;
|
||||
if (Summary.BestTotalTimeMs == 0 || AttemptHistoryEntry.TotalTimeMs < Summary.BestTotalTimeMs)
|
||||
{
|
||||
Summary.BestTotalTimeMs = AttemptHistoryEntry.TotalTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
if (AttemptHistoryEntry.RecognitionTimeMs > 0)
|
||||
{
|
||||
RecognitionTimeAccumulator += AttemptHistoryEntry.RecognitionTimeMs;
|
||||
RecognitionTimeSampleCount += 1;
|
||||
}
|
||||
|
||||
switch (AttemptHistoryEntry.Result)
|
||||
{
|
||||
case EHyperTwistTrainingAttemptResult::Success:
|
||||
Summary.SuccessCount += 1;
|
||||
if (!AttemptHistoryEntry.CaseId.IsEmpty())
|
||||
{
|
||||
CompletedCaseIds.Add(AttemptHistoryEntry.CaseId);
|
||||
}
|
||||
break;
|
||||
|
||||
case EHyperTwistTrainingAttemptResult::Failure:
|
||||
Summary.FailureCount += 1;
|
||||
break;
|
||||
|
||||
case EHyperTwistTrainingAttemptResult::Timeout:
|
||||
Summary.TimeoutCount += 1;
|
||||
break;
|
||||
|
||||
case EHyperTwistTrainingAttemptResult::DNF:
|
||||
Summary.DNFCount += 1;
|
||||
break;
|
||||
|
||||
case EHyperTwistTrainingAttemptResult::Aborted:
|
||||
Summary.AbortedCount += 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Summary.TouchedFocusCaseCount = TouchedCaseIds.Num();
|
||||
if (TotalTimeSampleCount > 0)
|
||||
{
|
||||
Summary.AverageTotalTimeMs = TotalTimeAccumulator / TotalTimeSampleCount;
|
||||
}
|
||||
if (RecognitionTimeSampleCount > 0)
|
||||
{
|
||||
Summary.AverageRecognitionTimeMs = RecognitionTimeAccumulator / RecognitionTimeSampleCount;
|
||||
}
|
||||
}
|
||||
|
||||
Summary.CompletedFocusCaseCount = CompletedCaseIds.Num();
|
||||
Summary.UntouchedFocusCaseCount = FMath::Max(0, Summary.FocusCaseCount - Summary.TouchedFocusCaseCount);
|
||||
for (const FString& FocusCaseId : NormalizedPlan.FocusCaseIds)
|
||||
{
|
||||
if (!FocusCaseId.IsEmpty() && !CompletedCaseIds.Contains(FocusCaseId))
|
||||
{
|
||||
Summary.FollowUpCaseIds.Add(FocusCaseId);
|
||||
}
|
||||
}
|
||||
if (Summary.FollowUpCaseIds.Num() == 0)
|
||||
{
|
||||
for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : NormalizedPlan.Entries)
|
||||
{
|
||||
if (!Entry.CaseId.IsEmpty()
|
||||
&& !CompletedCaseIds.Contains(Entry.CaseId)
|
||||
&& !Summary.FollowUpCaseIds.Contains(Entry.CaseId))
|
||||
{
|
||||
Summary.FollowUpCaseIds.Add(Entry.CaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Summary.FocusCaseCount > 0)
|
||||
{
|
||||
Summary.CompletionRate = static_cast<float>(Summary.CompletedFocusCaseCount)
|
||||
/ static_cast<float>(Summary.FocusCaseCount);
|
||||
}
|
||||
|
||||
const FString PlanStartUtc = !NormalizedPlan.StartedAtUtc.IsEmpty()
|
||||
? NormalizedPlan.StartedAtUtc
|
||||
: NormalizedPlan.CreatedAtUtc;
|
||||
const FString PlanEndUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
|
||||
? NormalizedPlan.CompletedAtUtc
|
||||
: Summary.ReferenceUtc;
|
||||
if (!PlanStartUtc.IsEmpty() && !PlanEndUtc.IsEmpty())
|
||||
{
|
||||
Summary.PlanDurationMs = HyperTwistTrainingRepositoryLibraryInternal::ComputeElapsedMilliseconds(
|
||||
PlanStartUtc,
|
||||
PlanEndUtc
|
||||
);
|
||||
}
|
||||
|
||||
const bool bPlanTerminal =
|
||||
NormalizedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed
|
||||
|| NormalizedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Aborted;
|
||||
Summary.bMetPrimaryGoal =
|
||||
bPlanTerminal
|
||||
&& NormalizedPlan.PlanState != EHyperTwistTrainingCoachActionPlanState::Aborted
|
||||
&& (Summary.FocusCaseCount == 0
|
||||
? Summary.SuccessCount > 0
|
||||
: Summary.CompletedFocusCaseCount >= Summary.FocusCaseCount);
|
||||
|
||||
Summary.OutcomeScore =
|
||||
(Summary.CompletionRate * 100.0f)
|
||||
+ (static_cast<float>(Summary.SuccessCount) * 4.0f)
|
||||
- (static_cast<float>(Summary.FailureCount) * 2.0f)
|
||||
- (static_cast<float>(Summary.TimeoutCount) * 3.0f)
|
||||
- (static_cast<float>(Summary.DNFCount) * 4.0f)
|
||||
- (static_cast<float>(Summary.AbortedCount) * 5.0f)
|
||||
- (static_cast<float>(Summary.UntouchedFocusCaseCount) * 4.0f)
|
||||
+ (static_cast<float>(Summary.ReplayBackedAttemptCount) * 0.5f);
|
||||
|
||||
Summary.bNeedsFollowUpPlan =
|
||||
Summary.FollowUpCaseIds.Num() > 0
|
||||
|| (
|
||||
bPlanTerminal
|
||||
&& (
|
||||
NormalizedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Aborted
|
||||
|| !Summary.bMetPrimaryGoal
|
||||
|| Summary.FailureCount > 0
|
||||
|| Summary.TimeoutCount > 0
|
||||
|| Summary.DNFCount > 0
|
||||
|| (NormalizedPlan.bNeedsCoachReview && Summary.ReplayBackedAttemptCount == 0)
|
||||
));
|
||||
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseRecommendation UHyperTwistTrainingRepositoryLibrary::MakeCaseRecommendation(
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
const FHyperTwistTrainingCaseLearnerState& LearnerState,
|
||||
|
|
|
|||
|
|
@ -194,6 +194,98 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRuntimeLibrary::GetAct
|
|||
return FHyperTwistTrainingCoachMemorySnapshot();
|
||||
}
|
||||
|
||||
FHyperTwistReplayReviewAnalytics UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingReplayReviewAnalytics(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveReplayReviewAnalytics();
|
||||
}
|
||||
|
||||
return FHyperTwistReplayReviewAnalytics();
|
||||
}
|
||||
|
||||
FHyperTwistRecognitionReplaySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRecognitionReplaySummary(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveRecognitionReplaySummary();
|
||||
}
|
||||
|
||||
return FHyperTwistRecognitionReplaySummary();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistCoachSignal> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachSignals(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachSignals();
|
||||
}
|
||||
|
||||
return TArray<FHyperTwistCoachSignal>();
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachBrief(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachBrief();
|
||||
}
|
||||
|
||||
return FHyperTwistCoachBrief();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachActionPlan(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachActionPlan();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingCoachActionPlan();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachActionPlanSummary(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachActionPlanSummary();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingCoachActionPlanSummary();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachActionPlanOutcomeSummary(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachActionPlanOutcomeSummary();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingCoachActionPlanOutcomeSummary();
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachFollowUpBrief(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveCoachFollowUpBrief();
|
||||
}
|
||||
|
||||
return FHyperTwistCoachBrief();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingMethodSegment> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
|
|
@ -368,6 +460,21 @@ bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingReviewPlan(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingCoachActionPlan(
|
||||
UObject* WorldContextObject,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->TryGetStoredCoachActionPlan(PlanId, OutActionPlan);
|
||||
}
|
||||
|
||||
OutActionPlan = FHyperTwistTrainingCoachActionPlan();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredTrainingSessionTemplate(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
|
|
@ -606,6 +713,32 @@ FHyperTwistTrainingDeck UHyperTwistTrainingRuntimeLibrary::BuildActiveReviewDeck
|
|||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistTrainingRuntimeLibrary::BuildActiveCoachRecommendedDeck(
|
||||
UObject* WorldContextObject,
|
||||
int32 MaxCases
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->BuildActiveCoachRecommendedDeck(MaxCases);
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistTrainingRuntimeLibrary::BuildActiveCoachFollowUpDeck(
|
||||
UObject* WorldContextObject,
|
||||
int32 MaxCases
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->BuildActiveCoachFollowUpDeck(MaxCases);
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartTrainingRunFromTemplate(
|
||||
UObject* WorldContextObject,
|
||||
const FString& TemplateId,
|
||||
|
|
@ -636,6 +769,34 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartRecommendedR
|
|||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachRecommendedRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->StartCoachRecommendedRun(SessionId, MaxCases);
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartCoachFollowUpRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->StartCoachFollowUpRun(SessionId, MaxCases);
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::StartSampleClassicTrainingRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& UserId,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
|
||||
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionReplayLibrary.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayReviewLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
|
|
@ -85,6 +88,46 @@ FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingSubsystem::GetActiveCo
|
|||
return ActiveCoachMemorySnapshot;
|
||||
}
|
||||
|
||||
FHyperTwistReplayReviewAnalytics UHyperTwistTrainingSubsystem::GetActiveReplayReviewAnalytics() const
|
||||
{
|
||||
return ActiveReplayReviewAnalytics;
|
||||
}
|
||||
|
||||
FHyperTwistRecognitionReplaySummary UHyperTwistTrainingSubsystem::GetActiveRecognitionReplaySummary() const
|
||||
{
|
||||
return ActiveRecognitionReplaySummary;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistCoachSignal> UHyperTwistTrainingSubsystem::GetActiveCoachSignals() const
|
||||
{
|
||||
return ActiveCoachSignals;
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistTrainingSubsystem::GetActiveCoachBrief() const
|
||||
{
|
||||
return ActiveCoachBrief;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingSubsystem::GetActiveCoachActionPlan() const
|
||||
{
|
||||
return ActiveCoachActionPlan;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanSummary UHyperTwistTrainingSubsystem::GetActiveCoachActionPlanSummary() const
|
||||
{
|
||||
return ActiveCoachActionPlanSummary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistTrainingSubsystem::GetActiveCoachActionPlanOutcomeSummary() const
|
||||
{
|
||||
return ActiveCoachActionPlanOutcomeSummary;
|
||||
}
|
||||
|
||||
FHyperTwistCoachBrief UHyperTwistTrainingSubsystem::GetActiveCoachFollowUpBrief() const
|
||||
{
|
||||
return ActiveCoachFollowUpBrief;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingMethodSegment> UHyperTwistTrainingSubsystem::GetActiveLearnerMethodSegments() const
|
||||
{
|
||||
return ActiveLearnerMethodSegments;
|
||||
|
|
@ -208,6 +251,18 @@ bool UHyperTwistTrainingSubsystem::TryGetStoredReviewPlan(
|
|||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredCoachActionPlan(
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::TryGetCoachActionPlan(
|
||||
TrainingRepositoryState,
|
||||
PlanId,
|
||||
OutActionPlan
|
||||
);
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetStoredTrainingSessionTemplate(
|
||||
const FString& UserId,
|
||||
const FString& TemplateId,
|
||||
|
|
@ -496,6 +551,136 @@ FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveReviewDeck(int3
|
|||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachRecommendedDeck(int32 MaxCases) const
|
||||
{
|
||||
if (!ActiveRunState.ActiveDeck.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
const int32 DesiredCaseCount = MaxCases > 0 ? MaxCases : 3;
|
||||
TArray<FHyperTwistTrainingCase> FocusCases;
|
||||
for (const FString& FocusCaseId : ActiveCoachBrief.FocusCaseIds)
|
||||
{
|
||||
const FHyperTwistTrainingCase* MatchingCase = ActiveRunState.ActiveDeck.Cases.FindByPredicate(
|
||||
[&FocusCaseId](const FHyperTwistTrainingCase& Candidate)
|
||||
{
|
||||
return Candidate.CaseId == FocusCaseId;
|
||||
}
|
||||
);
|
||||
if (MatchingCase != nullptr)
|
||||
{
|
||||
FocusCases.Add(*MatchingCase);
|
||||
if (FocusCases.Num() >= DesiredCaseCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FocusCases.Num() == 0)
|
||||
{
|
||||
const FHyperTwistTrainingDeck ReviewDeck = BuildActiveReviewDeck(DesiredCaseCount);
|
||||
if (ReviewDeck.IsStructurallyValid())
|
||||
{
|
||||
return ReviewDeck;
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingCase& Candidate : ActiveRunState.ActiveDeck.Cases)
|
||||
{
|
||||
FocusCases.Add(Candidate);
|
||||
if (FocusCases.Num() >= DesiredCaseCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FocusCases.Num() == 0)
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck FocusDeck = ActiveRunState.ActiveDeck;
|
||||
FocusDeck.Cases = FocusCases;
|
||||
FocusDeck.SelectionPolicy = ActiveCoachBrief.SuggestedSelectionPolicy;
|
||||
if (ActiveCoachBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer
|
||||
&& !FocusDeck.DeliveryModes.Contains(ActiveCoachBrief.SuggestedMode))
|
||||
{
|
||||
FocusDeck.DeliveryModes.Insert(ActiveCoachBrief.SuggestedMode, 0);
|
||||
}
|
||||
if (!ActiveCoachBrief.Headline.IsEmpty())
|
||||
{
|
||||
FocusDeck.Title = FString::Printf(TEXT("%s Focus"), *ActiveCoachBrief.Headline);
|
||||
}
|
||||
if (!FocusDeck.Tags.Contains(TEXT("coach-focus")))
|
||||
{
|
||||
FocusDeck.Tags.Add(TEXT("coach-focus"));
|
||||
}
|
||||
if (!ActiveCoachBrief.FocusMethodSegmentId.IsEmpty() && FocusDeck.SubsetId.IsEmpty())
|
||||
{
|
||||
FocusDeck.SubsetId = ActiveCoachBrief.FocusMethodSegmentId;
|
||||
}
|
||||
|
||||
return FocusDeck;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck UHyperTwistTrainingSubsystem::BuildActiveCoachFollowUpDeck(int32 MaxCases) const
|
||||
{
|
||||
if (!ActiveRunState.ActiveDeck.IsStructurallyValid() || ActiveCoachFollowUpBrief.UserId.IsEmpty())
|
||||
{
|
||||
return FHyperTwistTrainingDeck();
|
||||
}
|
||||
|
||||
const int32 DesiredCaseCount = MaxCases > 0 ? MaxCases : 3;
|
||||
TArray<FHyperTwistTrainingCase> FocusCases;
|
||||
for (const FString& FocusCaseId : ActiveCoachFollowUpBrief.FocusCaseIds)
|
||||
{
|
||||
const FHyperTwistTrainingCase* MatchingCase = ActiveRunState.ActiveDeck.Cases.FindByPredicate(
|
||||
[&FocusCaseId](const FHyperTwistTrainingCase& Candidate)
|
||||
{
|
||||
return Candidate.CaseId == FocusCaseId;
|
||||
}
|
||||
);
|
||||
if (MatchingCase != nullptr)
|
||||
{
|
||||
FocusCases.Add(*MatchingCase);
|
||||
if (FocusCases.Num() >= DesiredCaseCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FocusCases.Num() == 0)
|
||||
{
|
||||
return BuildActiveCoachRecommendedDeck(DesiredCaseCount);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck FocusDeck = ActiveRunState.ActiveDeck;
|
||||
FocusDeck.Cases = FocusCases;
|
||||
FocusDeck.SelectionPolicy = ActiveCoachFollowUpBrief.SuggestedSelectionPolicy;
|
||||
if (ActiveCoachFollowUpBrief.SuggestedMode != EHyperTwistTrainingDeliveryMode::Timer
|
||||
&& !FocusDeck.DeliveryModes.Contains(ActiveCoachFollowUpBrief.SuggestedMode))
|
||||
{
|
||||
FocusDeck.DeliveryModes.Insert(ActiveCoachFollowUpBrief.SuggestedMode, 0);
|
||||
}
|
||||
if (!ActiveCoachFollowUpBrief.Headline.IsEmpty())
|
||||
{
|
||||
FocusDeck.Title = FString::Printf(TEXT("%s Follow-Up"), *ActiveCoachFollowUpBrief.Headline);
|
||||
}
|
||||
if (!FocusDeck.Tags.Contains(TEXT("coach-follow-up")))
|
||||
{
|
||||
FocusDeck.Tags.Add(TEXT("coach-follow-up"));
|
||||
}
|
||||
if (!ActiveCoachFollowUpBrief.FocusMethodSegmentId.IsEmpty())
|
||||
{
|
||||
FocusDeck.SubsetId = ActiveCoachFollowUpBrief.FocusMethodSegmentId;
|
||||
}
|
||||
|
||||
return FocusDeck;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTemplate(
|
||||
const FString& TemplateId,
|
||||
const FString& UserId,
|
||||
|
|
@ -592,6 +777,90 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartRecommendedReview
|
|||
return RunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachRecommendedRun(
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
)
|
||||
{
|
||||
const FHyperTwistTrainingDeck CoachDeck = BuildActiveCoachRecommendedDeck(MaxCases);
|
||||
if (!CoachDeck.IsStructurallyValid())
|
||||
{
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FString UserId = ActiveRunState.Session.UserId.IsEmpty() ? TEXT("local-user") : ActiveRunState.Session.UserId;
|
||||
const FHyperTwistTrainingCoachActionPlan DraftActionPlan =
|
||||
UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
ActiveCoachBrief,
|
||||
FString::Printf(TEXT("coach_plan_%s"), *SessionId),
|
||||
FString()
|
||||
);
|
||||
FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(
|
||||
CoachDeck,
|
||||
UserId,
|
||||
SessionId,
|
||||
ActiveCoachBrief.SuggestedMode
|
||||
);
|
||||
if (!RunState.IsStructurallyValid() || !DraftActionPlan.IsStructurallyValid())
|
||||
{
|
||||
return RunState;
|
||||
}
|
||||
|
||||
ActiveCoachActionPlan = UHyperTwistTrainingRepositoryLibrary::ActivateCoachActionPlan(
|
||||
DraftActionPlan,
|
||||
RunState.Session.TrainingSessionId,
|
||||
RunState.Session.StartedAtUtc
|
||||
);
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachActionPlan
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return RunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartCoachFollowUpRun(
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
)
|
||||
{
|
||||
const FHyperTwistTrainingDeck FollowUpDeck = BuildActiveCoachFollowUpDeck(MaxCases);
|
||||
if (!FollowUpDeck.IsStructurallyValid() || ActiveCoachFollowUpBrief.UserId.IsEmpty())
|
||||
{
|
||||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FHyperTwistCoachBrief FollowUpBrief = ActiveCoachFollowUpBrief;
|
||||
const FHyperTwistTrainingCoachActionPlan DraftActionPlan =
|
||||
UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
|
||||
FollowUpBrief,
|
||||
FString::Printf(TEXT("coach_follow_up_%s"), *SessionId),
|
||||
FString()
|
||||
);
|
||||
FHyperTwistTrainingRunState RunState = StartTrainingRunFromDeck(
|
||||
FollowUpDeck,
|
||||
FollowUpBrief.UserId,
|
||||
SessionId,
|
||||
FollowUpBrief.SuggestedMode
|
||||
);
|
||||
if (!RunState.IsStructurallyValid() || !DraftActionPlan.IsStructurallyValid())
|
||||
{
|
||||
return RunState;
|
||||
}
|
||||
|
||||
ActiveCoachActionPlan = UHyperTwistTrainingRepositoryLibrary::ActivateCoachActionPlan(
|
||||
DraftActionPlan,
|
||||
RunState.Session.TrainingSessionId,
|
||||
RunState.Session.StartedAtUtc
|
||||
);
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachActionPlan
|
||||
);
|
||||
RefreshRepositoryViews();
|
||||
return RunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDeck(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FString& UserId,
|
||||
|
|
@ -601,6 +870,8 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromDe
|
|||
{
|
||||
ActiveReviewPlanState = FHyperTwistTrainingReviewPlanState();
|
||||
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
|
||||
ActiveCoachActionPlan = FHyperTwistTrainingCoachActionPlan();
|
||||
ActiveCoachFollowUpBrief = FHyperTwistCoachBrief();
|
||||
ActiveRunState = UHyperTwistTrainingLibrary::StartTrainingRun(Deck, UserId, SessionId, Mode);
|
||||
bHasActiveRun = ActiveRunState.IsStructurallyValid();
|
||||
RefreshSummary();
|
||||
|
|
@ -821,6 +1092,18 @@ FHyperTwistTrainingSession UHyperTwistTrainingSubsystem::CompleteActiveRun(bool
|
|||
);
|
||||
ActiveReviewFlowStatus = UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState);
|
||||
}
|
||||
if (ActiveCoachActionPlan.IsStructurallyValid())
|
||||
{
|
||||
ActiveCoachActionPlan = UHyperTwistTrainingRepositoryLibrary::FinalizeCoachActionPlan(
|
||||
ActiveCoachActionPlan,
|
||||
ActiveRunState.Session,
|
||||
bAbortSession
|
||||
);
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachActionPlan
|
||||
);
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
return ActiveRunState.Session;
|
||||
}
|
||||
|
|
@ -830,6 +1113,7 @@ void UHyperTwistTrainingSubsystem::ClearActiveRun()
|
|||
ActiveRunState = FHyperTwistTrainingRunState();
|
||||
ActiveRunSummary = FHyperTwistTrainingSessionSummary();
|
||||
ActiveReviewPlanState = FHyperTwistTrainingReviewPlanState();
|
||||
ActiveCoachActionPlan = FHyperTwistTrainingCoachActionPlan();
|
||||
bHasActiveRun = false;
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
|
|
@ -885,6 +1169,14 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
|
||||
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
|
||||
ActiveReplayReviewAnalytics = FHyperTwistReplayReviewAnalytics();
|
||||
ActiveRecognitionReplaySummary = FHyperTwistRecognitionReplaySummary();
|
||||
ActiveCoachSignals.Reset();
|
||||
ActiveCoachBrief = FHyperTwistCoachBrief();
|
||||
ActiveCoachActionPlan = FHyperTwistTrainingCoachActionPlan();
|
||||
ActiveCoachActionPlanSummary = FHyperTwistTrainingCoachActionPlanSummary();
|
||||
ActiveCoachActionPlanOutcomeSummary = FHyperTwistTrainingCoachActionPlanOutcomeSummary();
|
||||
ActiveCoachFollowUpBrief = FHyperTwistCoachBrief();
|
||||
ActiveLearnerMethodSegments.Reset();
|
||||
ActiveLearnerPresets.Reset();
|
||||
ActiveTrainingSessionTemplates.Reset();
|
||||
|
|
@ -945,6 +1237,12 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveReplayReviewAnalytics = UHyperTwistReplayReviewLibrary::DeriveReplayReviewAnalytics(
|
||||
ActiveRunState.ReplayPacket
|
||||
);
|
||||
ActiveRecognitionReplaySummary = UHyperTwistRecognitionReplayLibrary::DeriveRecognitionReplaySummary(
|
||||
ActiveRunState.ReplayPacket
|
||||
);
|
||||
ActiveLearnerMethodSegments = UHyperTwistTrainingRepositoryLibrary::DeriveLearnerMethodSegments(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
|
|
@ -974,6 +1272,45 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveCoachActionPlanSummary = UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanSummary(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState.Session.UserId,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
);
|
||||
ActiveCoachActionPlanOutcomeSummary = ActiveCoachActionPlan.IsStructurallyValid()
|
||||
? UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary(
|
||||
TrainingRepositoryState,
|
||||
ActiveCoachActionPlan,
|
||||
ActiveLearnerDeckStateSummary.ReferenceUtc
|
||||
)
|
||||
: FHyperTwistTrainingCoachActionPlanOutcomeSummary();
|
||||
ActiveCoachSignals = UHyperTwistTrainingCoachLibrary::DeriveCoachSignals(
|
||||
ActiveRunSummary,
|
||||
ActiveDeckScopedStats,
|
||||
ActiveLearnerDeckStateSummary,
|
||||
ActiveLearnerProfile,
|
||||
ActiveCoachMemorySnapshot,
|
||||
ActiveReviewProgramSummary,
|
||||
ActiveReplayReviewAnalytics,
|
||||
ActiveRecognitionReplaySummary
|
||||
);
|
||||
ActiveCoachBrief = UHyperTwistTrainingCoachLibrary::DeriveCoachBrief(
|
||||
ActiveRunSummary,
|
||||
ActiveDeckScopedStats,
|
||||
ActiveLearnerDeckStateSummary,
|
||||
ActiveLearnerProfile,
|
||||
ActiveCoachMemorySnapshot,
|
||||
ActiveReviewProgramSummary,
|
||||
ActiveReplayReviewAnalytics,
|
||||
ActiveRecognitionReplaySummary
|
||||
);
|
||||
ActiveCoachFollowUpBrief = ActiveCoachActionPlanOutcomeSummary.IsStructurallyValid()
|
||||
? UHyperTwistTrainingCoachLibrary::DeriveCoachFollowUpBrief(
|
||||
ActiveCoachBrief,
|
||||
ActiveCoachActionPlan,
|
||||
ActiveCoachActionPlanOutcomeSummary
|
||||
)
|
||||
: FHyperTwistCoachBrief();
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingSubsystem::BuildTrainingSessionTemplatesForUser(
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistCore/HyperTwistCoreTypes.h"
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionReplayLibrary.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayTypes.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayReviewLibrary.h"
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionTypes.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistContractLibrary.generated.h"
|
||||
|
||||
|
|
@ -161,6 +164,30 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingReviewProgramSummary MakeSampleTrainingReviewProgramSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Replay")
|
||||
static FHyperTwistReplayReviewAnalytics MakeSampleReplayReviewAnalytics();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static FHyperTwistRecognitionReplaySummary MakeSampleRecognitionReplaySummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static TArray<FHyperTwistCoachSignal> MakeSampleTrainingCoachSignals();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachBrief MakeSampleTrainingCoachBrief();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlan MakeSampleTrainingCoachActionPlan();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlanSummary MakeSampleTrainingCoachActionPlanSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlanOutcomeSummary MakeSampleTrainingCoachActionPlanOutcomeSummary();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachBrief MakeSampleTrainingCoachFollowUpBrief();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Validation")
|
||||
static bool IsPuzzleStateStructurallyValid(const FHyperTwistPuzzleState& State);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayTypes.h"
|
||||
#include "HyperTwistRecognitionReplayLibrary.generated.h"
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistRecognitionReplayEpisode
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EpisodeId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Stage;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PreviewCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ScanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 CorrectionCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 StartedAtMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 CompletedAtMs = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DurationMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AverageConfidence = -1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float PeakConfidence = -1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float FinalCommitConfidence = -1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FinalCommittedUnit;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCompleted = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bAmbiguous = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistRecognitionReplaySummary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalRecognitionEventCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalPreviewCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalScanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalCommitCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalCorrectionCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 EpisodeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 CompletedEpisodeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AmbiguousEpisodeCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AverageEpisodeDurationMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AverageCommitConfidence = -1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastCommittedUnit;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> CoveredStages;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistRecognitionReplayEpisode> Episodes;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistRecognitionReplayLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static TArray<FHyperTwistRecognitionReplayEpisode> CollectRecognitionReplayEpisodes(const FHyperTwistReplayPacket& Packet);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Recognition")
|
||||
static FHyperTwistRecognitionReplaySummary DeriveRecognitionReplaySummary(const FHyperTwistReplayPacket& Packet);
|
||||
};
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayTypes.h"
|
||||
#include "HyperTwistReplayReviewLibrary.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistReplayReviewAnchorKind : uint8
|
||||
{
|
||||
InspectionStart UMETA(DisplayName = "Inspection Start"),
|
||||
InspectionEnd UMETA(DisplayName = "Inspection End"),
|
||||
TimerStart UMETA(DisplayName = "Timer Start"),
|
||||
FirstMove UMETA(DisplayName = "First Move"),
|
||||
PhaseBoundary UMETA(DisplayName = "Phase Boundary"),
|
||||
RecognitionCommit UMETA(DisplayName = "Recognition Commit"),
|
||||
RecognitionCorrection UMETA(DisplayName = "Recognition Correction"),
|
||||
Pause UMETA(DisplayName = "Pause"),
|
||||
Resume UMETA(DisplayName = "Resume"),
|
||||
DeviceSync UMETA(DisplayName = "Device Sync"),
|
||||
StateSnapshot UMETA(DisplayName = "State Snapshot"),
|
||||
Annotation UMETA(DisplayName = "Annotation"),
|
||||
SolveEnd UMETA(DisplayName = "Solve End")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistReplayReviewAnchor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistReplayReviewAnchorKind AnchorKind = EHyperTwistReplayReviewAnchorKind::Annotation;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 Sequence = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TimeMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Label;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PhaseId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PhaseKind;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bMarksCompletion = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistReplayReviewAnalytics
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 EventCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TimerStartAtMs = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SolveEndAtMs = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DurationAfterTimerStartMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 FirstMoveAtMs = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LastMoveAtMs = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PauseCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalPausedMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LongestIdleGapMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AverageMoveGapMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 UniquePhaseCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PhaseBoundaryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LongestPhaseKind;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LongestPhaseDurationMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 RecognitionPreviewCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 RecognitionCommitCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 RecognitionCorrectionCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AverageRecognitionConfidence = -1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float LowestRecognitionConfidence = -1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float HighestRecognitionConfidence = -1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DeviceSyncCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 StateSnapshotCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AnnotationCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasRecognitionData = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasImportedSource = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistReplayReviewAnchor> ReviewAnchors;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistReplayReviewLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Replay")
|
||||
static TArray<FHyperTwistReplayReviewAnchor> CollectReplayReviewAnchors(const FHyperTwistReplayPacket& Packet);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Replay")
|
||||
static FHyperTwistReplayReviewAnalytics DeriveReplayReviewAnalytics(const FHyperTwistReplayPacket& Packet);
|
||||
};
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayReviewLibrary.h"
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionReplayLibrary.h"
|
||||
#include "HyperTwistTrainingCoachLibrary.generated.h"
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistCoachPriority : uint8
|
||||
{
|
||||
Informational UMETA(DisplayName = "Informational"),
|
||||
Advisory UMETA(DisplayName = "Advisory"),
|
||||
Elevated UMETA(DisplayName = "Elevated"),
|
||||
Critical UMETA(DisplayName = "Critical")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistCoachFocusLane : uint8
|
||||
{
|
||||
ReviewBacklog UMETA(DisplayName = "Review Backlog"),
|
||||
Cadence UMETA(DisplayName = "Cadence"),
|
||||
Recognition UMETA(DisplayName = "Recognition"),
|
||||
Execution UMETA(DisplayName = "Execution"),
|
||||
Accuracy UMETA(DisplayName = "Accuracy"),
|
||||
Variety UMETA(DisplayName = "Variety"),
|
||||
Stability UMETA(DisplayName = "Stability")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistCoachSignal
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SignalId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistCoachFocusLane FocusLane = EHyperTwistCoachFocusLane::Stability;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistCoachPriority Priority = EHyperTwistCoachPriority::Informational;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float Score = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Detail;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SuggestedDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SuggestedMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> SuggestedCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsImmediateAttention = false;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistCoachBrief
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReferenceUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString TrainingSessionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceReviewPlanId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistCoachPriority Priority = EHyperTwistCoachPriority::Informational;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistCoachFocusLane FocusLane = EHyperTwistCoachFocusLane::Stability;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float PriorityScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PrimaryActionLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> FocusCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistCoachSignal> Signals;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsCoachReview = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsCadenceRecovery = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsVarietyRotation = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPreferRecognitionReplayReview = false;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistTrainingCoachLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static TArray<FHyperTwistCoachSignal> DeriveCoachSignals(
|
||||
const FHyperTwistTrainingSessionSummary& SessionSummary,
|
||||
const FHyperTwistTrainingDeckScopedStats& DeckScopedStats,
|
||||
const FHyperTwistTrainingLearnerDeckStateSummary& LearnerDeckStateSummary,
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FHyperTwistTrainingReviewProgramSummary& ReviewProgramSummary,
|
||||
const FHyperTwistReplayReviewAnalytics& ReplayReviewAnalytics,
|
||||
const FHyperTwistRecognitionReplaySummary& RecognitionReplaySummary
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachBrief DeriveCoachBrief(
|
||||
const FHyperTwistTrainingSessionSummary& SessionSummary,
|
||||
const FHyperTwistTrainingDeckScopedStats& DeckScopedStats,
|
||||
const FHyperTwistTrainingLearnerDeckStateSummary& LearnerDeckStateSummary,
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
|
||||
const FHyperTwistTrainingReviewProgramSummary& ReviewProgramSummary,
|
||||
const FHyperTwistReplayReviewAnalytics& ReplayReviewAnalytics,
|
||||
const FHyperTwistRecognitionReplaySummary& RecognitionReplaySummary
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
static FHyperTwistCoachBrief DeriveCoachFollowUpBrief(
|
||||
const FHyperTwistCoachBrief& CurrentBrief,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FHyperTwistTrainingCoachActionPlanOutcomeSummary& OutcomeSummary
|
||||
);
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistTrainingRepositoryLibrary.generated.h"
|
||||
|
||||
|
|
@ -306,6 +307,60 @@ public:
|
|||
const FString& UserId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlan BuildCoachActionPlan(
|
||||
const FHyperTwistCoachBrief& CoachBrief,
|
||||
const FString& PlanId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlan ActivateCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FString& TrainingSessionId,
|
||||
const FString& StartedAtUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlan FinalizeCoachActionPlan(
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FHyperTwistTrainingSession& Session,
|
||||
bool bAbortPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static TArray<FHyperTwistTrainingCoachActionPlan> ListCoachActionPlansForUser(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static bool TryGetCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingRepositoryState UpsertCoachActionPlan(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlanSummary DeriveCoachActionPlanSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FString& UserId,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Coach")
|
||||
static FHyperTwistTrainingCoachActionPlanOutcomeSummary DeriveCoachActionPlanOutcomeSummary(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
const FHyperTwistTrainingCoachActionPlan& ActionPlan,
|
||||
const FString& ReferenceUtc
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingCaseRecommendation MakeCaseRecommendation(
|
||||
const FHyperTwistTrainingCase& TrainingCase,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionReplayLibrary.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayReviewLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistTrainingRuntimeLibrary.generated.h"
|
||||
|
||||
|
|
@ -66,6 +69,30 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCoachMemorySnapshot GetActiveTrainingCoachMemorySnapshot(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistReplayReviewAnalytics GetActiveTrainingReplayReviewAnalytics(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistRecognitionReplaySummary GetActiveTrainingRecognitionReplaySummary(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FHyperTwistCoachSignal> GetActiveTrainingCoachSignals(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistCoachBrief GetActiveTrainingCoachBrief(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCoachActionPlan GetActiveTrainingCoachActionPlan(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCoachActionPlanSummary GetActiveTrainingCoachActionPlanSummary(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCoachActionPlanOutcomeSummary GetActiveTrainingCoachActionPlanOutcomeSummary(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistCoachBrief GetActiveTrainingCoachFollowUpBrief(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FHyperTwistTrainingMethodSegment> GetActiveTrainingLearnerMethodSegments(UObject* WorldContextObject);
|
||||
|
||||
|
|
@ -139,6 +166,13 @@ public:
|
|||
FHyperTwistTrainingReviewPlanState& OutReviewPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryGetStoredTrainingCoachActionPlan(
|
||||
UObject* WorldContextObject,
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool TryGetStoredTrainingSessionTemplate(
|
||||
UObject* WorldContextObject,
|
||||
|
|
@ -252,6 +286,18 @@ public:
|
|||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingDeck BuildActiveCoachRecommendedDeck(
|
||||
UObject* WorldContextObject,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingDeck BuildActiveCoachFollowUpDeck(
|
||||
UObject* WorldContextObject,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRunState StartTrainingRunFromTemplate(
|
||||
UObject* WorldContextObject,
|
||||
|
|
@ -268,6 +314,20 @@ public:
|
|||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRunState StartCoachRecommendedRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRunState StartCoachFollowUpRun(
|
||||
UObject* WorldContextObject,
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRunState StartSampleClassicTrainingRun(
|
||||
UObject* WorldContextObject,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "HyperTwistRecognition/HyperTwistRecognitionReplayLibrary.h"
|
||||
#include "HyperTwistReplay/HyperTwistReplayReviewLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistTrainingSubsystem.generated.h"
|
||||
|
||||
|
|
@ -59,6 +62,30 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingCoachMemorySnapshot GetActiveCoachMemorySnapshot() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistReplayReviewAnalytics GetActiveReplayReviewAnalytics() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistRecognitionReplaySummary GetActiveRecognitionReplaySummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
TArray<FHyperTwistCoachSignal> GetActiveCoachSignals() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistCoachBrief GetActiveCoachBrief() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingCoachActionPlan GetActiveCoachActionPlan() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingCoachActionPlanSummary GetActiveCoachActionPlanSummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingCoachActionPlanOutcomeSummary GetActiveCoachActionPlanOutcomeSummary() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistCoachBrief GetActiveCoachFollowUpBrief() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
TArray<FHyperTwistTrainingMethodSegment> GetActiveLearnerMethodSegments() const;
|
||||
|
||||
|
|
@ -125,6 +152,12 @@ public:
|
|||
FHyperTwistTrainingReviewPlanState& OutReviewPlan
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
bool TryGetStoredCoachActionPlan(
|
||||
const FString& PlanId,
|
||||
FHyperTwistTrainingCoachActionPlan& OutActionPlan
|
||||
) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool TryGetStoredTrainingSessionTemplate(
|
||||
const FString& UserId,
|
||||
|
|
@ -208,6 +241,12 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingDeck BuildActiveReviewDeck(int32 MaxCases) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingDeck BuildActiveCoachRecommendedDeck(int32 MaxCases) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingDeck BuildActiveCoachFollowUpDeck(int32 MaxCases) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState StartTrainingRunFromTemplate(
|
||||
const FString& TemplateId,
|
||||
|
|
@ -222,6 +261,18 @@ public:
|
|||
EHyperTwistTrainingDeliveryMode Mode
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingRunState StartCoachRecommendedRun(
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Coach")
|
||||
FHyperTwistTrainingRunState StartCoachFollowUpRun(
|
||||
const FString& SessionId,
|
||||
int32 MaxCases
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRunState StartTrainingRunFromDeck(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
|
|
@ -342,6 +393,30 @@ private:
|
|||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachMemorySnapshot ActiveCoachMemorySnapshot;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistReplayReviewAnalytics ActiveReplayReviewAnalytics;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistRecognitionReplaySummary ActiveRecognitionReplaySummary;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FHyperTwistCoachSignal> ActiveCoachSignals;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistCoachBrief ActiveCoachBrief;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachActionPlan ActiveCoachActionPlan;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachActionPlanSummary ActiveCoachActionPlanSummary;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachActionPlanOutcomeSummary ActiveCoachActionPlanOutcomeSummary;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistCoachBrief ActiveCoachFollowUpBrief;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FHyperTwistTrainingMethodSegment> ActiveLearnerMethodSegments;
|
||||
|
||||
|
|
|
|||
|
|
@ -2639,6 +2639,328 @@ struct FHyperTwistTrainingReviewProgramSummary
|
|||
}
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingCoachActionPlanState : uint8
|
||||
{
|
||||
Draft UMETA(DisplayName = "Draft"),
|
||||
Active UMETA(DisplayName = "Active"),
|
||||
Completed UMETA(DisplayName = "Completed"),
|
||||
Aborted UMETA(DisplayName = "Aborted")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachActionPlanEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ActionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CaseId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ActionLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float PriorityScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPreferRecognitionReplayReview = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCompleted = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !ActionId.IsEmpty()
|
||||
&& !ActionLabel.IsEmpty()
|
||||
&& (!CaseId.IsEmpty() || !DeckId.IsEmpty() || !MethodSegmentId.IsEmpty());
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachActionPlan
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PlanId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReferenceUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceSessionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceReviewPlanId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusLaneLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PriorityLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float PriorityScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PrimaryActionLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy = EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> FocusCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingCoachActionPlanEntry> Entries;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingCoachActionPlanState PlanState = EHyperTwistTrainingCoachActionPlanState::Draft;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CreatedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString StartedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CompletedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsCoachReview = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsCadenceRecovery = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsVarietyRotation = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPreferRecognitionReplayReview = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !PlanId.IsEmpty()
|
||||
&& !UserId.IsEmpty()
|
||||
&& !Headline.IsEmpty()
|
||||
&& Entries.Num() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachActionPlanSummary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReferenceUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString CurrentPlanId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastCompletedPlanId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DraftPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ActivePlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 CompletedPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AbortedPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 CompletedEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 OpenEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float AveragePriorityScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AverageCompletedPlanDurationMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 BestCompletedPlanDurationMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode DominantSuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DominantFocusLaneLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DominantActionLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float ActionPressureScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasRecognitionBiasedPlans = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsCoachIntervention = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty() && TotalPlanCount > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachActionPlanOutcomeSummary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ReferenceUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PlanId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceSessionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusMethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PrimaryActionLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingCoachActionPlanState PlanState = EHyperTwistTrainingCoachActionPlanState::Draft;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 FocusCaseCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TouchedFocusCaseCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 CompletedFocusCaseCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 UntouchedFocusCaseCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AttemptCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 SuccessCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 FailureCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TimeoutCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DNFCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AbortedCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 ReplayBackedAttemptCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 RecognitionAttemptCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AverageTotalTimeMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 BestTotalTimeMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 AverageRecognitionTimeMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 PlanDurationMs = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float CompletionRate = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
float OutcomeScore = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> FollowUpCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bMetPrimaryGoal = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bNeedsFollowUpPlan = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty() && !PlanId.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingRepositoryState
|
||||
{
|
||||
|
|
@ -2661,6 +2983,9 @@ struct FHyperTwistTrainingRepositoryState
|
|||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingMethodDrillBatch> StoredMethodDrillBatches;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingCoachActionPlan> StoredCoachActionPlans;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
|
|
@ -2722,6 +3047,15 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateMethodDrillBatchCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalCoachActionPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidCoachActionPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateCoachActionPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> RepairActions;
|
||||
|
||||
|
|
@ -2738,6 +3072,8 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|
|||
|| InvalidMethodDrillFavoriteCount > 0
|
||||
|| DuplicateMethodDrillFavoriteCount > 0
|
||||
|| InvalidMethodDrillBatchCount > 0
|
||||
|| DuplicateMethodDrillBatchCount > 0;
|
||||
|| DuplicateMethodDrillBatchCount > 0
|
||||
|| InvalidCoachActionPlanCount > 0
|
||||
|| DuplicateCoachActionPlanCount > 0;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue