Skip to main content

wowlab_analytics/analytics/
tournament.rs

1use serde::Serialize;
2use wowlab_types::{constants::PROTO_DPS_SCALE, proto};
3
4use super::views::AnalyticsView;
5
6/// Chart-ready view model for a finalized tournament job.
7#[derive(Clone, Debug, Serialize)]
8#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
9pub struct TournamentView {
10    pub stats: TournamentStatsView,
11    pub top_permutations: Vec<PermutationSummaryView>,
12    pub slot_rankings: Vec<SlotRankingView>,
13}
14
15/// Headline tournament stats: winner vs baseline DPS, phases completed, totals.
16#[derive(Clone, Debug, Serialize)]
17#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
18pub struct TournamentStatsView {
19    pub winner_dps: f64,
20    pub baseline_dps: f64,
21    pub dps_gain: f64,
22    pub dps_gain_pct: Option<f64>,
23    pub total_permutations: u32,
24    pub total_iterations: u64,
25    pub phases_completed: u32,
26}
27
28/// One permutation in the ranked top-N output.
29#[derive(Clone, Debug, Serialize)]
30#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
31pub struct PermutationSummaryView {
32    pub rank: u32,
33    pub mean_dps: f64,
34    pub ci95_half: f64,
35    pub dps_delta_from_winner: f64,
36    pub diffs: Vec<SlotDiffView>,
37}
38
39/// A single slot override that distinguishes a permutation from baseline.
40#[derive(Clone, Debug, Serialize)]
41#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
42pub struct SlotDiffView {
43    pub slot_index: u32,
44    pub item_id: u32,
45    pub bonus_id_hash: u32,
46}
47
48/// Per-slot ranked candidate items from the tournament.
49#[derive(Clone, Debug, Serialize)]
50#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
51pub struct SlotRankingView {
52    pub slot_index: u32,
53    pub items: Vec<SlotItemEntryView>,
54}
55
56/// A single ranked item within a slot's top-N.
57#[derive(Clone, Debug, Serialize)]
58#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
59pub struct SlotItemEntryView {
60    pub item_id: u32,
61    pub bonus_id_hash: u32,
62    pub best_perm_dps: f64,
63    pub best_perm_rank: u32,
64    pub ci95_half: f64,
65    pub win_rate_pct: f64,
66    pub marginal_dps_delta: f64,
67}
68
69/// Tagged decoded result envelope: discriminates single vs tournament vs legacy.
70#[derive(Clone, Debug, Serialize)]
71#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
72#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
73#[serde(tag = "kind", rename_all = "snake_case")]
74#[non_exhaustive]
75// docref:start sim-ui-job-result-view
76pub enum JobResultView {
77    Single {
78        analytics: AnalyticsView,
79    },
80    Tournament {
81        analytics: Option<AnalyticsView>,
82        tournament: TournamentView,
83    },
84}
85// docref:end sim-ui-job-result-view
86
87pub(super) fn convert_slot_diffs(diffs: &[proto::SlotDiff]) -> Vec<SlotDiffView> {
88    diffs
89        .iter()
90        .map(|d| SlotDiffView {
91            slot_index: d.slot_index,
92            item_id: d.item_id,
93            bonus_id_hash: d.bonus_id_hash,
94        })
95        .collect()
96}
97
98pub(super) fn convert_permutation_summaries(
99    summaries: &[proto::PermutationSummary],
100    winner_dps_x10: u32,
101) -> Vec<PermutationSummaryView> {
102    summaries
103        .iter()
104        .map(|s| {
105            let mean_dps = f64::from(s.mean_dps_x10) / PROTO_DPS_SCALE;
106            let winner_dps = f64::from(winner_dps_x10) / PROTO_DPS_SCALE;
107
108            PermutationSummaryView {
109                rank: s.rank,
110                mean_dps,
111                ci95_half: f64::from(s.ci95_half_x10) / PROTO_DPS_SCALE,
112                dps_delta_from_winner: mean_dps - winner_dps,
113                diffs: convert_slot_diffs(&s.diffs),
114            }
115        })
116        .collect()
117}
118
119pub(super) fn convert_slot_rankings(rankings: &[proto::SlotRanking]) -> Vec<SlotRankingView> {
120    rankings
121        .iter()
122        .map(|r| SlotRankingView {
123            slot_index: r.slot_index,
124            items: r
125                .items
126                .iter()
127                .map(|it| SlotItemEntryView {
128                    item_id: it.item_id,
129                    bonus_id_hash: it.bonus_id_hash,
130                    best_perm_dps: f64::from(it.best_perm_dps_x10) / PROTO_DPS_SCALE,
131                    best_perm_rank: it.best_perm_rank,
132                    ci95_half: f64::from(it.ci95_half_x10) / PROTO_DPS_SCALE,
133                    win_rate_pct: f64::from(it.win_rate_x100),
134                    marginal_dps_delta: f64::from(it.marginal_dps_delta_x10) / PROTO_DPS_SCALE,
135                })
136                .collect(),
137        })
138        .collect()
139}