Persist dashboard template selector provenance
This commit is contained in:
parent
a6a784862c
commit
bc6d234a14
6 changed files with 307 additions and 25 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
|
||||
#include "Blueprint/WidgetTree.h"
|
||||
#include "Components/Button.h"
|
||||
#include "Components/ButtonSlot.h"
|
||||
|
|
@ -80,6 +81,7 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
Entry.bPreferReviewRun = RunRecord.TemplateLaunchProvenance.bPreferReviewRun;
|
||||
Entry.bCoachReview = RunRecord.TemplateLaunchProvenance.bCoachReview;
|
||||
Entry.bPreferShortReviewSet = RunRecord.TemplateLaunchProvenance.bPreferShortReviewSet;
|
||||
Entry.SelectionProvenance = RunRecord.TemplateLaunchProvenance.SelectionProvenance;
|
||||
return Entry;
|
||||
}
|
||||
|
||||
|
|
@ -174,20 +176,70 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
);
|
||||
}
|
||||
|
||||
FString BuildTemplateLaunchAnalyticsTieBreakRationale(
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance BuildTemplateLaunchAnalyticsTieBreakSelectionProvenance(
|
||||
const FHyperTwistTrainingSessionTemplate& SelectedTemplate,
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* SelectedStats,
|
||||
const FHyperTwistTrainingSessionTemplate& PeerTemplate,
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* PeerStats
|
||||
)
|
||||
{
|
||||
return FString::Printf(
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance Provenance;
|
||||
Provenance.SelectorSourceLabel = TEXT("CoachDashboard");
|
||||
Provenance.ReasonClass =
|
||||
EHyperTwistTrainingTemplateLaunchSelectionReasonClass::AnalyticsTieBreak;
|
||||
Provenance.PeerTemplateId = PeerTemplate.TemplateId;
|
||||
Provenance.PeerTemplateTitle = ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate);
|
||||
Provenance.DecisionSummaryLine = FString::Printf(
|
||||
TEXT("Selector: tied with %s on primary dashboard priority; analytics preferred %s on stored outcome signal: chosen %s | peer %s."),
|
||||
*ResolveDashboardTemplateLaunchDisplayTitle(PeerTemplate),
|
||||
*ResolveDashboardTemplateLaunchDisplayTitle(SelectedTemplate),
|
||||
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(SelectedStats),
|
||||
*BuildTemplateLaunchAnalyticsTieBreakSignalFragment(PeerStats)
|
||||
);
|
||||
return Provenance;
|
||||
}
|
||||
|
||||
FString ResolveTemplateLaunchSelectionReasonClassLabel(
|
||||
const EHyperTwistTrainingTemplateLaunchSelectionReasonClass ReasonClass
|
||||
)
|
||||
{
|
||||
switch (ReasonClass)
|
||||
{
|
||||
case EHyperTwistTrainingTemplateLaunchSelectionReasonClass::PrimaryDashboardPriority:
|
||||
return TEXT("Primary Dashboard Priority");
|
||||
case EHyperTwistTrainingTemplateLaunchSelectionReasonClass::AnalyticsTieBreak:
|
||||
return TEXT("Analytics Tie Break");
|
||||
case EHyperTwistTrainingTemplateLaunchSelectionReasonClass::None:
|
||||
default:
|
||||
return TEXT("None");
|
||||
}
|
||||
}
|
||||
|
||||
FString BuildTemplateLaunchSelectionDetailFragment(
|
||||
const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance
|
||||
)
|
||||
{
|
||||
if (SelectionProvenance == nullptr || !SelectionProvenance->IsStructurallyValid())
|
||||
{
|
||||
return FString();
|
||||
}
|
||||
|
||||
const FString PeerTemplateLabel = !SelectionProvenance->PeerTemplateTitle.IsEmpty()
|
||||
? SelectionProvenance->PeerTemplateTitle
|
||||
: (!SelectionProvenance->PeerTemplateId.IsEmpty()
|
||||
? SelectionProvenance->PeerTemplateId
|
||||
: TEXT("n/a"));
|
||||
return FString::Printf(
|
||||
TEXT("Selector %s | reason %s | peer %s | %s"),
|
||||
SelectionProvenance->SelectorSourceLabel.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *SelectionProvenance->SelectorSourceLabel,
|
||||
*ResolveTemplateLaunchSelectionReasonClassLabel(SelectionProvenance->ReasonClass),
|
||||
*PeerTemplateLabel,
|
||||
SelectionProvenance->DecisionSummaryLine.IsEmpty()
|
||||
? TEXT("no retained selector decision summary")
|
||||
: *SelectionProvenance->DecisionSummaryLine
|
||||
);
|
||||
}
|
||||
|
||||
struct FClosureRecoveryDashboardBias
|
||||
|
|
@ -3772,7 +3824,9 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartPreferredActiv
|
|||
return FHyperTwistTrainingRunState();
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSessionTemplate* PreferredTemplate = FindPreferredDashboardTemplateLaunch();
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance LaunchSelectionProvenance;
|
||||
const FHyperTwistTrainingSessionTemplate* PreferredTemplate =
|
||||
FindPreferredDashboardTemplateLaunch(&LaunchSelectionProvenance);
|
||||
if (PreferredTemplate == nullptr)
|
||||
{
|
||||
UpdateDashboardPresentation();
|
||||
|
|
@ -3797,7 +3851,40 @@ FHyperTwistTrainingRunState UHyperTwistCoachDashboardWidget::StartPreferredActiv
|
|||
);
|
||||
if (RunState.Session.IsStructurallyValid())
|
||||
{
|
||||
CaptureActiveTemplateLaunchContext(RunState.Session.TrainingSessionId, LaunchedTemplate);
|
||||
if (LaunchSelectionProvenance.IsStructurallyValid())
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem =
|
||||
UHyperTwistTrainingRuntimeLibrary::GetTrainingSubsystem(this))
|
||||
{
|
||||
if (TrainingSubsystem->RecordActiveRunTemplateLaunchSelectionProvenance(
|
||||
LaunchSelectionProvenance))
|
||||
{
|
||||
RefreshTrainingState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTemplateLaunchProvenance EffectiveLaunchProvenance =
|
||||
CachedRunState.Session.TrainingSessionId == RunState.Session.TrainingSessionId
|
||||
&& CachedRunState.TemplateLaunchProvenance.IsStructurallyValid()
|
||||
? CachedRunState.TemplateLaunchProvenance
|
||||
: RunState.TemplateLaunchProvenance;
|
||||
if (!EffectiveLaunchProvenance.IsStructurallyValid())
|
||||
{
|
||||
EffectiveLaunchProvenance =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchProvenance(LaunchedTemplate);
|
||||
}
|
||||
if (LaunchSelectionProvenance.IsStructurallyValid()
|
||||
&& EffectiveLaunchProvenance.IsStructurallyValid()
|
||||
&& !EffectiveLaunchProvenance.SelectionProvenance.IsStructurallyValid())
|
||||
{
|
||||
EffectiveLaunchProvenance.SelectionProvenance = LaunchSelectionProvenance;
|
||||
}
|
||||
|
||||
CaptureActiveTemplateLaunchContext(
|
||||
RunState.Session.TrainingSessionId,
|
||||
EffectiveLaunchProvenance
|
||||
);
|
||||
}
|
||||
SyncRetainedRunRecap();
|
||||
SyncSelectedQueueEntry();
|
||||
|
|
@ -7172,12 +7259,12 @@ const FHyperTwistCoachBrief* UHyperTwistCoachDashboardWidget::GetActiveGuidanceP
|
|||
}
|
||||
|
||||
const FHyperTwistTrainingSessionTemplate* UHyperTwistCoachDashboardWidget::FindPreferredDashboardTemplateLaunch(
|
||||
FString* OutSelectionRationale
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance* OutSelectionProvenance
|
||||
) const
|
||||
{
|
||||
if (OutSelectionRationale != nullptr)
|
||||
if (OutSelectionProvenance != nullptr)
|
||||
{
|
||||
OutSelectionRationale->Reset();
|
||||
*OutSelectionProvenance = FHyperTwistTrainingTemplateLaunchSelectionProvenance();
|
||||
}
|
||||
|
||||
const FString EffectiveDefaultUserId = DefaultUserId;
|
||||
|
|
@ -7191,9 +7278,9 @@ const FHyperTwistTrainingSessionTemplate* UHyperTwistCoachDashboardWidget::FindP
|
|||
{
|
||||
PreferredTemplate = &SessionTemplate;
|
||||
PreferredScore = TemplateScore;
|
||||
if (OutSelectionRationale != nullptr)
|
||||
if (OutSelectionProvenance != nullptr)
|
||||
{
|
||||
OutSelectionRationale->Reset();
|
||||
*OutSelectionProvenance = FHyperTwistTrainingTemplateLaunchSelectionProvenance();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -7222,10 +7309,10 @@ const FHyperTwistTrainingSessionTemplate* UHyperTwistCoachDashboardWidget::FindP
|
|||
{
|
||||
const FHyperTwistTrainingSessionTemplate& PreviousPreferredTemplate = *PreferredTemplate;
|
||||
PreferredTemplate = &SessionTemplate;
|
||||
if (OutSelectionRationale != nullptr)
|
||||
if (OutSelectionProvenance != nullptr)
|
||||
{
|
||||
*OutSelectionRationale =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsTieBreakRationale(
|
||||
*OutSelectionProvenance =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsTieBreakSelectionProvenance(
|
||||
SessionTemplate,
|
||||
CandidateStats,
|
||||
PreviousPreferredTemplate,
|
||||
|
|
@ -7288,12 +7375,11 @@ void UHyperTwistCoachDashboardWidget::CaptureActiveGuidanceLaunchContext(
|
|||
|
||||
void UHyperTwistCoachDashboardWidget::CaptureActiveTemplateLaunchContext(
|
||||
const FString& SessionId,
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate
|
||||
const FHyperTwistTrainingTemplateLaunchProvenance& TemplateLaunchProvenance
|
||||
)
|
||||
{
|
||||
ActiveTemplateLaunchSessionId = SessionId;
|
||||
ActiveTemplateLaunchProvenance =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchProvenance(SessionTemplate);
|
||||
ActiveTemplateLaunchProvenance = TemplateLaunchProvenance;
|
||||
}
|
||||
|
||||
void UHyperTwistCoachDashboardWidget::CaptureActiveMethodDrillFollowUpLaunchContext(
|
||||
|
|
@ -11505,6 +11591,13 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
DisplayedTemplateLaunch->TemplateId,
|
||||
DisplayedTemplateLaunchUserId)
|
||||
: nullptr;
|
||||
const FString DisplayedTemplateLaunchSelectionDetail =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchSelectionDetailFragment(
|
||||
DisplayedTemplateLaunch != nullptr
|
||||
&& DisplayedTemplateLaunch->SelectionProvenance.IsStructurallyValid()
|
||||
? &DisplayedTemplateLaunch->SelectionProvenance
|
||||
: nullptr
|
||||
);
|
||||
const bool bDisplayedTemplateLaunchUsesReviewLane =
|
||||
DisplayedTemplateLaunch != nullptr
|
||||
&& (DisplayedTemplateLaunch->bPreferReviewRun
|
||||
|
|
@ -11546,9 +11639,9 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
const bool bDisplayedGuidanceComparisonSingleCase = bHasActiveGuidanceComparison
|
||||
? bActiveGuidanceLaunchSingleCase
|
||||
: (bHasDisplayedRetainedGuidanceComparison ? bRetainedGuidanceComparisonSingleCase : false);
|
||||
FString PreferredTemplateLaunchSelectionRationale;
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance PreferredTemplateLaunchSelectionProvenance;
|
||||
const FHyperTwistTrainingSessionTemplate* PreferredTemplateLaunch =
|
||||
FindPreferredDashboardTemplateLaunch(&PreferredTemplateLaunchSelectionRationale);
|
||||
FindPreferredDashboardTemplateLaunch(&PreferredTemplateLaunchSelectionProvenance);
|
||||
int32 EligibleTemplateLaunchCount = 0;
|
||||
for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : CachedActiveTrainingSessionTemplates)
|
||||
{
|
||||
|
|
@ -11580,6 +11673,12 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
PreferredTemplateLaunch->TemplateId,
|
||||
PreferredTemplateLaunchUserId)
|
||||
: nullptr;
|
||||
const FString PreferredTemplateLaunchSelectionDetail =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchSelectionDetailFragment(
|
||||
PreferredTemplateLaunchSelectionProvenance.IsStructurallyValid()
|
||||
? &PreferredTemplateLaunchSelectionProvenance
|
||||
: nullptr
|
||||
);
|
||||
const bool bCanLaunchPreferredTemplate =
|
||||
!bHasLiveAttemptTimer && !bHasActiveRun && bHasPreferredTemplateLaunch;
|
||||
FString TemplateLaunchStatusLine;
|
||||
|
|
@ -11662,10 +11761,10 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
*HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsDetailFragment(
|
||||
PreferredTemplateLaunchStats)
|
||||
);
|
||||
if (!PreferredTemplateLaunchSelectionRationale.IsEmpty())
|
||||
if (!PreferredTemplateLaunchSelectionDetail.IsEmpty())
|
||||
{
|
||||
TemplateLaunchDetailLine += TEXT(" | ");
|
||||
TemplateLaunchDetailLine += PreferredTemplateLaunchSelectionRationale;
|
||||
TemplateLaunchDetailLine += PreferredTemplateLaunchSelectionDetail;
|
||||
}
|
||||
}
|
||||
const FHyperTwistCoachDashboardTemplateLaunchHistoryEntry* SelectedTemplateLaunchHistoryEntry =
|
||||
|
|
@ -11679,6 +11778,13 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
SelectedTemplateLaunchHistoryEntry->TemplateId,
|
||||
SelectedTemplateLaunchHistoryEntry->UserId)
|
||||
: nullptr;
|
||||
const FString SelectedTemplateLaunchHistorySelectionDetail =
|
||||
HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchSelectionDetailFragment(
|
||||
SelectedTemplateLaunchHistoryEntry != nullptr
|
||||
&& SelectedTemplateLaunchHistoryEntry->SelectionProvenance.IsStructurallyValid()
|
||||
? &SelectedTemplateLaunchHistoryEntry->SelectionProvenance
|
||||
: nullptr
|
||||
);
|
||||
const FString TemplateLaunchHistoryStatusLine =
|
||||
SelectedTemplateLaunchHistoryEntry != nullptr
|
||||
? FString::Printf(
|
||||
|
|
@ -11697,7 +11803,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
*HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsOutcomeFragment(
|
||||
SelectedTemplateLaunchHistoryStats))
|
||||
: TEXT("Template launch history: no completed template-backed runs retained yet.");
|
||||
const FString TemplateLaunchHistoryDetailLine =
|
||||
FString TemplateLaunchHistoryDetailLine =
|
||||
SelectedTemplateLaunchHistoryEntry != nullptr
|
||||
? FString::Printf(
|
||||
TEXT("Title: %s | kind: %s | mode: %s | selection: %s | deck: %s | user: %s | source plan: %s | recommended %d | focus %d | attempts %d | success %.2f | mistakes %d | review-backed: %s | coach-review: %s | short set: %s | Aggregate detail: %s"),
|
||||
|
|
@ -11730,6 +11836,11 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
*HyperTwistCoachDashboardWidgetInternal::BuildTemplateLaunchAnalyticsDetailFragment(
|
||||
SelectedTemplateLaunchHistoryStats))
|
||||
: TEXT("Template launch history detail: completed runs started through the dashboard template lane will accumulate here with template id, template kind, and source review-plan provenance.");
|
||||
if (!SelectedTemplateLaunchHistorySelectionDetail.IsEmpty())
|
||||
{
|
||||
TemplateLaunchHistoryDetailLine += TEXT(" | ");
|
||||
TemplateLaunchHistoryDetailLine += SelectedTemplateLaunchHistorySelectionDetail;
|
||||
}
|
||||
const bool bCanUseCoachHandoff = !bHasLiveAttemptTimer && !bHasActiveRun;
|
||||
FString CoachHandoffStatusLine;
|
||||
if (bHasLiveAttemptTimer)
|
||||
|
|
@ -15740,7 +15851,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
{
|
||||
if (bHasDisplayedRunRecap)
|
||||
{
|
||||
RunRecapDetailTextBlock->SetText(FText::FromString(
|
||||
FString RunRecapDetailLine =
|
||||
DisplayedTemplateLaunch != nullptr
|
||||
? FString::Printf(
|
||||
TEXT("Attempts %d/%d | Success %d | Failure %d | Timeout %d | DNF %d | Aborted %d | Success rate %.2f | Best %d ms | Avg %d ms | Avg raw %d ms | Avg inspection %d ms | Mistakes %d | Last case %s | Template title %s | Template mode %s | Template selection %s | Recommended %d | Focus %d | Review-backed %s | Template aggregate %s | Template detail %s"),
|
||||
|
|
@ -15787,8 +15898,13 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation()
|
|||
DisplayedRunRecap->AverageRawSolveTimeMs,
|
||||
DisplayedRunRecap->AverageInspectionTimeMs,
|
||||
DisplayedRunRecap->TotalMistakes,
|
||||
DisplayedRunRecap->LastCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->LastCaseId)
|
||||
));
|
||||
DisplayedRunRecap->LastCaseId.IsEmpty() ? TEXT("n/a") : *DisplayedRunRecap->LastCaseId);
|
||||
if (!DisplayedTemplateLaunchSelectionDetail.IsEmpty())
|
||||
{
|
||||
RunRecapDetailLine += TEXT(" | ");
|
||||
RunRecapDetailLine += DisplayedTemplateLaunchSelectionDetail;
|
||||
}
|
||||
RunRecapDetailTextBlock->SetText(FText::FromString(RunRecapDetailLine));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2073,6 +2073,25 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingSubsystem::GetTrainingRepo
|
|||
return TrainingRepositoryState;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::RecordActiveRunTemplateLaunchSelectionProvenance(
|
||||
const FHyperTwistTrainingTemplateLaunchSelectionProvenance& SelectionProvenance
|
||||
)
|
||||
{
|
||||
if (!SelectionProvenance.IsStructurallyValid()
|
||||
|| !ActiveRunState.IsStructurallyValid()
|
||||
|| !ActiveRunState.TemplateLaunchProvenance.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ActiveRunState.TemplateLaunchProvenance.SelectionProvenance = SelectionProvenance;
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RecordRunState(
|
||||
TrainingRepositoryState,
|
||||
ActiveRunState
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingSubsystem::TryGetLatestGeneratedModeLaunchRequestForDeck(
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
|
|
|
|||
|
|
@ -397,6 +397,7 @@ struct FHyperTwistCoachDashboardTemplateLaunchHistoryEntry
|
|||
bool bPreferReviewRun = false;
|
||||
bool bCoachReview = false;
|
||||
bool bPreferShortReviewSet = false;
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance SelectionProvenance;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
|
|
@ -1588,7 +1589,7 @@ private:
|
|||
const FString& UserId
|
||||
) const;
|
||||
const FHyperTwistTrainingSessionTemplate* FindPreferredDashboardTemplateLaunch(
|
||||
FString* OutSelectionRationale = nullptr
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance* OutSelectionProvenance = nullptr
|
||||
) const;
|
||||
FString ResolveDashboardHandoffStartActionSourceLabel(
|
||||
EHyperTwistTrainingCoachHandoffKind HandoffKind) const;
|
||||
|
|
@ -1683,7 +1684,7 @@ private:
|
|||
);
|
||||
void CaptureActiveTemplateLaunchContext(
|
||||
const FString& SessionId,
|
||||
const FHyperTwistTrainingSessionTemplate& SessionTemplate
|
||||
const FHyperTwistTrainingTemplateLaunchProvenance& TemplateLaunchProvenance
|
||||
);
|
||||
void CaptureActiveMethodDrillFollowUpLaunchContext(const FString& SessionId);
|
||||
bool IsLiveAttemptInspectionPhase() const;
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRepositoryState GetTrainingRepositoryState() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Templates")
|
||||
bool RecordActiveRunTemplateLaunchSelectionProvenance(
|
||||
const FHyperTwistTrainingTemplateLaunchSelectionProvenance& SelectionProvenance
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
|
||||
bool TryGetLatestGeneratedModeLaunchRequestForDeck(
|
||||
const FString& UserId,
|
||||
|
|
|
|||
|
|
@ -1045,6 +1045,42 @@ enum class EHyperTwistTrainingSessionTemplateKind : uint8
|
|||
Coach UMETA(DisplayName = "Coach")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingTemplateLaunchSelectionReasonClass : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
PrimaryDashboardPriority UMETA(DisplayName = "Primary Dashboard Priority"),
|
||||
AnalyticsTieBreak UMETA(DisplayName = "Analytics Tie Break")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingTemplateLaunchSelectionProvenance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SelectorSourceLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingTemplateLaunchSelectionReasonClass ReasonClass =
|
||||
EHyperTwistTrainingTemplateLaunchSelectionReasonClass::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PeerTemplateId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PeerTemplateTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DecisionSummaryLine;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !SelectorSourceLabel.IsEmpty()
|
||||
&& ReasonClass != EHyperTwistTrainingTemplateLaunchSelectionReasonClass::None;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingTemplateLaunchProvenance
|
||||
{
|
||||
|
|
@ -1100,6 +1136,9 @@ struct FHyperTwistTrainingTemplateLaunchProvenance
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPreferShortReviewSet = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingTemplateLaunchSelectionProvenance SelectionProvenance;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !TemplateId.IsEmpty();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
# HyperTwist Phase 4 template launch selection provenance packet
|
||||
|
||||
Created on `2026-05-07`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded Phase `4` dashboard template selection persistence slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet persists why the dashboard chose a template when analytics broke a tied launch decision.
|
||||
|
||||
The open tasks were:
|
||||
|
||||
- stop leaving dashboard template tie-break reasoning in widget-only state
|
||||
- stamp the analytics tie-break decision onto the launched run itself
|
||||
- make repo-backed template run history capable of showing not just which template launched, but which tied peer it beat and why
|
||||
|
||||
It is not:
|
||||
|
||||
- a new template analytics derivation packet
|
||||
- a dashboard priority rewrite packet
|
||||
- a broader run-history schema redesign packet
|
||||
- a new review-plan or method-drill semantics packet
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- add a small structured template-launch selection provenance payload to persisted template launch provenance
|
||||
- let the dashboard preferred-template selector emit structured analytics tie-break provenance
|
||||
- add a subsystem path to stamp that provenance onto the active run and re-record the run state
|
||||
- carry the persisted selector provenance through dashboard template history and run recap detail surfaces
|
||||
|
||||
Out of scope:
|
||||
|
||||
- persisting selector provenance for non-dashboard launch paths
|
||||
- changing which templates are eligible for dashboard launch
|
||||
- changing repository analytics aggregation rules
|
||||
- adding a separate selector-audit screen
|
||||
|
||||
## Why this was the right next packet
|
||||
|
||||
The previous packet explained the analytics tie-break on the live dashboard launch surface.
|
||||
|
||||
That still left one obvious gap:
|
||||
|
||||
- operators could see why the current preferred template won before launch
|
||||
- but that selector decision disappeared into widget state once the run was started
|
||||
- and repo-backed run history still knew only which template launched, not which tied peer it beat or why
|
||||
|
||||
So the next honest move was:
|
||||
|
||||
- persist the selector decision into the launched run provenance itself
|
||||
- keep the payload small and structured
|
||||
- and let the existing recap/history surfaces read it back from stored run records
|
||||
|
||||
## What landed
|
||||
|
||||
Primary code changes:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
|
||||
- added `EHyperTwistTrainingTemplateLaunchSelectionReasonClass`
|
||||
- added `FHyperTwistTrainingTemplateLaunchSelectionProvenance`
|
||||
- nested the new selection provenance under `FHyperTwistTrainingTemplateLaunchProvenance`
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingSubsystem.h`
|
||||
- added a subsystem entry point for recording active-run template launch selection provenance
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp`
|
||||
- stamps selection provenance onto the active run and re-records that run into repository state
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h`
|
||||
- widened template launch history entry state and selector/capture signatures to carry structured selection provenance
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp`
|
||||
- emits structured analytics tie-break provenance from the preferred-template selector
|
||||
- persists that provenance immediately after a successful dashboard template launch
|
||||
- carries the persisted selector provenance into active template launch context, template launch history detail, and run recap detail
|
||||
|
||||
## Product effect
|
||||
|
||||
Template launch auditing is now durable:
|
||||
|
||||
- when the dashboard launches a template because analytics broke a tie, that decision is written into repo-backed run history
|
||||
- the stored payload includes the selector source, reason class, tied peer template, and a concise decision summary
|
||||
- dashboard template launch history can now show that selector provenance for completed runs
|
||||
- template-backed run recap can show the same selector provenance after launch, not only before launch
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- analytics tie-break selection provenance is persisted on launched template-backed runs
|
||||
- the persisted payload includes reason class and tied peer template context
|
||||
- dashboard template launch history can read selector provenance from stored run records
|
||||
- template-backed run recap can read selector provenance from stored run provenance
|
||||
- full product build succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
|
||||
2. confirm an analytics tie-break template launch stamps selector provenance onto the active run
|
||||
3. confirm repo-backed template launch history shows the stored selector provenance
|
||||
4. confirm template-backed run recap shows the same stored selector provenance after launch completion
|
||||
|
||||
That is the packet.
|
||||
Loading…
Add table
Reference in a new issue