Split template analytics by support decision kind

This commit is contained in:
axiomlogicnexus 2026-05-07 04:27:09 +02:00
parent 2f4b0afcc4
commit 3cee1031ea
4 changed files with 155 additions and 1 deletions

View file

@ -121,6 +121,10 @@ namespace HyperTwistCoachDashboardWidgetInternal
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
);
FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment(
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
);
FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportBiasSnapshotFragment(
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
);
@ -221,7 +225,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 | tie-break support %s | template-signal support %s | template-signal support-bias snapshot %s | template-signal confidence snapshot %s | peer support split %s | comparable-peer confidence %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 | template-signal support %s | template-signal decision %s | template-signal support-bias snapshot %s | template-signal confidence snapshot %s | peer support split %s | comparable-peer confidence %s | last launch %s | last completed %s"),
TemplateStats->AverageRecommendedCaseCount,
TemplateStats->AverageFocusCaseCount,
TemplateStats->AverageCompletedAttemptCount,
@ -236,6 +240,8 @@ namespace HyperTwistCoachDashboardWidgetInternal
*BuildTemplateLaunchSelectionSplitAnalyticsFragment(TemplateStats),
*BuildTemplateLaunchAnalyticsTieBreakSupportSplitFragment(TemplateStats),
*BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportSplitFragment(TemplateStats),
*BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment(
TemplateStats),
*BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportBiasSnapshotFragment(
TemplateStats),
*BuildTemplateLaunchAnalyticsTieBreakTemplateSignalConfidenceSnapshotFragment(
@ -766,6 +772,52 @@ namespace HyperTwistCoachDashboardWidgetInternal
return Fragment;
}
FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment(
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
)
{
if (TemplateStats == nullptr || !TemplateStats->IsStructurallyValid())
{
return TEXT("no template-signal decision split is available for this template id yet");
}
const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& NoSnapshotGuardStats =
TemplateStats
->DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats;
const FHyperTwistTrainingTemplateLaunchSelectionScopedStats& SnapshotGuardStats =
TemplateStats
->DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats;
if (!NoSnapshotGuardStats.IsStructurallyValid()
&& !SnapshotGuardStats.IsStructurallyValid())
{
return TEXT("no template-signal decision split is available for this template id yet");
}
FString Fragment = FString::Printf(
TEXT("%s | %s"),
*BuildTemplateLaunchSelectionLaneAnalyticsSummary(
NoSnapshotGuardStats,
TEXT("no-snapshot-guard")),
*BuildTemplateLaunchSelectionLaneAnalyticsSummary(
SnapshotGuardStats,
TEXT("support-bias-snapshot-guard"))
);
if (NoSnapshotGuardStats.IsStructurallyValid()
&& SnapshotGuardStats.IsStructurallyValid()
&& NoSnapshotGuardStats.GetOutcomeRunCount() > 0
&& SnapshotGuardStats.GetOutcomeRunCount() > 0)
{
Fragment += FString::Printf(
TEXT(" | snapshot-guard-minus-no-guard delta success %+.2f | delta mistakes %+.2f"),
SnapshotGuardStats.OverallSuccessRate - NoSnapshotGuardStats.OverallSuccessRate,
SnapshotGuardStats.AverageMistakesPerOutcomeRun
- NoSnapshotGuardStats.AverageMistakesPerOutcomeRun
);
}
return Fragment;
}
FString BuildTemplateLaunchAnalyticsTieBreakTemplateSignalSupportBiasSnapshotFragment(
const FHyperTwistTrainingTemplateLaunchScopedStats* TemplateStats
)

View file

@ -9166,6 +9166,10 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
DashboardAnalyticsTieBreakTemplateScopedSignalBroaderOutcomeOnlyAccumulator;
FSelectionScopedAccumulator
DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator;
FSelectionScopedAccumulator
DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator;
FSelectionScopedAccumulator
DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator;
FSelectionScopedAccumulator
DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedAccumulator;
FSelectionScopedAccumulator
@ -9359,6 +9363,24 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
Provenance.SelectionProvenance
);
}
if (Provenance.SelectionProvenance.TemplateSignalSupportDecisionKind ==
EHyperTwistTrainingTemplateLaunchSelectionTemplateSignalSupportDecisionKind::
SupportBiasSnapshotGuard)
{
AccumulateSelectionScopedStats(
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator,
Provenance.SelectionProvenance
);
}
else
{
AccumulateSelectionScopedStats(
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator,
Provenance.SelectionProvenance
);
}
const EHyperTwistTrainingTemplateLaunchTemplateSignalSupportBiasSnapshotState
SupportBiasSnapshotState =
Provenance.SelectionProvenance.TemplateSignalSupportBiasSnapshot.State;
@ -9564,6 +9586,14 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator
);
FinalizeSelectionScopedAccumulator(
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator
);
FinalizeSelectionScopedAccumulator(
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator
);
FinalizeSelectionScopedAccumulator(
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedAccumulator
@ -9620,6 +9650,14 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
Accumulator.DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator
.Stats.PeerSupportKindDisposition =
EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None;
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator
.Stats.PeerSupportKindDisposition =
EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None;
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator
.Stats.PeerSupportKindDisposition =
EHyperTwistTrainingTemplateLaunchSelectionPeerSupportKindDisposition::None;
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedAccumulator
.Stats.PeerSupportKindDisposition =
@ -9660,6 +9698,16 @@ UHyperTwistTrainingRepositoryLibrary::ListTemplateLaunchScopedStatsForUser(
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasAccumulator
.Stats;
Accumulator.Stats
.DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats =
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardAccumulator
.Stats;
Accumulator.Stats
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats =
Accumulator
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardAccumulator
.Stats;
Accumulator.Stats
.DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedStats =
Accumulator

View file

@ -2311,6 +2311,14 @@ struct FHyperTwistTrainingTemplateLaunchScopedStats
FHyperTwistTrainingTemplateLaunchSelectionScopedStats
DashboardAnalyticsTieBreakTemplateScopedSignalConfidenceLaneBiasStats;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingTemplateLaunchSelectionScopedStats
DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingTemplateLaunchSelectionScopedStats
DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingTemplateLaunchSelectionScopedStats
DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotEarnedStats;

View file

@ -0,0 +1,46 @@
# HyperTwist Phase 4 template-signal decision split analytics packet
## Purpose
This packet aggregates the newly persisted template-signal support decision kind back into repo-backed template analytics. The dashboard can now compare broader template-signal launches that did not need the historical support-bias snapshot guard against launches where that guard explicitly decided the winner.
## Scope
- add repo-backed template-scoped stats buckets for broader template-signal launches with and without the `SupportBiasSnapshotGuard` decision
- route stored `TemplateSignalSupportDecisionKind` through template launch scoped analytics derivation
- append a shared `template-signal decision` analytics fragment so the live dashboard launch row, template history, and recap all expose the new split
- report outcome deltas when both decision lanes have meaningful outcome history
## What landed
- `HyperTwistTrainingTypes.h`
- added `DashboardAnalyticsTieBreakTemplateScopedSignalNoSupportBiasSnapshotGuardStats`
- added `DashboardAnalyticsTieBreakTemplateScopedSignalSupportBiasSnapshotGuardStats`
- `HyperTwistTrainingRepositoryLibrary.cpp`
- added matching scoped accumulators
- broader `TemplateScopedOutcomeSignal` runs with `TemplateSignalSupportDecisionKind = SupportBiasSnapshotGuard` now flow into the guard bucket
- all other broader template-signal runs, including older records with no retained decision kind, flow into the `no-support-bias-snapshot-guard` bucket
- `HyperTwistCoachDashboardWidget.cpp`
- added `BuildTemplateLaunchAnalyticsTieBreakTemplateSignalDecisionSplitFragment(...)`
- shared analytics detail now appends `template-signal decision ...`
- decision split shows both lanes plus `snapshot-guard-minus-no-guard` success and mistake deltas when both lanes have outcome history
## Product effect
- operators can now see whether broader template-signal launches that were explicitly decided by the historical support-bias snapshot guard are outperforming the broader template-signal launches that were selected without that guard
- template launch history and recap inherit the same decision split automatically because they already reuse the shared analytics detail formatter
- older runs remain analyzable without migration because missing decision-kind provenance is treated as `no-snapshot-guard`
## Acceptance criteria
- repo-backed template launch scoped stats retain separate broader template-signal buckets for `SupportBiasSnapshotGuard` and `no-snapshot-guard`
- shared dashboard analytics detail renders the new `template-signal decision` fragment anywhere template launch analytics are shown
- decision split deltas appear only when both decision lanes have outcome history
- older runs without retained `TemplateSignalSupportDecisionKind` continue to participate in analytics through the `no-snapshot-guard` bucket
## Validation checklist
1. full `Development Editor|Win64` build succeeds
2. confirm template launch scoped analytics route stored `TemplateSignalSupportDecisionKind = SupportBiasSnapshotGuard` into the new guard bucket
3. confirm shared analytics detail shows `template-signal decision no-snapshot-guard ... | support-bias-snapshot-guard ...`
4. confirm older broader template-signal runs without retained decision kind still surface under `no-snapshot-guard`