Persist template launch provenance in run history
This commit is contained in:
parent
b2ca2957fe
commit
c17bfca008
6 changed files with 467 additions and 76 deletions
|
|
@ -1,5 +1,7 @@
|
|||
#include "HyperTwistTraining/HyperTwistCoachDashboardWidget.h"
|
||||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h"
|
||||
#include "Blueprint/WidgetTree.h"
|
||||
#include "Components/Button.h"
|
||||
#include "Components/ButtonSlot.h"
|
||||
|
|
@ -13,6 +15,74 @@
|
|||
|
||||
namespace HyperTwistCoachDashboardWidgetInternal
|
||||
{
|
||||
FHyperTwistTrainingTemplateLaunchProvenance BuildTemplateLaunchProvenance(
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingTemplateLaunchProvenance Provenance;
|
||||
if (!SessionTemplate.IsStructurallyValid())
|
||||
{
|
||||
return Provenance;
|
||||
}
|
||||
|
||||
Provenance.UserId = SessionTemplate.UserId;
|
||||
Provenance.TemplateId = SessionTemplate.TemplateId;
|
||||
Provenance.SourceTemplateId = SessionTemplate.SourceTemplateId;
|
||||
Provenance.Title = SessionTemplate.Title;
|
||||
Provenance.TemplateKind = SessionTemplate.TemplateKind;
|
||||
Provenance.FocusDeckId = SessionTemplate.FocusDeckId;
|
||||
Provenance.MethodSegmentId = SessionTemplate.MethodSegmentId;
|
||||
Provenance.SuggestedMode = SessionTemplate.SuggestedMode;
|
||||
Provenance.SuggestedSelectionPolicy = SessionTemplate.SuggestedSelectionPolicy;
|
||||
Provenance.RecommendedCaseCount = SessionTemplate.RecommendedCaseCount;
|
||||
Provenance.FocusCaseIds = SessionTemplate.FocusCaseIds;
|
||||
Provenance.GuidanceLabel = SessionTemplate.GuidanceLabel;
|
||||
Provenance.SourceReviewPlanId = SessionTemplate.SourceReviewPlanId;
|
||||
Provenance.bPreferReviewRun = SessionTemplate.bPreferReviewRun;
|
||||
Provenance.bCoachReview = SessionTemplate.bCoachReview;
|
||||
Provenance.bPreferShortReviewSet = SessionTemplate.bPreferShortReviewSet;
|
||||
return Provenance;
|
||||
}
|
||||
|
||||
FHyperTwistCoachDashboardTemplateLaunchHistoryEntry BuildTemplateLaunchHistoryEntry(
|
||||
const FHyperTwistTrainingRunRecord& RunRecord
|
||||
)
|
||||
{
|
||||
FHyperTwistCoachDashboardTemplateLaunchHistoryEntry Entry;
|
||||
if (!RunRecord.IsStructurallyValid() || !RunRecord.TemplateLaunchProvenance.IsStructurallyValid())
|
||||
{
|
||||
return Entry;
|
||||
}
|
||||
|
||||
Entry.RecordedAtUtc = !RunRecord.LastUpdatedAtUtc.IsEmpty()
|
||||
? RunRecord.LastUpdatedAtUtc
|
||||
: (!RunRecord.Summary.LastCompletedAtUtc.IsEmpty()
|
||||
? RunRecord.Summary.LastCompletedAtUtc
|
||||
: RunRecord.Session.EndedAtUtc);
|
||||
Entry.SourceSessionId = RunRecord.Session.TrainingSessionId;
|
||||
Entry.TemplateId = RunRecord.TemplateLaunchProvenance.TemplateId;
|
||||
Entry.TemplateTitle = RunRecord.TemplateLaunchProvenance.Title;
|
||||
Entry.UserId = RunRecord.TemplateLaunchProvenance.UserId;
|
||||
Entry.FocusDeckId = RunRecord.TemplateLaunchProvenance.FocusDeckId;
|
||||
Entry.SourceReviewPlanId = RunRecord.TemplateLaunchProvenance.SourceReviewPlanId;
|
||||
Entry.TemplateKind = RunRecord.TemplateLaunchProvenance.TemplateKind;
|
||||
Entry.SessionState = RunRecord.Summary.SessionState;
|
||||
Entry.SuggestedMode = RunRecord.TemplateLaunchProvenance.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = RunRecord.TemplateLaunchProvenance.SuggestedSelectionPolicy;
|
||||
Entry.RecommendedCaseCount = RunRecord.TemplateLaunchProvenance.RecommendedCaseCount;
|
||||
Entry.FocusCaseCount = RunRecord.TemplateLaunchProvenance.FocusCaseIds.Num();
|
||||
Entry.CompletedAttemptCount = RunRecord.Summary.CompletedAttemptCount;
|
||||
Entry.TotalMistakes = RunRecord.Summary.TotalMistakes;
|
||||
Entry.SuccessRate = RunRecord.Summary.SuccessRate;
|
||||
Entry.CompletedAtUtc = !RunRecord.Summary.LastCompletedAtUtc.IsEmpty()
|
||||
? RunRecord.Summary.LastCompletedAtUtc
|
||||
: RunRecord.Session.EndedAtUtc;
|
||||
Entry.bPreferReviewRun = RunRecord.TemplateLaunchProvenance.bPreferReviewRun;
|
||||
Entry.bCoachReview = RunRecord.TemplateLaunchProvenance.bCoachReview;
|
||||
Entry.bPreferShortReviewSet = RunRecord.TemplateLaunchProvenance.bPreferShortReviewSet;
|
||||
return Entry;
|
||||
}
|
||||
|
||||
struct FClosureRecoveryDashboardBias
|
||||
{
|
||||
int32 ContinuationBias = 0;
|
||||
|
|
@ -3697,7 +3767,7 @@ void UHyperTwistCoachDashboardWidget::ClearRetainedRunRecap()
|
|||
RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
bHasRetainedTemplateLaunch = false;
|
||||
RetainedTemplateLaunchSessionId.Reset();
|
||||
RetainedTemplateLaunchTemplate = FHyperTwistTrainingSessionTemplate();
|
||||
RetainedTemplateLaunchProvenance = FHyperTwistTrainingTemplateLaunchProvenance();
|
||||
bHasRetainedGuidanceComparison = false;
|
||||
RetainedGuidanceComparisonSessionId.Reset();
|
||||
RetainedGuidanceComparisonLane = EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
|
|
@ -6653,6 +6723,13 @@ void UHyperTwistCoachDashboardWidget::SyncSelectedAttempt()
|
|||
|
||||
void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap()
|
||||
{
|
||||
if (CachedRunState.Session.IsStructurallyValid()
|
||||
&& CachedRunState.TemplateLaunchProvenance.IsStructurallyValid())
|
||||
{
|
||||
ActiveTemplateLaunchSessionId = CachedRunState.Session.TrainingSessionId;
|
||||
ActiveTemplateLaunchProvenance = CachedRunState.TemplateLaunchProvenance;
|
||||
}
|
||||
|
||||
if (CachedRunState.Session.IsStructurallyValid()
|
||||
&& (CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Completed
|
||||
|| CachedRunState.Session.SessionState == EHyperTwistTrainingSessionState::Aborted))
|
||||
|
|
@ -6665,12 +6742,11 @@ void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap()
|
|||
RetainedRunRecapMode = CachedRunState.Session.Mode;
|
||||
if (!ActiveTemplateLaunchSessionId.IsEmpty()
|
||||
&& ActiveTemplateLaunchSessionId == RetainedRunRecapSessionId
|
||||
&& ActiveTemplateLaunchTemplate.IsStructurallyValid())
|
||||
&& ActiveTemplateLaunchProvenance.IsStructurallyValid())
|
||||
{
|
||||
bHasRetainedTemplateLaunch = true;
|
||||
RetainedTemplateLaunchSessionId = ActiveTemplateLaunchSessionId;
|
||||
RetainedTemplateLaunchTemplate = ActiveTemplateLaunchTemplate;
|
||||
RecordTemplateLaunchHistoryEntry(RetainedRunRecapSummary);
|
||||
RetainedTemplateLaunchProvenance = ActiveTemplateLaunchProvenance;
|
||||
}
|
||||
if (!ActiveGuidanceLaunchSessionId.IsEmpty()
|
||||
&& ActiveGuidanceLaunchSessionId == RetainedRunRecapSessionId
|
||||
|
|
@ -6698,6 +6774,8 @@ void UHyperTwistCoachDashboardWidget::SyncRetainedRunRecap()
|
|||
ActiveMethodDrillFollowUpLaunchPacketStanceLabel.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
SyncTemplateLaunchHistoryFromRepository();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::SyncSelectedGuidancePreviewCase()
|
||||
|
|
@ -6939,7 +7017,8 @@ void UHyperTwistCoachDashboardWidget::CaptureActiveTemplateLaunchContext(
|
|||
)
|
||||
{
|
||||
ActiveTemplateLaunchSessionId = SessionId;
|
||||
ActiveTemplateLaunchTemplate = SessionTemplate;
|
||||
ActiveTemplateLaunchProvenance =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchProvenance(SessionTemplate);
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::CaptureActiveMethodDrillFollowUpLaunchContext(
|
||||
|
|
@ -9858,63 +9937,100 @@ void UHyperTwistCoachDashboardWidget::RecordClosureRecoverySnapshot()
|
|||
SelectedClosureRecoveryIndex = ClosureRecoveryHistoryEntries.Num() - 1;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::RecordTemplateLaunchHistoryEntry(
|
||||
const FHyperTwistTrainingSessionSummary& CompletedRunRecap
|
||||
)
|
||||
void UHyperTwistCoachDashboardWidget::SyncTemplateLaunchHistoryFromRepository()
|
||||
{
|
||||
if (CompletedRunRecap.TrainingSessionId.IsEmpty()
|
||||
|| !ActiveTemplateLaunchTemplate.IsStructurallyValid()
|
||||
|| ActiveTemplateLaunchSessionId != CompletedRunRecap.TrainingSessionId)
|
||||
const FString PreviouslySelectedSessionId =
|
||||
SelectedTemplateLaunchHistoryIndex >= 0
|
||||
&& SelectedTemplateLaunchHistoryIndex < TemplateLaunchHistoryEntries.Num()
|
||||
? TemplateLaunchHistoryEntries[SelectedTemplateLaunchHistoryIndex].SourceSessionId
|
||||
: FString();
|
||||
|
||||
TemplateLaunchHistoryEntries.Reset();
|
||||
SelectedTemplateLaunchHistoryIndex = INDEX_NONE;
|
||||
|
||||
FString HistoryUserId = !DefaultUserId.IsEmpty()
|
||||
? DefaultUserId
|
||||
: RetainedRunRecapUserId;
|
||||
if (HistoryUserId.IsEmpty() && CachedRunState.Session.IsStructurallyValid())
|
||||
{
|
||||
HistoryUserId = CachedRunState.Session.UserId;
|
||||
}
|
||||
if (HistoryUserId.IsEmpty() && ActiveTemplateLaunchProvenance.IsStructurallyValid())
|
||||
{
|
||||
HistoryUserId = ActiveTemplateLaunchProvenance.UserId;
|
||||
}
|
||||
if (HistoryUserId.IsEmpty() && RetainedTemplateLaunchProvenance.IsStructurallyValid())
|
||||
{
|
||||
HistoryUserId = RetainedTemplateLaunchProvenance.UserId;
|
||||
}
|
||||
if (HistoryUserId.IsEmpty() && CachedDefaultUserTrainingSessionTemplates.Num() > 0)
|
||||
{
|
||||
HistoryUserId = CachedDefaultUserTrainingSessionTemplates[0].UserId;
|
||||
}
|
||||
if (HistoryUserId.IsEmpty() && CachedActiveTrainingSessionTemplates.Num() > 0)
|
||||
{
|
||||
HistoryUserId = CachedActiveTrainingSessionTemplates[0].UserId;
|
||||
}
|
||||
if (HistoryUserId.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FHyperTwistCoachDashboardTemplateLaunchHistoryEntry Entry;
|
||||
Entry.RecordedAtUtc = FDateTime::UtcNow().ToIso8601();
|
||||
Entry.SourceSessionId = CompletedRunRecap.TrainingSessionId;
|
||||
Entry.TemplateId = ActiveTemplateLaunchTemplate.TemplateId;
|
||||
Entry.TemplateTitle = ActiveTemplateLaunchTemplate.Title;
|
||||
Entry.UserId = ActiveTemplateLaunchTemplate.UserId;
|
||||
Entry.FocusDeckId = ActiveTemplateLaunchTemplate.FocusDeckId;
|
||||
Entry.SourceReviewPlanId = ActiveTemplateLaunchTemplate.SourceReviewPlanId;
|
||||
Entry.TemplateKind = ActiveTemplateLaunchTemplate.TemplateKind;
|
||||
Entry.SessionState = CompletedRunRecap.SessionState;
|
||||
Entry.SuggestedMode = ActiveTemplateLaunchTemplate.SuggestedMode;
|
||||
Entry.SuggestedSelectionPolicy = ActiveTemplateLaunchTemplate.SuggestedSelectionPolicy;
|
||||
Entry.RecommendedCaseCount = ActiveTemplateLaunchTemplate.RecommendedCaseCount;
|
||||
Entry.FocusCaseCount = ActiveTemplateLaunchTemplate.FocusCaseIds.Num();
|
||||
Entry.CompletedAttemptCount = CompletedRunRecap.CompletedAttemptCount;
|
||||
Entry.TotalMistakes = CompletedRunRecap.TotalMistakes;
|
||||
Entry.SuccessRate = CompletedRunRecap.SuccessRate;
|
||||
Entry.CompletedAtUtc = CompletedRunRecap.LastCompletedAtUtc;
|
||||
Entry.bPreferReviewRun = ActiveTemplateLaunchTemplate.bPreferReviewRun;
|
||||
Entry.bCoachReview = ActiveTemplateLaunchTemplate.bCoachReview;
|
||||
Entry.bPreferShortReviewSet = ActiveTemplateLaunchTemplate.bPreferShortReviewSet;
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool bAlreadyRecorded = TemplateLaunchHistoryEntries.ContainsByPredicate(
|
||||
[&Entry](const FHyperTwistCoachDashboardTemplateLaunchHistoryEntry& Candidate)
|
||||
{
|
||||
return Candidate.SourceSessionId == Entry.SourceSessionId;
|
||||
}
|
||||
);
|
||||
if (bAlreadyRecorded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TemplateLaunchHistoryEntries.Add(Entry);
|
||||
const FHyperTwistTrainingRepositoryState RepositoryState =
|
||||
UHyperTwistTrainingRuntimeLibrary::GetTrainingRepositoryState(this);
|
||||
const TArray<FHyperTwistTrainingRunRecord> UserRunRecords =
|
||||
UHyperTwistTrainingRepositoryLibrary::ListRunRecordsForDeck(
|
||||
RepositoryState,
|
||||
HistoryUserId,
|
||||
FString());
|
||||
TArray<FHyperTwistCoachDashboardTemplateLaunchHistoryEntry> ReverseOrderedEntries;
|
||||
constexpr int32 MaxRetainedTemplateLaunchHistoryEntries = 16;
|
||||
if (TemplateLaunchHistoryEntries.Num() > MaxRetainedTemplateLaunchHistoryEntries)
|
||||
ReverseOrderedEntries.Reserve(MaxRetainedTemplateLaunchHistoryEntries);
|
||||
for (const FHyperTwistTrainingRunRecord& RunRecord : UserRunRecords)
|
||||
{
|
||||
const int32 ExcessCount =
|
||||
TemplateLaunchHistoryEntries.Num() - MaxRetainedTemplateLaunchHistoryEntries;
|
||||
TemplateLaunchHistoryEntries.RemoveAt(0, ExcessCount, EAllowShrinking::No);
|
||||
if (!RunRecord.IsStructurallyValid()
|
||||
|| !RunRecord.TemplateLaunchProvenance.IsStructurallyValid()
|
||||
|| (RunRecord.Summary.SessionState != EHyperTwistTrainingSessionState::Completed
|
||||
&& RunRecord.Summary.SessionState != EHyperTwistTrainingSessionState::Aborted))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FHyperTwistCoachDashboardTemplateLaunchHistoryEntry Entry =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchHistoryEntry(RunRecord);
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ReverseOrderedEntries.Add(MoveTemp(Entry));
|
||||
if (ReverseOrderedEntries.Num() >= MaxRetainedTemplateLaunchHistoryEntries)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
SelectedTemplateLaunchHistoryIndex = TemplateLaunchHistoryEntries.Num() - 1;
|
||||
|
||||
for (int32 Index = ReverseOrderedEntries.Num() - 1; Index >= 0; --Index)
|
||||
{
|
||||
TemplateLaunchHistoryEntries.Add(ReverseOrderedEntries[Index]);
|
||||
}
|
||||
|
||||
if (!PreviouslySelectedSessionId.IsEmpty())
|
||||
{
|
||||
const int32 MatchingIndex = TemplateLaunchHistoryEntries.IndexOfByPredicate(
|
||||
[&PreviouslySelectedSessionId](
|
||||
const FHyperTwistCoachDashboardTemplateLaunchHistoryEntry& Candidate)
|
||||
{
|
||||
return Candidate.SourceSessionId == PreviouslySelectedSessionId;
|
||||
}
|
||||
);
|
||||
if (MatchingIndex >= 0)
|
||||
{
|
||||
SelectedTemplateLaunchHistoryIndex = MatchingIndex;
|
||||
}
|
||||
}
|
||||
|
||||
SyncSelectedTemplateLaunchHistoryEntry();
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::RecordMethodDrillFollowUpHistoryEntry(
|
||||
|
|
@ -11083,18 +11199,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
&& DisplayedRunRecap != nullptr
|
||||
&& !ActiveTemplateLaunchSessionId.IsEmpty()
|
||||
&& DisplayedRunRecap->TrainingSessionId == ActiveTemplateLaunchSessionId
|
||||
&& ActiveTemplateLaunchTemplate.IsStructurallyValid();
|
||||
&& ActiveTemplateLaunchProvenance.IsStructurallyValid();
|
||||
const bool bHasDisplayedRetainedTemplateLaunch =
|
||||
!bHasCurrentRunRecap
|
||||
&& bHasRetainedTemplateLaunch
|
||||
&& DisplayedRunRecap != nullptr
|
||||
&& !RetainedTemplateLaunchSessionId.IsEmpty()
|
||||
&& DisplayedRunRecap->TrainingSessionId == RetainedTemplateLaunchSessionId
|
||||
&& RetainedTemplateLaunchTemplate.IsStructurallyValid();
|
||||
const FHyperTwistTrainingSessionTemplate* DisplayedTemplateLaunch =
|
||||
&& RetainedTemplateLaunchProvenance.IsStructurallyValid();
|
||||
const FHyperTwistTrainingTemplateLaunchProvenance* DisplayedTemplateLaunch =
|
||||
bHasActiveTemplateLaunchForDisplayedRun
|
||||
? &ActiveTemplateLaunchTemplate
|
||||
: (bHasDisplayedRetainedTemplateLaunch ? &RetainedTemplateLaunchTemplate : nullptr);
|
||||
? &ActiveTemplateLaunchProvenance
|
||||
: (bHasDisplayedRetainedTemplateLaunch ? &RetainedTemplateLaunchProvenance : nullptr);
|
||||
const bool bDisplayedTemplateLaunchUsesReviewLane =
|
||||
DisplayedTemplateLaunch != nullptr
|
||||
&& (DisplayedTemplateLaunch->bPreferReviewRun
|
||||
|
|
|
|||
|
|
@ -6212,8 +6212,9 @@ FHyperTwistTrainingRunRecord UHyperTwistTrainingRepositoryLibrary::BuildRunRecor
|
|||
RunState.Attempts,
|
||||
RunState.ImportedRuntimeSelectionState
|
||||
);
|
||||
RunRecord.ImportedRuntimeSelectionState = RunState.ImportedRuntimeSelectionState;
|
||||
RunRecord.ImportedGeneratedModeLaunchRequest = RunState.ImportedGeneratedModeLaunchRequest;
|
||||
RunRecord.ImportedRuntimeSelectionState = RunState.ImportedRuntimeSelectionState;
|
||||
RunRecord.ImportedGeneratedModeLaunchRequest = RunState.ImportedGeneratedModeLaunchRequest;
|
||||
RunRecord.TemplateLaunchProvenance = RunState.TemplateLaunchProvenance;
|
||||
RunRecord.Attempts = RunState.Attempts;
|
||||
RunRecord.RemainingCaseIds = RunState.RemainingCaseIds;
|
||||
RunRecord.ReplayId = RunState.ReplayPacket.ReplayId;
|
||||
|
|
|
|||
|
|
@ -188,6 +188,60 @@ namespace HyperTwistTrainingSubsystemInternal
|
|||
return FString();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTemplateLaunchProvenance BuildTemplateLaunchProvenance(
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingTemplateLaunchProvenance Provenance;
|
||||
if (!SessionTemplate.IsStructurallyValid())
|
||||
{
|
||||
return Provenance;
|
||||
}
|
||||
|
||||
Provenance.UserId = SessionTemplate.UserId;
|
||||
Provenance.TemplateId = SessionTemplate.TemplateId;
|
||||
Provenance.SourceTemplateId = SessionTemplate.SourceTemplateId;
|
||||
Provenance.Title = SessionTemplate.Title;
|
||||
Provenance.TemplateKind = SessionTemplate.TemplateKind;
|
||||
Provenance.FocusDeckId = SessionTemplate.FocusDeckId;
|
||||
Provenance.MethodSegmentId = SessionTemplate.MethodSegmentId;
|
||||
Provenance.SuggestedMode = SessionTemplate.SuggestedMode;
|
||||
Provenance.SuggestedSelectionPolicy = SessionTemplate.SuggestedSelectionPolicy;
|
||||
Provenance.RecommendedCaseCount = SessionTemplate.RecommendedCaseCount;
|
||||
Provenance.FocusCaseIds = SessionTemplate.FocusCaseIds;
|
||||
Provenance.GuidanceLabel = SessionTemplate.GuidanceLabel;
|
||||
Provenance.SourceReviewPlanId = SessionTemplate.SourceReviewPlanId;
|
||||
Provenance.bPreferReviewRun = SessionTemplate.bPreferReviewRun;
|
||||
Provenance.bCoachReview = SessionTemplate.bCoachReview;
|
||||
Provenance.bPreferShortReviewSet = SessionTemplate.bPreferShortReviewSet;
|
||||
return Provenance;
|
||||
}
|
||||
|
||||
void PersistActiveRunTemplateLaunchProvenance(
|
||||
FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
FHyperTwistTrainingRunState& ActiveRunState,
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate
|
||||
)
|
||||
{
|
||||
if (!ActiveRunState.IsStructurallyValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingTemplateLaunchProvenance Provenance =
|
||||
BuildTemplateLaunchProvenance(SessionTemplate);
|
||||
if (!Provenance.IsStructurallyValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ActiveRunState.TemplateLaunchProvenance = Provenance;
|
||||
RepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
|
||||
RepositoryState,
|
||||
ActiveRunState
|
||||
);
|
||||
}
|
||||
|
||||
FString FormatLaunchBudgetLabel(const int32 CaseCount)
|
||||
{
|
||||
return FString::Printf(TEXT("Launch cases: %d"), CaseCount);
|
||||
|
|
@ -3616,11 +3670,22 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTe
|
|||
ActiveReviewPlanState,
|
||||
ResolvedUserId))
|
||||
{
|
||||
return StartRecommendedReviewRun(
|
||||
FHyperTwistTrainingRunState RunState = StartRecommendedReviewRun(
|
||||
SessionId,
|
||||
MaterializedDeck.Cases.Num(),
|
||||
MatchingTemplate->SuggestedMode
|
||||
);
|
||||
HyperTwistTrainingSubsystemInternal::PersistActiveRunTemplateLaunchProvenance(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState,
|
||||
*MatchingTemplate
|
||||
);
|
||||
if (ActiveRunState.IsStructurallyValid())
|
||||
{
|
||||
RunState = ActiveRunState;
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
return RunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck SourceDeck;
|
||||
|
|
@ -3643,10 +3708,23 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartTrainingRunFromTe
|
|||
SessionId,
|
||||
MatchingTemplate->SuggestedMode
|
||||
);
|
||||
HyperTwistTrainingSubsystemInternal::PersistActiveRunTemplateLaunchProvenance(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState,
|
||||
*MatchingTemplate
|
||||
);
|
||||
if (ActiveRunState.IsStructurallyValid())
|
||||
{
|
||||
RunState = ActiveRunState;
|
||||
}
|
||||
if (!RunState.IsStructurallyValid()
|
||||
|| !MatchingTemplate->bPreferReviewRun
|
||||
|| !bPreparedReviewTemplateLaunch)
|
||||
{
|
||||
if (RunState.IsStructurallyValid())
|
||||
{
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
return RunState;
|
||||
}
|
||||
|
||||
|
|
@ -3713,11 +3791,22 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartActiveMethodDrill
|
|||
ActiveReviewPlanState,
|
||||
ActiveMethodDrillRunState.UserId))
|
||||
{
|
||||
return StartRecommendedReviewRun(
|
||||
FHyperTwistTrainingRunState RunState = StartRecommendedReviewRun(
|
||||
StructuredSessionId,
|
||||
FollowUpDeck.Cases.Num(),
|
||||
ActiveMethodDrillFollowUpTemplate.SuggestedMode
|
||||
);
|
||||
HyperTwistTrainingSubsystemInternal::PersistActiveRunTemplateLaunchProvenance(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState,
|
||||
ActiveMethodDrillFollowUpTemplate
|
||||
);
|
||||
if (ActiveRunState.IsStructurallyValid())
|
||||
{
|
||||
RunState = ActiveRunState;
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
return RunState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeck SourceDeck;
|
||||
|
|
@ -3739,10 +3828,23 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartActiveMethodDrill
|
|||
StructuredSessionId,
|
||||
ActiveMethodDrillFollowUpTemplate.SuggestedMode
|
||||
);
|
||||
HyperTwistTrainingSubsystemInternal::PersistActiveRunTemplateLaunchProvenance(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState,
|
||||
ActiveMethodDrillFollowUpTemplate
|
||||
);
|
||||
if (ActiveRunState.IsStructurallyValid())
|
||||
{
|
||||
RunState = ActiveRunState;
|
||||
}
|
||||
if (!RunState.IsStructurallyValid()
|
||||
|| !ActiveMethodDrillFollowUpTemplate.bPreferReviewRun
|
||||
|| !bPreparedReviewTemplateLaunch)
|
||||
{
|
||||
if (RunState.IsStructurallyValid())
|
||||
{
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
return RunState;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1649,7 +1649,7 @@ private:
|
|||
);
|
||||
void RecordQueueSuppressionSnapshot();
|
||||
void RecordClosureRecoverySnapshot();
|
||||
void RecordTemplateLaunchHistoryEntry(const FHyperTwistTrainingSessionSummary& CompletedRunRecap);
|
||||
void SyncTemplateLaunchHistoryFromRepository();
|
||||
void RecordMethodDrillFollowUpHistoryEntry(const FHyperTwistTrainingSessionSummary& CompletedRunRecap);
|
||||
void RecordMethodDrillRecoveryMemoryHistorySnapshot(
|
||||
const FHyperTwistCoachDashboardQueueRecoveryWeighting& QueueRecoveryWeighting,
|
||||
|
|
@ -1696,10 +1696,10 @@ private:
|
|||
FString RetainedRunRecapSessionId;
|
||||
EHyperTwistTrainingDeliveryMode RetainedRunRecapMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
FString ActiveTemplateLaunchSessionId;
|
||||
FHyperTwistTrainingSessionTemplate ActiveTemplateLaunchTemplate;
|
||||
FHyperTwistTrainingTemplateLaunchProvenance ActiveTemplateLaunchProvenance;
|
||||
bool bHasRetainedTemplateLaunch = false;
|
||||
FString RetainedTemplateLaunchSessionId;
|
||||
FHyperTwistTrainingSessionTemplate RetainedTemplateLaunchTemplate;
|
||||
FHyperTwistTrainingTemplateLaunchProvenance RetainedTemplateLaunchProvenance;
|
||||
FString ActiveGuidanceLaunchSessionId;
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane ActiveGuidanceLaunchLane =
|
||||
EHyperTwistCoachDashboardGuidancePreviewLane::None;
|
||||
|
|
|
|||
|
|
@ -1035,6 +1035,77 @@ struct FHyperTwistTrainingAttemptResolution
|
|||
}
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingSessionTemplateKind : uint8
|
||||
{
|
||||
ReviewPlan UMETA(DisplayName = "Review Plan"),
|
||||
Recovery UMETA(DisplayName = "Recovery"),
|
||||
Accuracy UMETA(DisplayName = "Accuracy"),
|
||||
Speed UMETA(DisplayName = "Speed"),
|
||||
Coach UMETA(DisplayName = "Coach")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingTemplateLaunchProvenance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString TemplateId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceTemplateId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Title;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSessionTemplateKind TemplateKind =
|
||||
EHyperTwistTrainingSessionTemplateKind::Recovery;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FocusDeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MethodSegmentId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingDeliveryMode SuggestedMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingSelectionPolicy SuggestedSelectionPolicy =
|
||||
EHyperTwistTrainingSelectionPolicy::Weighted;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 RecommendedCaseCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> FocusCaseIds;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString GuidanceLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SourceReviewPlanId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPreferReviewRun = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bCoachReview = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPreferShortReviewSet = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !TemplateId.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingRunState
|
||||
{
|
||||
|
|
@ -1070,6 +1141,9 @@ struct FHyperTwistTrainingRunState
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingImportedGeneratedModeLaunchRequest ImportedGeneratedModeLaunchRequest;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingTemplateLaunchProvenance TemplateLaunchProvenance;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return ActiveDeck.IsStructurallyValid()
|
||||
|
|
@ -1390,6 +1464,9 @@ struct FHyperTwistTrainingRunRecord
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingImportedGeneratedModeLaunchRequest ImportedGeneratedModeLaunchRequest;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingTemplateLaunchProvenance TemplateLaunchProvenance;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return Session.IsStructurallyValid() && Summary.TrainingSessionId == Session.TrainingSessionId;
|
||||
|
|
@ -3567,16 +3644,6 @@ struct FHyperTwistTrainingLearnerPreset
|
|||
bool bPreferShortReviewSet = false;
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingSessionTemplateKind : uint8
|
||||
{
|
||||
ReviewPlan UMETA(DisplayName = "Review Plan"),
|
||||
Recovery UMETA(DisplayName = "Recovery"),
|
||||
Accuracy UMETA(DisplayName = "Accuracy"),
|
||||
Speed UMETA(DisplayName = "Speed"),
|
||||
Coach UMETA(DisplayName = "Coach")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingSessionTemplate
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
# HyperTwist Phase 4 template launch repository provenance packet
|
||||
|
||||
Created on `2026-05-06`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded Phase `4` repository-backed template provenance slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet moves template-launch provenance out of dashboard-only retained state and into repository-backed run state and run history.
|
||||
|
||||
The open tasks were:
|
||||
|
||||
- persist template launch identity into the active run state itself
|
||||
- carry that provenance into stored run records
|
||||
- let the dashboard rebuild template-launch recap/history from repository state after widget refresh or rebuild
|
||||
|
||||
It is not:
|
||||
|
||||
- a repository schema migration packet
|
||||
- a template browser or editor packet
|
||||
- a coach weighting rewrite packet
|
||||
- a broader run-history redesign
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- add a compact persisted template-launch provenance struct in training types
|
||||
- store that provenance on `FHyperTwistTrainingRunState` and `FHyperTwistTrainingRunRecord`
|
||||
- annotate template-backed subsystem launches so direct template runs and review-plan resume runs both persist provenance
|
||||
- copy persisted provenance into run records during repository recording
|
||||
- rebuild dashboard template-launch recap/history from repository run records instead of widget-only retained snapshots
|
||||
|
||||
Out of scope:
|
||||
|
||||
- changing dashboard template ranking
|
||||
- changing review-plan scoring or recommendation rules
|
||||
- widening into analytics consumers beyond existing dashboard history/recap
|
||||
- changing non-template run persistence
|
||||
|
||||
## Why this was the right next packet
|
||||
|
||||
The previous packet made template launch provenance visible on the dashboard, but only as retained widget state.
|
||||
|
||||
That left an obvious continuity gap:
|
||||
|
||||
- widget rebuilds could discard template-launch identity
|
||||
- dashboard history depended on the current widget lifetime instead of durable run records
|
||||
- later coach weighting or analytics work still had no repository-backed template launch provenance to read
|
||||
|
||||
So the next honest move was:
|
||||
|
||||
- persist the launch provenance at the run-state layer
|
||||
- let run-record recording carry it forward automatically
|
||||
- and switch the dashboard history surface to read from repository state instead of only its own memory
|
||||
|
||||
## What landed
|
||||
|
||||
Primary code changes:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
|
||||
- added `FHyperTwistTrainingTemplateLaunchProvenance`
|
||||
- added persisted template-launch provenance fields to `FHyperTwistTrainingRunState` and `FHyperTwistTrainingRunRecord`
|
||||
- moved `EHyperTwistTrainingSessionTemplateKind` earlier so the persisted provenance struct can use it
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp`
|
||||
- `BuildRunRecord(...)` now copies template-launch provenance from active run state into stored run records
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp`
|
||||
- added shared template-to-provenance projection helpers
|
||||
- `StartTrainingRunFromTemplate(...)` now persists template-launch provenance for direct launches and stored review-plan resume launches
|
||||
- `StartActiveMethodDrillFollowUpRun(...)` now persists the same provenance for template-backed follow-up launches
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h`
|
||||
- switched active/retained template launch cache members to the persisted provenance shape
|
||||
- replaced widget-only template-history recording with repository-sync plumbing
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp`
|
||||
- rehydrates active template-launch context from `CachedRunState.TemplateLaunchProvenance`
|
||||
- rebuilds `[Template Launch History]` from stored run records for the selected user
|
||||
- keeps recap rendering on the same provenance fields, now backed by repository state
|
||||
|
||||
## Product effect
|
||||
|
||||
Template-backed run provenance now survives beyond a single dashboard widget instance:
|
||||
|
||||
- active and completed template-backed runs carry `TemplateId`, `TemplateKind`, and `SourceReviewPlanId` in repository-backed run state/history
|
||||
- dashboard template-launch history can be rebuilt after widget refresh or reconstruction
|
||||
- later analytics or coach-weighting work can read template-launch provenance from stored run records instead of depending on transient UI memory
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- template-backed launches persist provenance into active run state
|
||||
- stored run records retain that provenance
|
||||
- dashboard recap can rehydrate template launch context from cached repository-backed run state
|
||||
- dashboard template-launch history rebuilds from repository run records
|
||||
- full product build succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
|
||||
2. launch a template-backed run and confirm the active run state carries template provenance
|
||||
3. complete or abort the run and confirm the dashboard template-launch history still appears after widget refresh/rebuild
|
||||
4. confirm stored run records include template provenance for template-backed launches
|
||||
|
||||
That is the packet.
|
||||
Loading…
Add table
Reference in a new issue