Prefer stronger analytics on tied template launches
This commit is contained in:
parent
2d20d0ba60
commit
c6215f194c
2 changed files with 208 additions and 0 deletions
|
|
@ -433,6 +433,96 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
return Score;
|
||||
}
|
||||
|
||||
FString ResolveDashboardTemplateAnalyticsUserId(
|
||||
const FHyperTwistTrainingSessionTemplate& Template,
|
||||
const FString& DefaultUserId
|
||||
)
|
||||
{
|
||||
return !Template.UserId.IsEmpty() ? Template.UserId : DefaultUserId;
|
||||
}
|
||||
|
||||
int32 CountTemplateLaunchOutcomeRuns(const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats)
|
||||
{
|
||||
return TemplateStats != nullptr && TemplateStats->IsStructurallyValid()
|
||||
? (TemplateStats->CompletedRunCount + TemplateStats->AbortedRunCount)
|
||||
: 0;
|
||||
}
|
||||
|
||||
int32 BuildTemplateLaunchAnalyticsTieBreakScore(
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
|
||||
)
|
||||
{
|
||||
if (TemplateStats == nullptr || !TemplateStats->IsStructurallyValid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int32 OutcomeRuns = CountTemplateLaunchOutcomeRuns(TemplateStats);
|
||||
if (OutcomeRuns <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 Score = 0;
|
||||
Score += FMath::Clamp(OutcomeRuns, 0, 4);
|
||||
Score += FMath::Clamp(FMath::RoundToInt(TemplateStats->OverallSuccessRate * 10.0f), 0, 10);
|
||||
Score -= FMath::Clamp(FMath::RoundToInt(TemplateStats->AverageMistakesPerOutcomeRun * 2.0f), 0, 8);
|
||||
Score -= FMath::Clamp(TemplateStats->AbortedRunCount, 0, 3);
|
||||
return Score;
|
||||
}
|
||||
|
||||
bool ShouldPreferTemplateByAnalytics(
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* CandidateStats,
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* IncumbentStats
|
||||
)
|
||||
{
|
||||
const int32 CandidateOutcomeRuns = CountTemplateLaunchOutcomeRuns(CandidateStats);
|
||||
const int32 IncumbentOutcomeRuns = CountTemplateLaunchOutcomeRuns(IncumbentStats);
|
||||
if (CandidateOutcomeRuns < 2 && IncumbentOutcomeRuns < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 CandidateScore = BuildTemplateLaunchAnalyticsTieBreakScore(CandidateStats);
|
||||
const int32 IncumbentScore = BuildTemplateLaunchAnalyticsTieBreakScore(IncumbentStats);
|
||||
if (CandidateScore != IncumbentScore)
|
||||
{
|
||||
return CandidateScore > IncumbentScore;
|
||||
}
|
||||
if (CandidateOutcomeRuns != IncumbentOutcomeRuns)
|
||||
{
|
||||
return CandidateOutcomeRuns > IncumbentOutcomeRuns;
|
||||
}
|
||||
|
||||
const float CandidateSuccessRate =
|
||||
CandidateStats != nullptr && CandidateStats->IsStructurallyValid()
|
||||
? CandidateStats->OverallSuccessRate
|
||||
: 0.0f;
|
||||
const float IncumbentSuccessRate =
|
||||
IncumbentStats != nullptr && IncumbentStats->IsStructurallyValid()
|
||||
? IncumbentStats->OverallSuccessRate
|
||||
: 0.0f;
|
||||
if (FMath::Abs(CandidateSuccessRate - IncumbentSuccessRate) >= 0.05f)
|
||||
{
|
||||
return CandidateSuccessRate > IncumbentSuccessRate;
|
||||
}
|
||||
|
||||
const float CandidateMistakeRate =
|
||||
CandidateStats != nullptr && CandidateStats->IsStructurallyValid()
|
||||
? CandidateStats->AverageMistakesPerOutcomeRun
|
||||
: 0.0f;
|
||||
const float IncumbentMistakeRate =
|
||||
IncumbentStats != nullptr && IncumbentStats->IsStructurallyValid()
|
||||
? IncumbentStats->AverageMistakesPerOutcomeRun
|
||||
: 0.0f;
|
||||
if (FMath::Abs(CandidateMistakeRate - IncumbentMistakeRate) >= 0.5f)
|
||||
{
|
||||
return CandidateMistakeRate < IncumbentMistakeRate;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
EMethodDrillRecoveryMemoryBias ResolveMethodDrillRecoveryMemoryBias(
|
||||
const int32 HistoryCount,
|
||||
const int32 StrongCount,
|
||||
|
|
@ -7036,6 +7126,33 @@ const FHyperTwistTrainingSessionTemplate* UHyperTwistCoachDashboardWidget::FindP
|
|||
{
|
||||
PreferredTemplate = &SessionTemplate;
|
||||
PreferredScore = TemplateScore;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TemplateScore == PreferredScore
|
||||
&& TemplateScore > 0
|
||||
&& PreferredTemplate != nullptr)
|
||||
{
|
||||
const FString CandidateUserId =
|
||||
HyperTwistCoachDashboardWidgetInternal::ResolveDashboardTemplateAnalyticsUserId(
|
||||
SessionTemplate,
|
||||
DefaultUserId
|
||||
);
|
||||
const FString PreferredUserId =
|
||||
HyperTwistCoachDashboardWidgetInternal::ResolveDashboardTemplateAnalyticsUserId(
|
||||
*PreferredTemplate,
|
||||
DefaultUserId
|
||||
);
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* CandidateStats =
|
||||
FindCachedTemplateLaunchScopedStats(SessionTemplate.TemplateId, CandidateUserId);
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* PreferredStats =
|
||||
FindCachedTemplateLaunchScopedStats(PreferredTemplate->TemplateId, PreferredUserId);
|
||||
if (HyperTwistCoachDashboardWidgetInternal::ShouldPreferTemplateByAnalytics(
|
||||
CandidateStats,
|
||||
PreferredStats))
|
||||
{
|
||||
PreferredTemplate = &SessionTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
# HyperTwist Phase 4 template launch analytics tie-breaker packet
|
||||
|
||||
Created on `2026-05-07`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded Phase `4` dashboard template selection slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet adds a conservative analytics tie-breaker to dashboard template launch selection.
|
||||
|
||||
The open tasks were:
|
||||
|
||||
- keep the existing review and recovery template priority rules intact
|
||||
- stop breaking equal-priority template ties by incidental cache order alone
|
||||
- prefer the equally eligible template with the stronger stored outcome signal when enough history exists
|
||||
|
||||
It is not:
|
||||
|
||||
- a primary template ranking rewrite packet
|
||||
- a change to review-plan resume semantics
|
||||
- a change to template provenance persistence
|
||||
- a broader coach weighting or recommendation packet
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- add a small dashboard helper for resolving the analytics user id for a template
|
||||
- derive a conservative tie-break score from cached per-template scoped stats
|
||||
- require enough stored outcome history before analytics can affect selection
|
||||
- apply the tie-break only when two active templates already have the same dashboard launch priority score
|
||||
|
||||
Out of scope:
|
||||
|
||||
- changing the existing review or recovery template score bands
|
||||
- changing repository aggregation logic for template launch stats
|
||||
- introducing new UI surfaces or controls
|
||||
- using template analytics outside the dashboard launch selector
|
||||
|
||||
## Why this was the right next packet
|
||||
|
||||
The previous packet surfaced repo-backed per-template outcome stats on the dashboard.
|
||||
|
||||
That still left one clean next gap:
|
||||
|
||||
- operators could now see the per-template outcome signal
|
||||
- but the actual preferred template selector still ignored that signal
|
||||
- and equal-priority templates were still effectively chosen by iteration order
|
||||
|
||||
So the next honest move was:
|
||||
|
||||
- keep the current launch scoring rules as the primary decision
|
||||
- add analytics only as a tie-breaker between already-equal candidates
|
||||
- and gate that tie-break behind a modest history threshold so one noisy run does not distort launch selection
|
||||
|
||||
## What landed
|
||||
|
||||
Primary code changes:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp`
|
||||
- added helper logic for resolving template analytics ownership, counting outcome runs, and building a bounded tie-break score from stored success, mistake, and abort signals
|
||||
- added a conservative analytics preference check that only activates when at least one tied template has meaningful stored outcome history
|
||||
- updated `FindPreferredDashboardTemplateLaunch()` so equally scored dashboard template candidates use the analytics tie-break instead of falling through to cache order
|
||||
|
||||
## Product effect
|
||||
|
||||
Preferred dashboard template launch selection is now slightly smarter without changing the visible priority model:
|
||||
|
||||
- review and recovery templates still win or lose on the same primary score rules as before
|
||||
- analytics only matter when templates are already equally eligible
|
||||
- templates with better stored success and lower mistake pressure now win tied selections more often
|
||||
- templates with too little history still behave as before, which keeps the selector stable under sparse data
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- dashboard template selection still uses existing primary score rules
|
||||
- analytics only influence selection when candidates tie on primary score
|
||||
- sparse history does not trigger analytics-driven template churn
|
||||
- full product build succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
|
||||
2. confirm preferred dashboard template selection still respects the existing review and recovery priority order
|
||||
3. confirm tied templates with stronger stored success and lower mistake pressure are preferred when scoped stats exist
|
||||
4. confirm templates with insufficient outcome history still fall back to the preexisting selector behavior
|
||||
|
||||
That is the packet.
|
||||
Loading…
Add table
Reference in a new issue