Split template analytics tie-break outcomes by support
This commit is contained in:
parent
cfd971c600
commit
c65fc2a312
4 changed files with 211 additions and 28 deletions
|
|
@ -113,6 +113,10 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
|
||||
);
|
||||
|
||||
FString BuildTemplateLaunchAnalyticsTieBreakSupportSplitFragment(
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
|
||||
);
|
||||
|
||||
FString BuildTemplateLaunchAnalyticsDetailFragment(
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
|
||||
)
|
||||
|
|
@ -123,7 +127,7 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
}
|
||||
|
||||
return FString::Printf(
|
||||
TEXT("avg recommended %d | avg focus %d | avg completed attempts %d | total success/failure/timeout/dnf %d/%d/%d/%d | avg mistakes %.2f | review-backed %d | coach-review %d | short set %d | selector split %s | last launch %s | last completed %s"),
|
||||
TEXT("avg recommended %d | avg focus %d | avg completed attempts %d | total success/failure/timeout/dnf %d/%d/%d/%d | avg mistakes %.2f | review-backed %d | coach-review %d | short set %d | selector split %s | tie-break support %s | last launch %s | last completed %s"),
|
||||
TemplateStats->AverageRecommendedCaseCount,
|
||||
TemplateStats->AverageFocusCaseCount,
|
||||
TemplateStats->AverageCompletedAttemptCount,
|
||||
|
|
@ -136,6 +140,7 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
TemplateStats->CoachReviewLaunchCount,
|
||||
TemplateStats->ShortReviewSetLaunchCount,
|
||||
*BuildTemplateLaunchSelectionSplitAnalyticsFragment(TemplateStats),
|
||||
*BuildTemplateLaunchAnalyticsTieBreakSupportSplitFragment(TemplateStats),
|
||||
TemplateStats->LastLaunchAtUtc.IsEmpty()
|
||||
? TEXT("n/a")
|
||||
: *TemplateStats->LastLaunchAtUtc,
|
||||
|
|
@ -333,6 +338,49 @@ namespace HyperTwistCoachDashboardWidgetInternal
|
|||
return Fragment;
|
||||
}
|
||||
|
||||
FString BuildTemplateLaunchAnalyticsTieBreakSupportSplitFragment(
|
||||
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
|
||||
)
|
||||
{
|
||||
if (TemplateStats == nullptr || !TemplateStats->IsStructurallyValid())
|
||||
{
|
||||
return TEXT("no analytics tie-break support split is available for this template id yet");
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& TemplateSignalStats =
|
||||
TemplateStats->DashboardAnalyticsTieBreakTemplateScopedSignalStats;
|
||||
const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& SplitConfidenceStats =
|
||||
TemplateStats->DashboardAnalyticsTieBreakSelectorSplitConfidenceStats;
|
||||
if (!TemplateSignalStats.IsStructurallyValid() && !SplitConfidenceStats.IsStructurallyValid())
|
||||
{
|
||||
return TEXT("no analytics tie-break support split is available for this template id yet");
|
||||
}
|
||||
|
||||
FString Fragment = FString::Printf(
|
||||
TEXT("%s | %s"),
|
||||
*BuildTemplateLaunchSelectionLaneAnalyticsSummary(
|
||||
TemplateSignalStats,
|
||||
TEXT("template-signal")),
|
||||
*BuildTemplateLaunchSelectionLaneAnalyticsSummary(
|
||||
SplitConfidenceStats,
|
||||
TEXT("split-confidence"))
|
||||
);
|
||||
if (TemplateSignalStats.IsStructurallyValid()
|
||||
&& SplitConfidenceStats.IsStructurallyValid()
|
||||
&& TemplateSignalStats.GetOutcomeRunCount() > 0
|
||||
&& SplitConfidenceStats.GetOutcomeRunCount() > 0)
|
||||
{
|
||||
Fragment += FString::Printf(
|
||||
TEXT(" | delta success %+.2f | delta mistakes %+.2f"),
|
||||
SplitConfidenceStats.OverallSuccessRate - TemplateSignalStats.OverallSuccessRate,
|
||||
SplitConfidenceStats.AverageMistakesPerOutcomeRun
|
||||
- TemplateSignalStats.AverageMistakesPerOutcomeRun
|
||||
);
|
||||
}
|
||||
|
||||
return Fragment;
|
||||
}
|
||||
|
||||
FString BuildTemplateLaunchSelectionAnalyticsSupportDetailFragment(
|
||||
const FHyperTwistTrainingTemplateLaunchSelectionProvenance* SelectionProvenance
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9161,6 +9161,8 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
|
|||
int32 OutcomeRunSampleCount = 0;
|
||||
FSelectionScopedAccumulator DashboardPrimarySelectionAccumulator;
|
||||
FSelectionScopedAccumulator DashboardAnalyticsTieBreakSelectionAccumulator;
|
||||
FSelectionScopedAccumulator DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator;
|
||||
FSelectionScopedAccumulator DashboardAnalyticsTieBreakSelectorSplitConfidenceAccumulator;
|
||||
};
|
||||
|
||||
const FString ResolvedReferenceUtc =
|
||||
|
|
@ -9261,6 +9263,8 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
|
|||
{
|
||||
SelectionAccumulator.Stats.SelectorSourceLabel = SelectionProvenance.SelectorSourceLabel;
|
||||
SelectionAccumulator.Stats.ReasonClass = SelectionProvenance.ReasonClass;
|
||||
SelectionAccumulator.Stats.AnalyticsSupportKind =
|
||||
SelectionProvenance.AnalyticsSupportKind;
|
||||
SelectionAccumulator.Stats.LaunchCount += 1;
|
||||
SelectionAccumulator.Stats.TotalCompletedAttemptCount +=
|
||||
RunRecord.Summary.CompletedAttemptCount;
|
||||
|
|
@ -9304,6 +9308,22 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
|
|||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator,
|
||||
Provenance.SelectionProvenance
|
||||
);
|
||||
if (Provenance.SelectionProvenance.AnalyticsSupportKind ==
|
||||
EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::TemplateScopedOutcomeSignal)
|
||||
{
|
||||
AccumulateSelectionScopedStats(
|
||||
Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator,
|
||||
Provenance.SelectionProvenance
|
||||
);
|
||||
}
|
||||
else if (Provenance.SelectionProvenance.AnalyticsSupportKind ==
|
||||
EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::SelectorSplitConfidence)
|
||||
{
|
||||
AccumulateSelectionScopedStats(
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectorSplitConfidenceAccumulator,
|
||||
Provenance.SelectionProvenance
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9363,37 +9383,44 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
|
|||
? static_cast<float>(Accumulator.MistakeAccumulator)
|
||||
/ static_cast<float>(Accumulator.OutcomeRunSampleCount)
|
||||
: 0.0f;
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.Stats.OverallSuccessRate =
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.Stats.TotalCompletedAttemptCount > 0
|
||||
? static_cast<float>(
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.Stats.TotalSuccessCount)
|
||||
/ static_cast<float>(
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.Stats.TotalCompletedAttemptCount)
|
||||
: 0.0f;
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.Stats.AverageMistakesPerOutcomeRun =
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.OutcomeRunSampleCount > 0
|
||||
? static_cast<float>(Accumulator.DashboardPrimarySelectionAccumulator.MistakeAccumulator)
|
||||
/ static_cast<float>(
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.OutcomeRunSampleCount)
|
||||
: 0.0f;
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats.OverallSuccessRate =
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats.TotalCompletedAttemptCount > 0
|
||||
? static_cast<float>(
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats.TotalSuccessCount)
|
||||
/ static_cast<float>(
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats.TotalCompletedAttemptCount)
|
||||
: 0.0f;
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats.AverageMistakesPerOutcomeRun =
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.OutcomeRunSampleCount > 0
|
||||
? static_cast<float>(
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.MistakeAccumulator)
|
||||
/ static_cast<float>(
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.OutcomeRunSampleCount)
|
||||
: 0.0f;
|
||||
auto FinalizeSelectionScopedAccumulator =
|
||||
[](
|
||||
FTemplateLaunchScopedStatsAccumulator::FSelectionScopedAccumulator& SelectionAccumulator)
|
||||
{
|
||||
SelectionAccumulator.Stats.OverallSuccessRate =
|
||||
SelectionAccumulator.Stats.TotalCompletedAttemptCount > 0
|
||||
? static_cast<float>(SelectionAccumulator.Stats.TotalSuccessCount)
|
||||
/ static_cast<float>(
|
||||
SelectionAccumulator.Stats.TotalCompletedAttemptCount)
|
||||
: 0.0f;
|
||||
SelectionAccumulator.Stats.AverageMistakesPerOutcomeRun =
|
||||
SelectionAccumulator.OutcomeRunSampleCount > 0
|
||||
? static_cast<float>(SelectionAccumulator.MistakeAccumulator)
|
||||
/ static_cast<float>(SelectionAccumulator.OutcomeRunSampleCount)
|
||||
: 0.0f;
|
||||
};
|
||||
FinalizeSelectionScopedAccumulator(Accumulator.DashboardPrimarySelectionAccumulator);
|
||||
FinalizeSelectionScopedAccumulator(
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator
|
||||
);
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.Stats.AnalyticsSupportKind =
|
||||
EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::None;
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats.AnalyticsSupportKind =
|
||||
EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::None;
|
||||
FinalizeSelectionScopedAccumulator(
|
||||
Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator
|
||||
);
|
||||
FinalizeSelectionScopedAccumulator(
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectorSplitConfidenceAccumulator
|
||||
);
|
||||
Accumulator.Stats.DashboardPrimarySelectionStats =
|
||||
Accumulator.DashboardPrimarySelectionAccumulator.Stats;
|
||||
Accumulator.Stats.DashboardAnalyticsTieBreakStats =
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectionAccumulator.Stats;
|
||||
Accumulator.Stats.DashboardAnalyticsTieBreakTemplateScopedSignalStats =
|
||||
Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalAccumulator.Stats;
|
||||
Accumulator.Stats.DashboardAnalyticsTieBreakSelectorSplitConfidenceStats =
|
||||
Accumulator.DashboardAnalyticsTieBreakSelectorSplitConfidenceAccumulator.Stats;
|
||||
if (Accumulator.Stats.IsStructurallyValid())
|
||||
{
|
||||
ScopedStats.Add(Accumulator.Stats);
|
||||
|
|
|
|||
|
|
@ -2037,6 +2037,10 @@ struct FHyperTwistTrainingTemplateLaunchSelectionScopedStats
|
|||
EHyperTwistTrainingTemplateLaunchSelectionReasonClass ReasonClass =
|
||||
EHyperTwistTrainingTemplateLaunchSelectionReasonClass::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind AnalyticsSupportKind =
|
||||
EHyperTwistTrainingTemplateLaunchSelectionAnalyticsSupportKind::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 LaunchCount = 0;
|
||||
|
||||
|
|
@ -2173,6 +2177,14 @@ struct FHyperTwistTrainingTemplateLaunchScopedStats
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingTemplateLaunchSelectionScopedStats DashboardAnalyticsTieBreakStats;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingTemplateLaunchSelectionScopedStats
|
||||
DashboardAnalyticsTieBreakTemplateScopedSignalStats;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingTemplateLaunchSelectionScopedStats
|
||||
DashboardAnalyticsTieBreakSelectorSplitConfidenceStats;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastLaunchAtUtc;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
# HyperTwist Phase 4 template launch analytics support split packet
|
||||
|
||||
Created on `2026-05-07`
|
||||
|
||||
Status:
|
||||
|
||||
- first-party HyperTwist packet
|
||||
- bounded Phase `4` dashboard template analytics refinement slice
|
||||
|
||||
## Purpose
|
||||
|
||||
This packet aggregates analytics tie-break outcomes by persisted support kind so the dashboard can compare tie-break launches backed by selector-split confidence against tie-break launches decided by broader template-scoped outcome signal alone.
|
||||
|
||||
The open tasks were:
|
||||
|
||||
- stop leaving the new analytics support provenance as run-by-run audit detail only
|
||||
- derive repo-backed per-template support-kind outcome splits inside the existing template launch analytics lane
|
||||
- surface that support-kind comparison back into the dashboard without creating a new screen
|
||||
|
||||
It is not:
|
||||
|
||||
- another selector math packet
|
||||
- a new template launch history packet
|
||||
- a new repository API surface beyond scoped stat derivation
|
||||
- a non-dashboard analytics consumer packet
|
||||
|
||||
## Scope
|
||||
|
||||
Bounded lane:
|
||||
|
||||
- extend selection-scoped template stats to carry analytics support identity
|
||||
- derive per-template analytics-tie-break support-kind aggregates from stored run provenance
|
||||
- persist two repo-backed support buckets for dashboard analytics tie-break launches: broader template-scoped signal and selector-split confidence
|
||||
- append the new support-kind split comparison to the shared dashboard template analytics detail fragment already used by live launch, template history, and recap
|
||||
|
||||
Out of scope:
|
||||
|
||||
- changing the current analytics support provenance schema
|
||||
- changing selector confidence thresholds
|
||||
- feeding the new support-kind split back into selector behavior
|
||||
- adding separate support-kind navigation or controls
|
||||
|
||||
## Why this was the right next packet
|
||||
|
||||
The previous packet made it possible to audit whether a single analytics tie-break win carried selector-split confidence support.
|
||||
|
||||
That still left the obvious aggregate gap:
|
||||
|
||||
- operators could inspect support kind on an individual launch
|
||||
- but they still could not tell whether selector-split-confidence-backed tie-breaks were actually outperforming broader template-signal tie-breaks over time
|
||||
- and the repo-backed template analytics lane still treated all analytics tie-break launches as one bucket
|
||||
|
||||
So the next honest move was:
|
||||
|
||||
- keep the existing repo-backed template launch scoped stats as the aggregation home
|
||||
- split analytics tie-break outcomes by support kind inside that same structure
|
||||
- and surface that comparison through the dashboard’s existing shared analytics detail path
|
||||
|
||||
## What landed
|
||||
|
||||
Primary code changes:
|
||||
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h`
|
||||
- added analytics-support identity to `FHyperTwistTrainingTemplateLaunchSelectionScopedStats`
|
||||
- added per-template support-kind analytics tie-break stat buckets for template-scoped signal and selector-split confidence
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.cpp`
|
||||
- derives repo-backed support-kind aggregates for dashboard analytics tie-break launches from stored selection provenance
|
||||
- computes launch counts, outcome counts, success rate, and average mistakes separately for each support kind
|
||||
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp`
|
||||
- adds a shared support-kind split formatter for template analytics detail
|
||||
- appends the new repo-backed support-kind comparison to the live template launch detail, template launch history, and template-backed recap through the existing analytics detail fragment
|
||||
|
||||
## Product effect
|
||||
|
||||
Dashboard template analytics can now compare two different kinds of analytics tie-break wins for the same template:
|
||||
|
||||
- broader template-scoped outcome signal tie-break launches
|
||||
- selector-split-confidence-backed tie-break launches
|
||||
|
||||
Those support-kind splits are now visible anywhere the dashboard already shows shared template analytics detail, and they are derived from stored run provenance rather than widget-retained state.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- repo-backed template launch scoped stats split analytics tie-break outcomes by support kind
|
||||
- dashboard template analytics detail shows the new support-kind comparison when data exists
|
||||
- live template launch detail, template launch history, and template-backed recap all inherit that comparison through the shared analytics detail formatter
|
||||
- full product build succeeds
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. build `UnrealHyperTwist.sln` / `UnrealHyperTwistEditor`
|
||||
2. confirm repo-backed template stats derive separate support-kind buckets for analytics tie-break launches
|
||||
3. confirm the dashboard analytics detail fragment shows the support-kind split comparison on live launch, history, and recap surfaces
|
||||
4. confirm templates with no support-kind tie-break history show the explicit empty-state message
|
||||
|
||||
That is the packet.
|
||||
Loading…
Add table
Reference in a new issue