Skip to content

Core Charting

Developer-facing API reference for the core.charting package.

Package

core.charting

Declarative chart configuration and rendering helpers.

Charts in the UI are driven by ChartConfig objects rather than bespoke view logic. This package contains the schema, validation, and rendering utilities used by the dashboard view.

Modules

core.charting.builder

Chart Builder helpers for generating runtime ChartConfig instances.

The Charts dashboard already supports declarative ChartConfig entries. Phase 7 adds a constrained Chart Builder that constructs ChartConfig values from a limited set of user selections, then validates them using the same validator used for built-in charts.

ChartBuilderChartType = Literal['line', 'bar', 'area', 'scatter', 'donut'] module-attribute

ChartBuilderComparison = Literal['none', 'before_after', 'run_vs_run'] module-attribute

ChartBuilderGroupBy = Literal['time', 'tier', 'preset'] module-attribute

ChartBuilderSmoothing = Literal['none', 'rolling_avg'] module-attribute

ChartBuilderXAxis = Literal['time', 'metric'] module-attribute

ChartCategory = Literal['economy', 'damage', 'enemy_destruction', 'efficiency', 'ultimate_weapons', 'guardians', 'bots', 'comparison', 'derived'] module-attribute

ChartDomain = Literal['economy', 'damage', 'enemy_destruction', 'efficiency'] module-attribute

DEFAULT_REGISTRY = MetricSeriesRegistry(specs=(MetricSeriesSpec(key='coins_earned', label='Coins earned', description=None, unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_earned', allowed_transforms=(frozenset({'none', 'rate_per_hour', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cash_earned', label='Cash earned', description=None, unit='cash', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cash_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='interest_earned', label='Interest earned', description='Observed interest earned from Battle Reports.', unit='cash', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='interest_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cells_earned', label='Cells earned', description=None, unit='cells', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cells_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='reroll_shards_earned', label='Reroll shards earned', description=None, unit='shards', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='reroll_shards_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='reroll_dice_earned', label='Reroll dice earned', description='Alias for reroll shards earned (legacy naming).', unit='shards', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='reroll_shards_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='waves_reached', label='Waves reached', description=None, unit='waves', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='wave', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_per_wave', label='Coins per wave', description='Computed as coins earned divided by waves reached.', unit='coins/wave', category=(MetricCategory.economy), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='coins_earned / wave', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_per_hour', label='Coins/hour', description='Observed coins earned divided by real time (hours).', unit='coins/hour', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='coins_per_hour', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cells_per_hour', label='Cells/hour', description='Observed cells earned divided by real time (hours).', unit='cells/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='cells_earned / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='reroll_shards_per_hour', label='Reroll shards/hour', description='Observed reroll shards earned divided by real time (hours).', unit='shards/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='reroll_shards_earned / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='real_time_hours', label='Run duration (hours)', description='Observed real-time duration converted to hours.', unit='hours', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='real_time_seconds / 3600', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='waves_per_hour', label='Waves/hour', description='Observed waves reached divided by real time (hours).', unit='waves/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='waves_reached / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_total', label='Enemies destroyed (derived total)', description='Derived from per-type counts; ignores game-reported totals.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(enemy_type_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_common', label='Enemies destroyed (common)', description='Derived from Basic, Fast, Ranged, Tank, and Protector counts.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(common_enemy_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_elite', label='Enemies destroyed (elite)', description='Derived from Vampire, Ray, and Scatter counts.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(elite_enemy_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_fleet', label='Enemies destroyed (fleet)', description='Derived from Saboteur, Commander, and Overcharge counts.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(fleet_enemy_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_per_hour', label='Enemies destroyed/hour', description='Enemies destroyed (derived total) divided by real time (hours).', unit='enemies/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='enemies_destroyed_total / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='damage_dealt', label='Damage dealt', description='Total damage dealt from Battle Reports.', unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='damage_dealt', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='projectiles_damage', label='Projectiles Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='projectiles_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='thorn_damage', label='Thorn Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='thorn_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='orb_damage', label='Orb Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='orb_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='land_mine_damage', label='Land Mine Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='land_mine_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='inner_land_mine_damage', label='Inner Land Mine Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='inner_land_mine_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='chain_lightning_damage', label='Chain Lightning Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='chain_lightning_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='death_wave_damage', label='Death Wave Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='death_wave_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='death_ray_damage', label='Death Ray Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='death_ray_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='smart_missile_damage', label='Smart Missile Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='smart_missile_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='black_hole_damage', label='Black Hole Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='black_hole_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='swamp_damage', label='Swamp Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='swamp_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='electrons_damage', label='Electrons Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='electrons_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='rend_armor_damage', label='Rend Armor Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='rend_armor_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_hit_by_orbs', label='Enemies Hit by Orbs', description=None, unit='enemies', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_hit_by_orbs', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_basic', label='Basic', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_basic', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_fast', label='Fast', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_fast', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_tank', label='Tank', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_tank', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_ranged', label='Ranged', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_ranged', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_boss', label='Boss', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_boss', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_protector', label='Protector', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_protector', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_vampires', label='Vampires', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_vampires', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_rays', label='Rays', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_rays', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_scatters', label='Scatters', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_scatters', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_saboteur', label='Saboteur', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_saboteur', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_commander', label='Commander', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_commander', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_overcharge', label='Overcharge', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_overcharge', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_orbs', label='Destroyed By Orbs', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_orbs', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_thorns', label='Destroyed by Thorns', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_thorns', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_death_ray', label='Destroyed by Death Ray', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_death_ray', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_land_mine', label='Destroyed by Land Mine', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_land_mine', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_in_spotlight', label='Destroyed in Spotlight', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_in_spotlight', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_in_golden_bot', label='Destroyed in Golden Bot', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_in_golden_bot', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='uw_runs_count', label='Runs using selected ultimate weapon', description=None, unit='runs', category=(MetricCategory.utility), kind='observed', source_model='RunCombat', aggregation='sum', time_index='timestamp', value_field='ultimate_weapon_present', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw'}))), MetricSeriesSpec(key='guardian_runs_count', label='Runs using selected guardian chip', description=None, unit='runs', category=(MetricCategory.utility), kind='observed', source_model='RunGuardian', aggregation='sum', time_index='timestamp', value_field='guardian_chip_present', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'guardian'}))), MetricSeriesSpec(key='bot_runs_count', label='Runs using selected bot', description=None, unit='runs', category=(MetricCategory.utility), kind='observed', source_model='RunBots', aggregation='sum', time_index='timestamp', value_field='bot_present', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'bot'}))), MetricSeriesSpec(key='uw_uptime_percent', label='Ultimate Weapon uptime', description='Derived uptime percent for the selected Ultimate Weapon.', unit='percent', category=(MetricCategory.utility), kind='derived', source_model='RunCombat', aggregation='avg', time_index='timestamp', value_field='uw_uptime_percent', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw'}))), MetricSeriesSpec(key='guardian_activations_per_minute', label='Guardian activations/minute', description='Derived activations/minute for the selected Guardian Chip.', unit='activations/min', category=(MetricCategory.utility), kind='derived', source_model='RunGuardian', aggregation='avg', time_index='timestamp', value_field='guardian_activations_per_minute', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'guardian'}))), MetricSeriesSpec(key='uw_effective_cooldown_seconds', label='Ultimate Weapon effective cooldown', description='Derived cooldown seconds for the selected Ultimate Weapon.', unit='seconds', category=(MetricCategory.utility), kind='derived', source_model='RunCombat', aggregation='avg', time_index='timestamp', value_field='uw_effective_cooldown_seconds', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw'}))), MetricSeriesSpec(key='bot_uptime_percent', label='Bot uptime', description='Derived uptime percent for the selected Bot.', unit='percent', category=(MetricCategory.utility), kind='derived', source_model='RunBots', aggregation='avg', time_index='timestamp', value_field='bot_uptime_percent', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'bot'}))), MetricSeriesSpec(key='cooldown_reduction_effective', label='Effective cooldown', description='Entity-scoped effective cooldown in seconds (placeholder for future reduction modeling).', unit='seconds', category=(MetricCategory.utility), kind='derived', source_model='RunCombat', aggregation='avg', time_index='timestamp', value_field='effective_cooldown_seconds', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw', 'guardian', 'bot'}))), MetricSeriesSpec(key='coins_from_death_wave', label='Coins From Death Wave', description='Battle Report utility breakdown: coins earned from Death Wave.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_death_wave', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_golden_tower', label='Coins From Golden Tower', description='Battle Report utility breakdown: coins earned from Golden Tower.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_golden_tower', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cash_from_golden_tower', label='Cash From Golden Tower', description='Battle Report utility breakdown: cash earned from Golden Tower.', unit='cash', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cash_from_golden_tower', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cash_from_other_sources', label='Other cash', description='Residual cash not covered by named sources (derived).', unit='cash', category=(MetricCategory.economy), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cash_earned - cash_from_golden_tower - interest_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_black_hole', label='Coins From Black Hole', description='Battle Report utility breakdown: coins earned from Black Hole.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_black_hole', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_spotlight', label='Coins From Spotlight', description='Battle Report utility breakdown: coins earned from Spotlight.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_spotlight', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_orb', label='Coins From Orb', description='Battle Report utility breakdown: coins earned from Orbs.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_orb', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_coin_upgrade', label='Coins from Coin Upgrade', description='Battle Report utility breakdown: coins earned from coin upgrades.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_coin_upgrade', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_coin_bonuses', label='Coins from Coin Bonuses', description='Battle Report utility breakdown: coins earned from coin bonuses.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_coin_bonuses', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_attack_upgrades', label='Free Attack Upgrade', description='Battle Report utility breakdown: free attack upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_attack_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_defense_upgrades', label='Free Defense Upgrade', description='Battle Report utility breakdown: free defense upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_defense_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_utility_upgrades', label='Free Utility Upgrade', description='Battle Report utility breakdown: free utility upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_utility_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_upgrades_total', label='Free Upgrades (Total)', description='Derived total of free attack, defense, and utility upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_attack_upgrades + free_defense_upgrades + free_utility_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='recovery_packages', label='Recovery Packages', description='Battle Report derived metrics: recovery packages.', unit='count', category=(MetricCategory.utility), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='recovery_packages', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_other_sources', label='Other coins', description='Residual coins not covered by named sources; ensures sources sum to total coins earned.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_other_sources', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_damage', label='Guardian Damage', description='Battle Report Guardian section: damage dealt by the Guardian.', unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='guardian_damage', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_summoned_enemies', label='Guardian Summoned Enemies', description='Battle Report Guardian section: summoned enemies count.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='guardian_summoned_enemies', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_coins_stolen', label='Guardian coins stolen', description='Battle Report Guardian section: coins stolen (rolls up into Coins Earned by Source).', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_coins_stolen', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_coins_fetched', label='Coins Fetched', description='Battle Report Guardian section: coins fetched (rolls up into Coins Earned by Source).', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_coins_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_gems_fetched', label='Gems', description='Battle Report Guardian section: gems fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_gems_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_medals_fetched', label='Medals', description='Battle Report Guardian section: medals fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_medals_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_reroll_shards_fetched', label='Reroll Shards', description='Battle Report Guardian section: reroll shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_reroll_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_cannon_shards_fetched', label='Cannon Shards', description='Battle Report Guardian section: cannon shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_cannon_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_armor_shards_fetched', label='Armor Shards', description='Battle Report Guardian section: armor shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_armor_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_generator_shards_fetched', label='Generator Shards', description='Battle Report Guardian section: generator shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_generator_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_core_shards_fetched', label='Core Shards', description='Battle Report Guardian section: core shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_core_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_common_modules_fetched', label='Common Modules', description='Battle Report Guardian section: common modules fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_common_modules_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_rare_modules_fetched', label='Rare Modules', description='Battle Report Guardian section: rare modules fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_rare_modules_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='flame_bot_damage', label='Flame Bot Damage', description='Battle Report bot section: damage dealt by Flame Bot.', unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='flame_bot_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='thunder_bot_stuns', label='Thunder Bot Stuns', description='Battle Report bot section: stuns delivered by Thunder Bot.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='thunder_bot_stuns', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='golden_bot_coins_earned', label='Golden Bot Coins Earned', description='Battle Report bot section: coins earned by Golden Bot.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='golden_bot_coins_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS))) module-attribute

ChartBuilderSelection dataclass

Validated, typed selections used to build a runtime chart.

Parameters:

Name Type Description Default
metric_keys tuple[str, ...]

One or more MetricSeries keys selected by the user.

required
chart_type ChartBuilderChartType

The chart type requested by the user.

required
group_by ChartBuilderGroupBy

Grouping control for splitting the chart (tier/preset).

required
comparison ChartBuilderComparison

Optional two-scope comparison mode.

required
smoothing ChartBuilderSmoothing

Optional smoothing control (rolling average).

required
x_axis ChartBuilderXAxis

X-axis mode ("time" or "metric").

required
aggregation Literal['sum', 'avg'] | None

Optional aggregation override ("sum" or "avg").

None
scope_a ComparisonScope | None

Scope A when comparison is enabled.

None
scope_b ComparisonScope | None

Scope B when comparison is enabled.

None

ChartComparison dataclass

Optional comparison behavior for a chart configuration.

The comparison layer supports two styles:

  • Grouping comparisons (by tier/preset) that split a single scope into multiple datasets.
  • Two-scope comparisons (before/after, run vs run) that split the same config into exactly two datasets.

ChartConfig dataclass

Declarative chart definition for the dashboard.

Parameters:

Name Type Description Default
id str

Stable, unique identifier used by the dashboard selection control.

required
title str

Chart title displayed in the UI.

required
description str | None

Optional chart description shown in selection lists/tooltips.

required
category ChartCategory

Used for grouping charts in the selection UI.

required
domain ChartDomain

Taxonomy domain for the chart ("economy", "damage", "enemy_destruction", "efficiency").

required
semantic_type ChartSemanticType

Semantic chart type used for guardrails and validation.

required
chart_type ChartType

The visual chart type.

required
metric_series tuple[ChartSeriesConfig, ...]

One or more metric series definitions that feed the chart.

required
filters ChartFilters

Filter toggles, including default date range behavior.

required
comparison ChartComparison | None

Optional comparison behavior (generates multiple datasets).

None
derived ChartDerived | None

Optional derived metric definition (computed from series inputs).

None
multi_axis bool

Whether the chart may display multiple y-axes (comparative charts only).

False
donut_value_mode Literal['raw', 'percent']

When chart_type="donut", choose whether the donut uses raw values or percentages.

'raw'
default_granularity ChartGranularity

Default x-axis granularity used by the dashboard.

'daily'
stacked bool

Whether a bar chart renders as stacked bars.

False
ui ChartUI

UI behavior flags (default selection, ordering, etc).

ChartUI(show_by_default=False, selectable=True, order=999)

ChartFilters dataclass

Filter toggles for a chart configuration.

ChartSeriesConfig dataclass

A single metric series used by a chart.

Parameters:

Name Type Description Default
metric_key str

Registered MetricSeries key.

required
label str | None

Optional override label for the series.

None
transform MetricTransform

Optional transform applied to the series values.

'none'

ChartUI dataclass

UI presentation hints for charts.

ComparisonScope dataclass

A concrete scope used by two-scope chart comparisons.

Parameters:

Name Type Description Default
label str

Display label for the scope (shown in chart legends).

required
run_id int | None

Optional BattleReport id for run-vs-run comparisons.

None
start_date date | None

Optional inclusive window start (used by before/after).

None
end_date date | None

Optional inclusive window end (used by before/after).

None

DateRangeFilterConfig dataclass

Configure an optional date-range filter for a chart.

Parameters:

Name Type Description Default
enabled bool

Whether the chart accepts date range filtering.

required
default_start datetime

Default lower bound for the date range.

required

MetricCategory

Bases: StrEnum

Semantic category for a metric.

Values are stable identifiers used across the analysis registry and UI.

SimpleFilterConfig dataclass

Configure a simple boolean-enabled filter.

_domain_for_category(category)

Map a MetricCategory to a ChartDomain.

_infer_domain(metric_keys)

Infer a chart domain for Chart Builder selections.

Parameters:

Name Type Description Default
metric_keys tuple[str, ...]

MetricSeries keys selected in the builder.

required

Returns:

Type Description
ChartDomain

Best-effort ChartDomain based on the registered metric categories.

build_before_after_scopes(*, window_a_start, window_a_end, window_b_start, window_b_end, label_a='Window A', label_b='Window B')

Build ComparisonScope values for a before/after chart comparison.

Parameters:

Name Type Description Default
window_a_start date

Inclusive start for scope A.

required
window_a_end date

Inclusive end for scope A.

required
window_b_start date

Inclusive start for scope B.

required
window_b_end date

Inclusive end for scope B.

required
label_a str

Display label for scope A.

'Window A'
label_b str

Display label for scope B.

'Window B'

Returns:

Type Description
tuple[ComparisonScope, ComparisonScope]

Two ComparisonScope objects (A, B).

build_run_vs_run_scopes(*, run_a_id, run_b_id, label_a='Run A', label_b='Run B')

Build ComparisonScope values for a run-vs-run chart comparison.

Parameters:

Name Type Description Default
run_a_id int

BattleReport id for scope A.

required
run_b_id int

BattleReport id for scope B.

required
label_a str

Display label for scope A.

'Run A'
label_b str

Display label for scope B.

'Run B'

Returns:

Type Description
tuple[ComparisonScope, ComparisonScope]

Two ComparisonScope objects (A, B).

build_runtime_chart_config(selection)

Build a runtime ChartConfig from a Chart Builder selection.

Parameters:

Name Type Description Default
selection ChartBuilderSelection

Validated chart builder selections.

required

Returns:

Type Description
ChartConfig

ChartConfig compatible with the existing dashboard renderer.

core.charting.configs

Built-in ChartConfig definitions for the Charts dashboard.

CHART_CONFIGS = (ChartConfig(id='coins_earned', title='Coins Earned', description=None, category='economy', domain='economy', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='coins_earned'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=True, selectable=True, order=1))), ChartConfig(id='coins_per_hour', title='Coins per Hour', description=None, category='efficiency', domain='efficiency', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='coins_per_hour'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=True, selectable=True, order=2))), ChartConfig(id='coins_earned_over_time', title='Coins Earned Over Time', description='Area view of coins earned across the current filters.', category='economy', domain='economy', semantic_type='absolute', chart_type='area', metric_series=(ChartSeriesConfig(metric_key='coins_earned'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=8))), ChartConfig(id='coins_by_source', title='Coins Earned by Source', description='Breakdown of observed coin sources from Battle Reports (aggregated within the current filters).', category='economy', domain='economy', semantic_type='distribution', chart_type='donut', metric_series=(ChartSeriesConfig(metric_key='coins_from_death_wave'), ChartSeriesConfig(metric_key='coins_from_golden_tower'), ChartSeriesConfig(metric_key='coins_from_black_hole'), ChartSeriesConfig(metric_key='coins_from_spotlight'), ChartSeriesConfig(metric_key='coins_from_orb'), ChartSeriesConfig(metric_key='coins_from_coin_upgrade'), ChartSeriesConfig(metric_key='coins_from_coin_bonuses'), ChartSeriesConfig(metric_key='guardian_coins_stolen'), ChartSeriesConfig(metric_key='guardian_coins_fetched'), ChartSeriesConfig(metric_key='coins_from_other_sources')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=True, selectable=True, order=3))), ChartConfig(id='coins_from_ultimate_weapons', title='Coins From Ultimate Weapons', description='Breakdown of coin sources attributed to Ultimate Weapons (aggregated within the current filters).', category='economy', domain='economy', semantic_type='distribution', chart_type='donut', metric_series=(ChartSeriesConfig(metric_key='coins_from_death_wave'), ChartSeriesConfig(metric_key='coins_from_golden_tower'), ChartSeriesConfig(metric_key='coins_from_black_hole'), ChartSeriesConfig(metric_key='coins_from_spotlight'), ChartSeriesConfig(metric_key='coins_from_orb')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=4))), ChartConfig(id='damage_by_source', title='Damage by Source', description='Breakdown of damage sources from Battle Reports (aggregated within the current filters).', category='damage', domain='damage', semantic_type='distribution', chart_type='donut', metric_series=(ChartSeriesConfig(metric_key='projectiles_damage'), ChartSeriesConfig(metric_key='thorn_damage'), ChartSeriesConfig(metric_key='orb_damage'), ChartSeriesConfig(metric_key='land_mine_damage'), ChartSeriesConfig(metric_key='inner_land_mine_damage'), ChartSeriesConfig(metric_key='chain_lightning_damage'), ChartSeriesConfig(metric_key='death_wave_damage'), ChartSeriesConfig(metric_key='death_ray_damage'), ChartSeriesConfig(metric_key='smart_missile_damage'), ChartSeriesConfig(metric_key='black_hole_damage'), ChartSeriesConfig(metric_key='swamp_damage'), ChartSeriesConfig(metric_key='electrons_damage'), ChartSeriesConfig(metric_key='rend_armor_damage')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=True, selectable=True, order=5))), ChartConfig(id='damage_percent_by_source', title='% of Total Damage by Source', description='Percent contribution of each damage source (aggregated within the current filters).', category='damage', domain='damage', semantic_type='contribution', chart_type='donut', donut_value_mode='percent', metric_series=(ChartSeriesConfig(metric_key='projectiles_damage'), ChartSeriesConfig(metric_key='thorn_damage'), ChartSeriesConfig(metric_key='orb_damage'), ChartSeriesConfig(metric_key='land_mine_damage'), ChartSeriesConfig(metric_key='inner_land_mine_damage'), ChartSeriesConfig(metric_key='chain_lightning_damage'), ChartSeriesConfig(metric_key='death_wave_damage'), ChartSeriesConfig(metric_key='death_ray_damage'), ChartSeriesConfig(metric_key='smart_missile_damage'), ChartSeriesConfig(metric_key='black_hole_damage'), ChartSeriesConfig(metric_key='swamp_damage'), ChartSeriesConfig(metric_key='electrons_damage'), ChartSeriesConfig(metric_key='rend_armor_damage')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=6))), ChartConfig(id='damage_vs_enemies_destroyed', title='Damage vs Enemies Destroyed', description='Compares total damage and derived enemies destroyed across the current filters.', category='damage', domain='damage', semantic_type='comparative', multi_axis=True, chart_type='line', metric_series=(ChartSeriesConfig(metric_key='damage_dealt'), ChartSeriesConfig(metric_key='enemies_destroyed_total')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=7))), ChartConfig(id='orb_effectiveness', title='Orb Effectiveness', description='Orb damage and related enemy interactions (hits and destroys).', category='damage', domain='damage', semantic_type='comparative', multi_axis=True, chart_type='line', metric_series=(ChartSeriesConfig(metric_key='orb_damage'), ChartSeriesConfig(metric_key='enemies_hit_by_orbs'), ChartSeriesConfig(metric_key='enemies_destroyed_by_orbs')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=8))), ChartConfig(id='free_upgrades_by_run', title='Free Upgrades by Run', description='Stacked breakdown of free attack, defense, and utility upgrades per run. Total appears in the tooltip and header badge.', category='economy', domain='economy', semantic_type='absolute', chart_type='bar', stacked=True, default_granularity='per_run', metric_series=(ChartSeriesConfig(metric_key='free_attack_upgrades', label='Attack'), ChartSeriesConfig(metric_key='free_defense_upgrades', label='Defense'), ChartSeriesConfig(metric_key='free_utility_upgrades', label='Utility')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=True, selectable=True, order=6))), ChartConfig(id='free_upgrades_vs_coins_earned', title='Free Upgrades vs Coins Earned', description='Scatter of total free upgrades against total coins earned.', category='economy', domain='economy', semantic_type='comparative', chart_type='scatter', default_granularity='per_run', metric_series=(ChartSeriesConfig(metric_key='free_upgrades_total', label='Free upgrades'), ChartSeriesConfig(metric_key='coins_earned', label='Coins earned')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=9))), ChartConfig(id='run_duration_vs_coins_earned', title='Run Duration vs Coins Earned', description='Scatter of real-time run duration against total coins earned.', category='efficiency', domain='efficiency', semantic_type='comparative', chart_type='scatter', default_granularity='per_run', metric_series=(ChartSeriesConfig(metric_key='real_time_hours', label='Run duration'), ChartSeriesConfig(metric_key='coins_earned', label='Coins earned')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=True, selectable=True, order=7))), ChartConfig(id='cash_earned', title='Cash Earned', description=None, category='economy', domain='economy', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='cash_earned'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=10))), ChartConfig(id='cash_by_source', title='Cash by Source', description='Cash sources from Battle Reports (aggregated within the current filters).', category='economy', domain='economy', semantic_type='distribution', chart_type='donut', metric_series=(ChartSeriesConfig(metric_key='cash_from_golden_tower'), ChartSeriesConfig(metric_key='interest_earned'), ChartSeriesConfig(metric_key='cash_from_other_sources')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=11))), ChartConfig(id='cells_earned', title='Cells Earned', description=None, category='economy', domain='economy', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='cells_earned'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=12))), ChartConfig(id='reroll_shards_earned', title='Reroll Shards Earned', description=None, category='economy', domain='economy', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='reroll_shards_earned'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=13))), ChartConfig(id='waves_per_hour', title='Waves per Hour', description='Waves reached normalized by real time (hours).', category='efficiency', domain='efficiency', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='waves_per_hour'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=14))), ChartConfig(id='enemies_destroyed_per_hour', title='Enemies Destroyed per Hour', description='Enemies destroyed normalized by real time (hours).', category='efficiency', domain='efficiency', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='enemies_destroyed_per_hour'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=15))), ChartConfig(id='enemies_destroyed_by_source', title='Enemies Destroyed by Source', description='Breakdown of destruction sources from Battle Reports (aggregated within the current filters).', category='enemy_destruction', domain='enemy_destruction', semantic_type='distribution', chart_type='donut', metric_series=(ChartSeriesConfig(metric_key='enemies_destroyed_by_orbs'), ChartSeriesConfig(metric_key='enemies_destroyed_by_thorns'), ChartSeriesConfig(metric_key='enemies_destroyed_by_death_ray'), ChartSeriesConfig(metric_key='enemies_destroyed_by_land_mine'), ChartSeriesConfig(metric_key='enemies_destroyed_in_spotlight'), ChartSeriesConfig(metric_key='enemies_destroyed_in_golden_bot')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=16))), ChartConfig(id='enemy_type_distribution', title='Enemy Type Distribution', description='Distribution of destroyed enemy types from Battle Reports (aggregated within the current filters).', category='enemy_destruction', domain='enemy_destruction', semantic_type='distribution', chart_type='donut', metric_series=(ChartSeriesConfig(metric_key='enemies_destroyed_basic'), ChartSeriesConfig(metric_key='enemies_destroyed_fast'), ChartSeriesConfig(metric_key='enemies_destroyed_tank'), ChartSeriesConfig(metric_key='enemies_destroyed_ranged'), ChartSeriesConfig(metric_key='enemies_destroyed_boss'), ChartSeriesConfig(metric_key='enemies_destroyed_protector'), ChartSeriesConfig(metric_key='enemies_destroyed_vampires'), ChartSeriesConfig(metric_key='enemies_destroyed_rays'), ChartSeriesConfig(metric_key='enemies_destroyed_scatters'), ChartSeriesConfig(metric_key='enemies_destroyed_saboteur'), ChartSeriesConfig(metric_key='enemies_destroyed_commander'), ChartSeriesConfig(metric_key='enemies_destroyed_overcharge')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=17))), ChartConfig(id='uw_runs_count', title='Runs Using Selected UW', description='Count of runs where the selected Ultimate Weapon is present.', category='ultimate_weapons', domain='efficiency', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='uw_runs_count'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)), uw=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=20))), ChartConfig(id='guardian_runs_count', title='Runs Using Selected Guardian Chip', description='Count of runs where the selected Guardian Chip is present.', category='guardians', domain='efficiency', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='guardian_runs_count'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)), guardian=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=30))), ChartConfig(id='guardian_fetch_breakdown', title='Guardian Fetch Metrics', description='Breakdown of non-coin Guardian fetch outputs (aggregated within the current filters).', category='guardians', domain='economy', semantic_type='distribution', chart_type='donut', metric_series=(ChartSeriesConfig(metric_key='guardian_gems_fetched'), ChartSeriesConfig(metric_key='guardian_medals_fetched'), ChartSeriesConfig(metric_key='guardian_reroll_shards_fetched'), ChartSeriesConfig(metric_key='guardian_cannon_shards_fetched'), ChartSeriesConfig(metric_key='guardian_armor_shards_fetched'), ChartSeriesConfig(metric_key='guardian_generator_shards_fetched'), ChartSeriesConfig(metric_key='guardian_core_shards_fetched'), ChartSeriesConfig(metric_key='guardian_common_modules_fetched'), ChartSeriesConfig(metric_key='guardian_rare_modules_fetched')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=31))), ChartConfig(id='bot_runs_count', title='Runs Using Selected Bot', description='Count of runs where the selected Bot is present.', category='bots', domain='efficiency', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='bot_runs_count'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)), bot=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=40))), ChartConfig(id='coins_earned_by_tier', title='Coins Earned (Compare Tiers)', description=None, category='comparison', domain='economy', semantic_type='comparative', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='coins_earned'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), preset=(SimpleFilterConfig(enabled=True)))), comparison=(ChartComparison(mode='by_tier')), ui=(ChartUI(show_by_default=False, selectable=True, order=50))), ChartConfig(id='coins_per_hour_by_preset', title='Coins per Hour (Compare Presets)', description=None, category='comparison', domain='efficiency', semantic_type='comparative', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='coins_per_hour'),), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)))), comparison=(ChartComparison(mode='by_preset')), ui=(ChartUI(show_by_default=False, selectable=True, order=51))), ChartConfig(id='coins_per_hour_moving_average', title='Coins per Hour (Moving Average)', description='Moving average uses the dashboard-level window setting.', category='comparison', domain='efficiency', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='coins_per_hour', label='Coins per Hour'), ChartSeriesConfig(metric_key='coins_per_hour', label='Moving Average', transform='moving_average')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=52))), ChartConfig(id='coins_per_wave', title='Coins per Wave', description='Derived from coins earned divided by waves reached.', category='derived', domain='economy', semantic_type='absolute', chart_type='line', metric_series=(ChartSeriesConfig(metric_key='coins_earned'), ChartSeriesConfig(metric_key='waves_reached')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), derived=(ChartDerived(formula='coins_earned / waves_reached', x_axis='time')), ui=(ChartUI(show_by_default=False, selectable=True, order=60))), ChartConfig(id='damage_percent_by_source_stacked', title='% Damage by Source (Per Run)', description='Percent contribution of each damage source per run (100% stacked).', category='damage', domain='damage', semantic_type='contribution', chart_type='bar', stacked=True, default_granularity='per_run', metric_series=(ChartSeriesConfig(metric_key='projectiles_damage'), ChartSeriesConfig(metric_key='thorn_damage'), ChartSeriesConfig(metric_key='orb_damage'), ChartSeriesConfig(metric_key='land_mine_damage'), ChartSeriesConfig(metric_key='inner_land_mine_damage'), ChartSeriesConfig(metric_key='chain_lightning_damage'), ChartSeriesConfig(metric_key='death_wave_damage'), ChartSeriesConfig(metric_key='death_ray_damage'), ChartSeriesConfig(metric_key='smart_missile_damage'), ChartSeriesConfig(metric_key='black_hole_damage'), ChartSeriesConfig(metric_key='swamp_damage'), ChartSeriesConfig(metric_key='electrons_damage'), ChartSeriesConfig(metric_key='rend_armor_damage')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=18))), ChartConfig(id='damage_vs_enemies_destroyed_bar', title='Damage vs Enemies Destroyed (Bar)', description='Side-by-side bars per run for damage dealt and enemies destroyed.', category='damage', domain='damage', semantic_type='comparative', chart_type='bar', multi_axis=True, default_granularity='per_run', metric_series=(ChartSeriesConfig(metric_key='damage_dealt'), ChartSeriesConfig(metric_key='enemies_destroyed_total')), filters=(ChartFilters(date_range=(DateRangeFilterConfig(enabled=True, default_start=DEFAULT_START)), tier=(SimpleFilterConfig(enabled=True)), preset=(SimpleFilterConfig(enabled=True)))), ui=(ChartUI(show_by_default=False, selectable=True, order=19)))) module-attribute

CHART_CONFIG_BY_ID = {(config.id): config for config in CHART_CONFIGS} module-attribute

DEFAULT_REGISTRY = MetricSeriesRegistry(specs=(MetricSeriesSpec(key='coins_earned', label='Coins earned', description=None, unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_earned', allowed_transforms=(frozenset({'none', 'rate_per_hour', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cash_earned', label='Cash earned', description=None, unit='cash', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cash_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='interest_earned', label='Interest earned', description='Observed interest earned from Battle Reports.', unit='cash', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='interest_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cells_earned', label='Cells earned', description=None, unit='cells', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cells_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='reroll_shards_earned', label='Reroll shards earned', description=None, unit='shards', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='reroll_shards_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='reroll_dice_earned', label='Reroll dice earned', description='Alias for reroll shards earned (legacy naming).', unit='shards', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='reroll_shards_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='waves_reached', label='Waves reached', description=None, unit='waves', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='wave', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_per_wave', label='Coins per wave', description='Computed as coins earned divided by waves reached.', unit='coins/wave', category=(MetricCategory.economy), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='coins_earned / wave', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_per_hour', label='Coins/hour', description='Observed coins earned divided by real time (hours).', unit='coins/hour', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='coins_per_hour', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cells_per_hour', label='Cells/hour', description='Observed cells earned divided by real time (hours).', unit='cells/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='cells_earned / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='reroll_shards_per_hour', label='Reroll shards/hour', description='Observed reroll shards earned divided by real time (hours).', unit='shards/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='reroll_shards_earned / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='real_time_hours', label='Run duration (hours)', description='Observed real-time duration converted to hours.', unit='hours', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='real_time_seconds / 3600', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='waves_per_hour', label='Waves/hour', description='Observed waves reached divided by real time (hours).', unit='waves/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='waves_reached / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_total', label='Enemies destroyed (derived total)', description='Derived from per-type counts; ignores game-reported totals.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(enemy_type_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_common', label='Enemies destroyed (common)', description='Derived from Basic, Fast, Ranged, Tank, and Protector counts.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(common_enemy_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_elite', label='Enemies destroyed (elite)', description='Derived from Vampire, Ray, and Scatter counts.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(elite_enemy_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_fleet', label='Enemies destroyed (fleet)', description='Derived from Saboteur, Commander, and Overcharge counts.', unit='enemies', category=(MetricCategory.enemy_destruction), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='sum(fleet_enemy_counts)', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average', 'rate_per_hour'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_per_hour', label='Enemies destroyed/hour', description='Enemies destroyed (derived total) divided by real time (hours).', unit='enemies/hour', category=(MetricCategory.efficiency), kind='derived', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='enemies_destroyed_total / hours', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='damage_dealt', label='Damage dealt', description='Total damage dealt from Battle Reports.', unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='damage_dealt', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='projectiles_damage', label='Projectiles Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='projectiles_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='thorn_damage', label='Thorn Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='thorn_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='orb_damage', label='Orb Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='orb_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='land_mine_damage', label='Land Mine Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='land_mine_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='inner_land_mine_damage', label='Inner Land Mine Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='inner_land_mine_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='chain_lightning_damage', label='Chain Lightning Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='chain_lightning_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='death_wave_damage', label='Death Wave Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='death_wave_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='death_ray_damage', label='Death Ray Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='death_ray_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='smart_missile_damage', label='Smart Missile Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='smart_missile_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='black_hole_damage', label='Black Hole Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='black_hole_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='swamp_damage', label='Swamp Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='swamp_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='electrons_damage', label='Electrons Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='electrons_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='rend_armor_damage', label='Rend Armor Damage', description=None, unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='rend_armor_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_hit_by_orbs', label='Enemies Hit by Orbs', description=None, unit='enemies', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_hit_by_orbs', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_basic', label='Basic', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_basic', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_fast', label='Fast', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_fast', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_tank', label='Tank', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_tank', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_ranged', label='Ranged', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_ranged', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_boss', label='Boss', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_boss', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_protector', label='Protector', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_protector', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_vampires', label='Vampires', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_vampires', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_rays', label='Rays', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_rays', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_scatters', label='Scatters', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_scatters', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_saboteur', label='Saboteur', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_saboteur', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_commander', label='Commander', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_commander', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_overcharge', label='Overcharge', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_overcharge', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_orbs', label='Destroyed By Orbs', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_orbs', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_thorns', label='Destroyed by Thorns', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_thorns', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_death_ray', label='Destroyed by Death Ray', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_death_ray', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_land_mine', label='Destroyed by Land Mine', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_land_mine', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_in_spotlight', label='Destroyed in Spotlight', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_in_spotlight', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_in_golden_bot', label='Destroyed in Golden Bot', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_in_golden_bot', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='uw_runs_count', label='Runs using selected ultimate weapon', description=None, unit='runs', category=(MetricCategory.utility), kind='observed', source_model='RunCombat', aggregation='sum', time_index='timestamp', value_field='ultimate_weapon_present', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw'}))), MetricSeriesSpec(key='guardian_runs_count', label='Runs using selected guardian chip', description=None, unit='runs', category=(MetricCategory.utility), kind='observed', source_model='RunGuardian', aggregation='sum', time_index='timestamp', value_field='guardian_chip_present', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'guardian'}))), MetricSeriesSpec(key='bot_runs_count', label='Runs using selected bot', description=None, unit='runs', category=(MetricCategory.utility), kind='observed', source_model='RunBots', aggregation='sum', time_index='timestamp', value_field='bot_present', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'bot'}))), MetricSeriesSpec(key='uw_uptime_percent', label='Ultimate Weapon uptime', description='Derived uptime percent for the selected Ultimate Weapon.', unit='percent', category=(MetricCategory.utility), kind='derived', source_model='RunCombat', aggregation='avg', time_index='timestamp', value_field='uw_uptime_percent', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw'}))), MetricSeriesSpec(key='guardian_activations_per_minute', label='Guardian activations/minute', description='Derived activations/minute for the selected Guardian Chip.', unit='activations/min', category=(MetricCategory.utility), kind='derived', source_model='RunGuardian', aggregation='avg', time_index='timestamp', value_field='guardian_activations_per_minute', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'guardian'}))), MetricSeriesSpec(key='uw_effective_cooldown_seconds', label='Ultimate Weapon effective cooldown', description='Derived cooldown seconds for the selected Ultimate Weapon.', unit='seconds', category=(MetricCategory.utility), kind='derived', source_model='RunCombat', aggregation='avg', time_index='timestamp', value_field='uw_effective_cooldown_seconds', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw'}))), MetricSeriesSpec(key='bot_uptime_percent', label='Bot uptime', description='Derived uptime percent for the selected Bot.', unit='percent', category=(MetricCategory.utility), kind='derived', source_model='RunBots', aggregation='avg', time_index='timestamp', value_field='bot_uptime_percent', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'bot'}))), MetricSeriesSpec(key='cooldown_reduction_effective', label='Effective cooldown', description='Entity-scoped effective cooldown in seconds (placeholder for future reduction modeling).', unit='seconds', category=(MetricCategory.utility), kind='derived', source_model='RunCombat', aggregation='avg', time_index='timestamp', value_field='effective_cooldown_seconds', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=(frozenset({'date_range', 'tier', 'preset', 'uw', 'guardian', 'bot'}))), MetricSeriesSpec(key='coins_from_death_wave', label='Coins From Death Wave', description='Battle Report utility breakdown: coins earned from Death Wave.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_death_wave', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_golden_tower', label='Coins From Golden Tower', description='Battle Report utility breakdown: coins earned from Golden Tower.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_golden_tower', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cash_from_golden_tower', label='Cash From Golden Tower', description='Battle Report utility breakdown: cash earned from Golden Tower.', unit='cash', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cash_from_golden_tower', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cash_from_other_sources', label='Other cash', description='Residual cash not covered by named sources (derived).', unit='cash', category=(MetricCategory.economy), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='cash_earned - cash_from_golden_tower - interest_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_black_hole', label='Coins From Black Hole', description='Battle Report utility breakdown: coins earned from Black Hole.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_black_hole', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_spotlight', label='Coins From Spotlight', description='Battle Report utility breakdown: coins earned from Spotlight.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_spotlight', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_orb', label='Coins From Orb', description='Battle Report utility breakdown: coins earned from Orbs.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_orb', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_coin_upgrade', label='Coins from Coin Upgrade', description='Battle Report utility breakdown: coins earned from coin upgrades.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_coin_upgrade', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_coin_bonuses', label='Coins from Coin Bonuses', description='Battle Report utility breakdown: coins earned from coin bonuses.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_coin_bonuses', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_attack_upgrades', label='Free Attack Upgrade', description='Battle Report utility breakdown: free attack upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_attack_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_defense_upgrades', label='Free Defense Upgrade', description='Battle Report utility breakdown: free defense upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_defense_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_utility_upgrades', label='Free Utility Upgrade', description='Battle Report utility breakdown: free utility upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_utility_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='free_upgrades_total', label='Free Upgrades (Total)', description='Derived total of free attack, defense, and utility upgrades.', unit='upgrades', category=(MetricCategory.economy), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='free_attack_upgrades + free_defense_upgrades + free_utility_upgrades', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='recovery_packages', label='Recovery Packages', description='Battle Report derived metrics: recovery packages.', unit='count', category=(MetricCategory.utility), kind='derived', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='recovery_packages', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_other_sources', label='Other coins', description='Residual coins not covered by named sources; ensures sources sum to total coins earned.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_other_sources', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_damage', label='Guardian Damage', description='Battle Report Guardian section: damage dealt by the Guardian.', unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='guardian_damage', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_summoned_enemies', label='Guardian Summoned Enemies', description='Battle Report Guardian section: summoned enemies count.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='guardian_summoned_enemies', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_coins_stolen', label='Guardian coins stolen', description='Battle Report Guardian section: coins stolen (rolls up into Coins Earned by Source).', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_coins_stolen', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_coins_fetched', label='Coins Fetched', description='Battle Report Guardian section: coins fetched (rolls up into Coins Earned by Source).', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_coins_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_gems_fetched', label='Gems', description='Battle Report Guardian section: gems fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_gems_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_medals_fetched', label='Medals', description='Battle Report Guardian section: medals fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_medals_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_reroll_shards_fetched', label='Reroll Shards', description='Battle Report Guardian section: reroll shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_reroll_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_cannon_shards_fetched', label='Cannon Shards', description='Battle Report Guardian section: cannon shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_cannon_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_armor_shards_fetched', label='Armor Shards', description='Battle Report Guardian section: armor shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_armor_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_generator_shards_fetched', label='Generator Shards', description='Battle Report Guardian section: generator shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_generator_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_core_shards_fetched', label='Core Shards', description='Battle Report Guardian section: core shards fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_core_shards_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_common_modules_fetched', label='Common Modules', description='Battle Report Guardian section: common modules fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_common_modules_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='guardian_rare_modules_fetched', label='Rare Modules', description='Battle Report Guardian section: rare modules fetched.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='guardian_rare_modules_fetched', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='flame_bot_damage', label='Flame Bot Damage', description='Battle Report bot section: damage dealt by Flame Bot.', unit='damage', category=(MetricCategory.damage), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='flame_bot_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='thunder_bot_stuns', label='Thunder Bot Stuns', description='Battle Report bot section: stuns delivered by Thunder Bot.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='thunder_bot_stuns', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='golden_bot_coins_earned', label='Golden Bot Coins Earned', description='Battle Report bot section: coins earned by Golden Bot.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='golden_bot_coins_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS))) module-attribute

DEFAULT_START = datetime(2025, 12, 9, 0, 0, 0, tzinfo=UTC) module-attribute

_VALIDATION = validate_chart_configs(CHART_CONFIGS, registry=DEFAULT_REGISTRY) module-attribute

joined = '\n'.join(_VALIDATION.errors) module-attribute

ChartComparison dataclass

Optional comparison behavior for a chart configuration.

The comparison layer supports two styles:

  • Grouping comparisons (by tier/preset) that split a single scope into multiple datasets.
  • Two-scope comparisons (before/after, run vs run) that split the same config into exactly two datasets.

ChartConfig dataclass

Declarative chart definition for the dashboard.

Parameters:

Name Type Description Default
id str

Stable, unique identifier used by the dashboard selection control.

required
title str

Chart title displayed in the UI.

required
description str | None

Optional chart description shown in selection lists/tooltips.

required
category ChartCategory

Used for grouping charts in the selection UI.

required
domain ChartDomain

Taxonomy domain for the chart ("economy", "damage", "enemy_destruction", "efficiency").

required
semantic_type ChartSemanticType

Semantic chart type used for guardrails and validation.

required
chart_type ChartType

The visual chart type.

required
metric_series tuple[ChartSeriesConfig, ...]

One or more metric series definitions that feed the chart.

required
filters ChartFilters

Filter toggles, including default date range behavior.

required
comparison ChartComparison | None

Optional comparison behavior (generates multiple datasets).

None
derived ChartDerived | None

Optional derived metric definition (computed from series inputs).

None
multi_axis bool

Whether the chart may display multiple y-axes (comparative charts only).

False
donut_value_mode Literal['raw', 'percent']

When chart_type="donut", choose whether the donut uses raw values or percentages.

'raw'
default_granularity ChartGranularity

Default x-axis granularity used by the dashboard.

'daily'
stacked bool

Whether a bar chart renders as stacked bars.

False
ui ChartUI

UI behavior flags (default selection, ordering, etc).

ChartUI(show_by_default=False, selectable=True, order=999)

ChartDerived dataclass

Derived metric configuration computed from other series keys.

ChartFilters dataclass

Filter toggles for a chart configuration.

ChartSeriesConfig dataclass

A single metric series used by a chart.

Parameters:

Name Type Description Default
metric_key str

Registered MetricSeries key.

required
label str | None

Optional override label for the series.

None
transform MetricTransform

Optional transform applied to the series values.

'none'

ChartUI dataclass

UI presentation hints for charts.

DateRangeFilterConfig dataclass

Configure an optional date-range filter for a chart.

Parameters:

Name Type Description Default
enabled bool

Whether the chart accepts date range filtering.

required
default_start datetime

Default lower bound for the date range.

required

SimpleFilterConfig dataclass

Configure a simple boolean-enabled filter.

default_selected_chart_ids()

Return default selected chart IDs for the multiselect control.

list_selectable_chart_configs()

Return selectable charts in UI order.

validate_chart_configs(configs, *, registry)

Validate a collection of ChartConfig entries, enforcing uniqueness.

Parameters:

Name Type Description Default
configs Iterable[ChartConfig]

ChartConfig entries to validate.

required
registry MetricSeriesRegistry

MetricSeriesRegistry used for metric lookups/capabilities.

required

Returns:

Type Description
ValidationResult

ValidationResult covering all input configs.

core.charting.dto_builder

Chart Builder compilation from UI forms into ChartConfigDTO.

ChartBuilderForm

Bases: Form

Validate constrained Chart Builder selections.

The Chart Builder produces a runtime ChartConfig (not persisted) that is validated and rendered using the same pipeline as built-in charts.

__init__(*args, **kwargs)

Initialize choices from the MetricSeries registry and run queryset.

clean()

Enforce the constrained Chart Builder contract.

scopes()

Return two-scope DTOs for before/after and run-vs-run comparisons.

Returns:

Type Description
tuple[ChartScopeDTO, ChartScopeDTO] | None

A tuple of (scope_a, scope_b) when comparison is enabled, otherwise None.

Raises:

Type Description
ValueError

When the form is invalid.

selection()

Return a typed selection for building a runtime ChartConfig.

Returns:

Type Description
ChartBuilderSelection

ChartBuilderSelection derived from validated form values.

Raises:

Type Description
ValueError

If the form is invalid.

ChartConfigDTO dataclass

Constrained chart configuration produced by the Chart Builder.

Parameters:

Name Type Description Default
metrics tuple[str, ...]

One or more MetricSeries keys.

required
chart_type ChartType

Visualization type.

required
group_by GroupBy

Grouping selection for splitting datasets.

required
comparison ComparisonMode

Optional two-scope comparison mode.

required
smoothing SmoothingMode

Optional smoothing mode (rolling average).

required
aggregation AggregationMode | None

Optional aggregation override ("sum" or "avg").

None
context ChartContextDTO

Context filters used when producing the chart.

required
scopes tuple[ChartScopeDTO, ChartScopeDTO] | None

Exactly two scopes when comparison != "none".

None
x_axis XAxisMode

X-axis mode ("time" or metric-vs-metric).

'time'
version str

DTO version for forwards-compatible snapshot decoding.

'phase7_chart_config_v1'

ChartContextDTO dataclass

Context filters attached to a chart configuration.

Parameters:

Name Type Description Default
start_date date | None

Optional inclusive lower bound date.

required
end_date date | None

Optional inclusive upper bound date.

required
tier int | None

Optional tier filter.

None
tournament_filter str | None

Optional tournament filter ("all" or specific rank key).

None
preset_id int | None

Optional preset id filter.

None
excluded_preset_ids tuple[int, ...]

Preset ids to exclude from the scope.

()
include_tournaments bool

Whether tournament runs are included in the scope.

False
include_hidden bool

Whether hidden Battle Reports are included in the scope.

False
patch_boundaries tuple[date, ...]

Patch boundary dates used to define included windows.

()

ChartContextForm

Bases: Form

Validate contextual filters and chart overlay options.

__init__(*args, **kwargs)

Initialize the form with dynamic preset choices.

clean()

Apply Event-window defaults and dashboard invariants.

clean_tier()

Parse tier selections into tier or tournament filters.

build_chart_config_dto(*, context_form, builder_form)

Compile validated UI forms into a ChartConfigDTO.

Parameters:

Name Type Description Default
context_form ChartContextForm

Validated ChartContextForm (filters used to scope runs).

required
builder_form ChartBuilderForm

Validated ChartBuilderForm (schema-driven chart settings).

required

Returns:

Type Description
ChartConfigDTO

ChartConfigDTO containing both the builder configuration and context filters.

Raises:

Type Description
ValueError

If either form is invalid.

core.charting.flagging

Flagging helpers used by chart rendering.

These helpers are UI-adjacent but remain deterministic and value-preserving.

INCOMPLETE_RUN = FlagRule(key='incomplete_run', label='Incomplete run', description='Incomplete run metadata (wave/time missing).') module-attribute

PATCH_BOUNDARY = FlagRule(key='patch_boundary', label='Boundary', description='Marked boundary date (data may not be comparable across the boundary).') module-attribute

ROLLING_MEDIAN_DEVIATION = FlagRule(key='rolling_median_deviation', label='Deviation', description='Value differs from the rolling median by more than 3×.') module-attribute

apply_patch_boundaries(labels, *, boundary_dates)

Return a boolean list indicating label dates matching known boundaries.

Parameters:

Name Type Description Default
labels list[str]

ISO date labels ("YYYY-MM-DD").

required
boundary_dates Iterable[date]

Boundary dates considered important for interpretation.

required

Returns:

Type Description
list[bool]

A list aligned to labels, True for boundary dates.

flag_reasons(labels, *, values, incomplete_labels, patch_boundaries)

Compute per-point flag reasons aligned to labels.

Parameters:

Name Type Description Default
labels list[str]

ISO date labels for the series.

required
values list[float | None]

Series values aligned to labels.

required
incomplete_labels set[str]

Set of labels containing incomplete run metadata.

required
patch_boundaries tuple[date, ...]

Known boundary dates (optional).

required

Returns:

Type Description
list[str | None]

List of optional reason strings aligned to labels.

incomplete_run_labels(records)

Return ISO date labels that contain at least one incomplete run.

Parameters:

Name Type Description Default
records Iterable[object]

Typically a BattleReport QuerySet filtered to the current context.

required

Returns:

Type Description
set[str]

Set of ISO date strings ("YYYY-MM-DD") where a run is missing key metadata.

rolling_median_flags(values, *, window=7)

Return a boolean list indicating rolling-median deviations.

Parameters:

Name Type Description Default
values list[float | None]

Numeric series aligned to chart labels, with None for missing points.

required
window int

Lookback window size used for computing the reference median.

7

Returns:

Type Description
list[bool]

A list of booleans aligned to values, True where a point is flagged.

core.charting.flags

Deterministic run flags for charts.

Flags are advisory signals that help a user interpret charts. They never change metric values and must avoid probabilistic language.

INCOMPLETE_RUN = FlagRule(key='incomplete_run', label='Incomplete run', description='Incomplete run metadata (wave/time missing).') module-attribute

PATCH_BOUNDARY = FlagRule(key='patch_boundary', label='Boundary', description='Marked boundary date (data may not be comparable across the boundary).') module-attribute

ROLLING_MEDIAN_DEVIATION = FlagRule(key='rolling_median_deviation', label='Deviation', description='Value differs from the rolling median by more than 3×.') module-attribute

FlagRule dataclass

A single chart flag rule.

Parameters:

Name Type Description Default
key str

Stable key used for programmatic matching.

required
label str

Short label for UI display.

required
description str

Plain-language explanation of what triggered the flag.

required

apply_patch_boundaries(labels, *, boundary_dates)

Return a boolean list indicating label dates matching known boundaries.

Parameters:

Name Type Description Default
labels list[str]

ISO date labels ("YYYY-MM-DD").

required
boundary_dates Iterable[date]

Boundary dates considered important for interpretation.

required

Returns:

Type Description
list[bool]

A list aligned to labels, True for boundary dates.

rolling_median_flags(values, *, window=7)

Return a boolean list indicating rolling-median deviations.

Parameters:

Name Type Description Default
values list[float | None]

Numeric series aligned to chart labels, with None for missing points.

required
window int

Lookback window size used for computing the reference median.

7

Returns:

Type Description
list[bool]

A list of booleans aligned to values, True where a point is flagged.

core.charting.render

Generic rendering for ChartConfig-driven Chart.js datasets.

MAX_CHART_LABELS = 400 module-attribute

STACKED_SERIES_PALETTE = ['#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#0099C6', '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99'] module-attribute

ChartConfig dataclass

Declarative chart definition for the dashboard.

Parameters:

Name Type Description Default
id str

Stable, unique identifier used by the dashboard selection control.

required
title str

Chart title displayed in the UI.

required
description str | None

Optional chart description shown in selection lists/tooltips.

required
category ChartCategory

Used for grouping charts in the selection UI.

required
domain ChartDomain

Taxonomy domain for the chart ("economy", "damage", "enemy_destruction", "efficiency").

required
semantic_type ChartSemanticType

Semantic chart type used for guardrails and validation.

required
chart_type ChartType

The visual chart type.

required
metric_series tuple[ChartSeriesConfig, ...]

One or more metric series definitions that feed the chart.

required
filters ChartFilters

Filter toggles, including default date range behavior.

required
comparison ChartComparison | None

Optional comparison behavior (generates multiple datasets).

None
derived ChartDerived | None

Optional derived metric definition (computed from series inputs).

None
multi_axis bool

Whether the chart may display multiple y-axes (comparative charts only).

False
donut_value_mode Literal['raw', 'percent']

When chart_type="donut", choose whether the donut uses raw values or percentages.

'raw'
default_granularity ChartGranularity

Default x-axis granularity used by the dashboard.

'daily'
stacked bool

Whether a bar chart renders as stacked bars.

False
ui ChartUI

UI behavior flags (default selection, ordering, etc).

ChartUI(show_by_default=False, selectable=True, order=999)

ChartData

Bases: TypedDict

The full Chart.js payload (labels + datasets) for a chart panel.

ChartDataset

Bases: TypedDict

A Chart.js dataset payload for the dashboard.

ChartSeriesConfig dataclass

A single metric series used by a chart.

Parameters:

Name Type Description Default
metric_key str

Registered MetricSeries key.

required
label str | None

Optional override label for the series.

None
transform MetricTransform

Optional transform applied to the series values.

'none'

MetricPoint dataclass

A per-run metric point for charting.

Attributes:

Name Type Description
run_id int | None

Optional identifier for the underlying persisted record.

battle_date datetime

Timestamp used as the x-axis.

tier int | None

Optional tier value when available.

preset_name str | None

Optional preset label when available.

value float | None

Metric value, or None when inputs are missing.

MetricSeriesRegistry

Lookup and validation helpers for metric series definitions.

__init__(specs)

Initialize a registry from a collection of specs.

formula_metric_keys(formula)

Return metric keys referenced by a derived formula.

Prefer inspect_formula when validation needs unknown identifier detection and safety guarantees.

get(key)

Return a spec for a metric key, or None when missing.

inspect_formula(formula)

Inspect a derived-metric formula for identifiers and safety.

The formula language matches analysis.derived_formula.evaluate_formula: constants, metric-key identifiers, unary +/- and binary + - * / only.

Parameters:

Name Type Description Default
formula str

An expression containing metric keys as variable names.

required

Returns:

Type Description
FormulaInspection

FormulaInspection describing referenced/unknown identifiers and whether

FormulaInspection

the expression is syntactically valid and safe.

list()

Return all specs in a stable order.

RenderedChart dataclass

A rendered chart panel produced from a ChartConfig.

_aggregate_points(points, labels, *, aggregation, labeler)

Aggregate run points into a series aligned to x-axis labels.

_apply_series_transform(data, *, series_config, moving_average_window)

Apply chart-side transforms to an already-aggregated data list.

_chart_cache_key(*, config, entity_selections, granularity, moving_average_window, patch_boundaries)

Return a content-based cache key for chart rendering.

Notes

This cache is request-local and is intended as a small guardrail against redundant work when the same config is rendered multiple times.

_color_for_group(key, *, config)

Return a stable dataset color based on the group key.

_color_for_preset(preset_name)

Return a stable color for preset labels.

_color_for_tier(tier)

Return a stable color for a tier number.

_compute_derived_points(*, config, records, moving_average_window, entity_selections)

Compute per-run derived points for a derived ChartConfig.

_dataset(*, label, metric_key, metric_kind, unit, data, color, series_kind='raw')

Build a Chart.js dataset dict with consistent styling.

_engine_transform(series)

Translate a chart-series transform into an engine transform.

_entity_scope_for_series(config, entity_selections)

Return analysis-engine entity scope inferred from enabled chart filters.

_group_points(points, *, config)

Group points according to chart comparison mode.

_infer_division_unit(*, config, registry)

Infer a unit string for simple a/b formulas when possible.

_iterable_has_any(records)

Return True when an iterable contains at least one record.

Parameters:

Name Type Description Default
records Iterable[object]

Records iterable, typically a Django QuerySet.

required

Returns:

Type Description
bool

True when at least one record is present; otherwise False.

_jsonable_config(config)

Convert a ChartConfig dataclass into a JSON-serializable dictionary.

_label_for_group(key, *, config)

Return a human-friendly label for a grouping key.

_labeler_for_records(records, *, granularity, run_numbers_by_report_id=None)

Return a point-labeling function aligned to the selected granularity.

_merge_labels(existing, new_labels)

Merge x-axis labels into a sorted unique list.

_render_donut_chart(*, config, records, registry, entity_selections)

Render a donut chart by aggregating selected metrics across filtered runs.

Parameters:

Name Type Description Default
config ChartConfig

ChartConfig with chart_type="donut".

required
records Iterable[object]

Iterable/QuerySet of run records (already filtered by context).

required
registry MetricSeriesRegistry

MetricSeriesRegistry for labels/units.

required
entity_selections dict[str, str | None]

Mapping for entity filters ("uw", "guardian", "bot") to selected names.

required

Returns:

Type Description
RenderedChart

RenderedChart with labels representing slices and a single dataset.

_render_scatter_chart(*, config, records, registry, entity_selections)

Render a scatter chart using two metric series as x/y axes.

_render_stacked_percent_bar_chart(*, config, records, registry, granularity, entity_selections)

Render a 100% stacked bar chart by normalizing values per x-axis label.

_run_ids_for_labels(points, labeler, labels)

Align run IDs to chart labels for per-run tooltip interactions.

_series_label(*, config, series, group_label, spec_label)

Build a dataset label for a chart series.

_stacked_totals(*, labels, datasets)

Return per-label totals for stacked bar datasets.

_unit_for_series(base_unit, series)

Return the displayed unit for a series after transforms.

analyze_metric_series(records, *, metric_key, transform='none', context=None, entity_type=None, entity_name=None, monte_carlo_trials=None, monte_carlo_seed=None)

Analyze runs for a specific metric, returning a chart-friendly series.

Parameters:

Name Type Description Default
records Iterable[object]

An iterable of RunProgress-like objects, or GameData objects with a run_progress attribute.

required
metric_key str

Metric key to compute (observed or derived).

required
transform str

Optional transform to apply (e.g. "rate_per_hour").

'none'
context PlayerContextInput | None

Optional player context + selected parameter tables.

None
entity_type str | None

Optional entity category for entity-scoped derived metrics (e.g. "ultimate_weapon", "guardian_chip", "bot").

None
entity_name str | None

Optional entity name for entity-scoped derived metrics.

None
monte_carlo_trials int | None

Optional override for Monte Carlo trial count used by simulated EV metrics.

None
monte_carlo_seed int | None

Optional override for the Monte Carlo RNG seed.

None

Returns:

Type Description
MetricSeriesResult

MetricSeriesResult with per-run points and transparent metadata about

MetricSeriesResult

used parameters/assumptions.

Notes

Records missing a battle_date are skipped. Other missing fields do not raise; values become None instead.

evaluate_formula(formula, variables)

Evaluate a derived-metric formula using provided variables.

Parameters:

Name Type Description Default
formula str

Expression referencing metric keys as identifiers (e.g. "a / b").

required
variables Mapping[str, float | None]

Mapping from identifier name to numeric value (or None).

required

Returns:

Type Description
float | None

The computed float value, or None when inputs are missing/invalid.

flag_reasons(labels, *, values, incomplete_labels, patch_boundaries)

Compute per-point flag reasons aligned to labels.

Parameters:

Name Type Description Default
labels list[str]

ISO date labels for the series.

required
values list[float | None]

Series values aligned to labels.

required
incomplete_labels set[str]

Set of labels containing incomplete run metadata.

required
patch_boundaries tuple[date, ...]

Known boundary dates (optional).

required

Returns:

Type Description
list[str | None]

List of optional reason strings aligned to labels.

incomplete_run_labels(records)

Return ISO date labels that contain at least one incomplete run.

Parameters:

Name Type Description Default
records Iterable[object]

Typically a BattleReport QuerySet filtered to the current context.

required

Returns:

Type Description
set[str]

Set of ISO date strings ("YYYY-MM-DD") where a run is missing key metadata.

render_chart(*, config, records, registry, granularity, moving_average_window, entity_selections, patch_boundaries=(), run_numbers_by_report_id=None)

Render a single chart panel from a ChartConfig.

Parameters:

Name Type Description Default
config ChartConfig

ChartConfig to render.

required
records Iterable[object]

Iterable/QuerySet of run records (already filtered by date/tier/preset).

required
registry MetricSeriesRegistry

MetricSeriesRegistry for labels/units and supported transforms.

required
moving_average_window int | None

Optional window size for moving average transforms.

required
entity_selections dict[str, str | None]

Mapping for entity filters ("uw", "guardian", "bot") to selected names.

required

Returns:

Type Description
RenderedChart

RenderedChart containing chart labels and datasets.

render_charts(*, configs, records, registry, granularity, moving_average_window, entity_selections, patch_boundaries=(), run_numbers_by_report_id=None)

Render a set of charts from configs and already-filtered records.

Parameters:

Name Type Description Default
configs tuple[ChartConfig, ...]

ChartConfig entries to render.

required
records Iterable[object]

Iterable/QuerySet of run records (already filtered by date/tier/preset).

required
registry MetricSeriesRegistry

MetricSeriesRegistry for metric capabilities and labels.

required
moving_average_window int | None

Optional window size for moving average transforms.

required
entity_selections dict[str, str | None]

Mapping for entity filters ("uw", "guardian", "bot") to selected names.

required

Returns:

Type Description
tuple[RenderedChart, ...]

RenderedChart entries in the same order as configs.

simple_moving_average(values, *, window)

Compute a simple moving average over a numeric series.

Parameters:

Name Type Description Default
values Sequence[float | None]

A sequence of values aligned to chart labels (None for missing).

required
window int

Window size (>= 2).

required

Returns:

Type Description
list[float | None]

A list the same length as values, with None for indices that cannot be

list[float | None]

computed due to insufficient history or missing inputs.

core.charting.schema

Schema types for declarative chart configuration.

The Charts dashboard is driven by configuration objects (ChartConfig) instead of hard-coded chart logic. This keeps the rendering layer generic and makes it possible to add charts without touching view code.

ChartCategory = Literal['economy', 'damage', 'enemy_destruction', 'efficiency', 'ultimate_weapons', 'guardians', 'bots', 'comparison', 'derived'] module-attribute

ChartDomain = Literal['economy', 'damage', 'enemy_destruction', 'efficiency'] module-attribute

ChartGranularity = Literal['daily', 'per_run'] module-attribute

ChartSemanticType = Literal['absolute', 'distribution', 'contribution', 'comparative'] module-attribute

ChartType = Literal['line', 'bar', 'area', 'scatter', 'donut'] module-attribute

ComparisonMode = Literal['none', 'by_tier', 'by_preset', 'by_entity', 'before_after', 'run_vs_run'] module-attribute

MetricTransform = Literal['none', 'moving_average', 'cumulative', 'rate_per_hour'] module-attribute

XAxis = Literal['time', 'wave_number'] module-attribute

ChartComparison dataclass

Optional comparison behavior for a chart configuration.

The comparison layer supports two styles:

  • Grouping comparisons (by tier/preset) that split a single scope into multiple datasets.
  • Two-scope comparisons (before/after, run vs run) that split the same config into exactly two datasets.

ChartConfig dataclass

Declarative chart definition for the dashboard.

Parameters:

Name Type Description Default
id str

Stable, unique identifier used by the dashboard selection control.

required
title str

Chart title displayed in the UI.

required
description str | None

Optional chart description shown in selection lists/tooltips.

required
category ChartCategory

Used for grouping charts in the selection UI.

required
domain ChartDomain

Taxonomy domain for the chart ("economy", "damage", "enemy_destruction", "efficiency").

required
semantic_type ChartSemanticType

Semantic chart type used for guardrails and validation.

required
chart_type ChartType

The visual chart type.

required
metric_series tuple[ChartSeriesConfig, ...]

One or more metric series definitions that feed the chart.

required
filters ChartFilters

Filter toggles, including default date range behavior.

required
comparison ChartComparison | None

Optional comparison behavior (generates multiple datasets).

None
derived ChartDerived | None

Optional derived metric definition (computed from series inputs).

None
multi_axis bool

Whether the chart may display multiple y-axes (comparative charts only).

False
donut_value_mode Literal['raw', 'percent']

When chart_type="donut", choose whether the donut uses raw values or percentages.

'raw'
default_granularity ChartGranularity

Default x-axis granularity used by the dashboard.

'daily'
stacked bool

Whether a bar chart renders as stacked bars.

False
ui ChartUI

UI behavior flags (default selection, ordering, etc).

ChartUI(show_by_default=False, selectable=True, order=999)

ChartDerived dataclass

Derived metric configuration computed from other series keys.

ChartFilters dataclass

Filter toggles for a chart configuration.

ChartSeriesConfig dataclass

A single metric series used by a chart.

Parameters:

Name Type Description Default
metric_key str

Registered MetricSeries key.

required
label str | None

Optional override label for the series.

None
transform MetricTransform

Optional transform applied to the series values.

'none'

ChartUI dataclass

UI presentation hints for charts.

ComparisonScope dataclass

A concrete scope used by two-scope chart comparisons.

Parameters:

Name Type Description Default
label str

Display label for the scope (shown in chart legends).

required
run_id int | None

Optional BattleReport id for run-vs-run comparisons.

None
start_date date | None

Optional inclusive window start (used by before/after).

None
end_date date | None

Optional inclusive window end (used by before/after).

None

DateRangeFilterConfig dataclass

Configure an optional date-range filter for a chart.

Parameters:

Name Type Description Default
enabled bool

Whether the chart accepts date range filtering.

required
default_start datetime

Default lower bound for the date range.

required

SimpleFilterConfig dataclass

Configure a simple boolean-enabled filter.

core.charting.snapshot_codec

Snapshot encoding/decoding helpers for ChartConfigDTO payloads.

ChartConfigDTO dataclass

Constrained chart configuration produced by the Chart Builder.

Parameters:

Name Type Description Default
metrics tuple[str, ...]

One or more MetricSeries keys.

required
chart_type ChartType

Visualization type.

required
group_by GroupBy

Grouping selection for splitting datasets.

required
comparison ComparisonMode

Optional two-scope comparison mode.

required
smoothing SmoothingMode

Optional smoothing mode (rolling average).

required
aggregation AggregationMode | None

Optional aggregation override ("sum" or "avg").

None
context ChartContextDTO

Context filters used when producing the chart.

required
scopes tuple[ChartScopeDTO, ChartScopeDTO] | None

Exactly two scopes when comparison != "none".

None
x_axis XAxisMode

X-axis mode ("time" or metric-vs-metric).

'time'
version str

DTO version for forwards-compatible snapshot decoding.

'phase7_chart_config_v1'

ChartContextDTO dataclass

Context filters attached to a chart configuration.

Parameters:

Name Type Description Default
start_date date | None

Optional inclusive lower bound date.

required
end_date date | None

Optional inclusive upper bound date.

required
tier int | None

Optional tier filter.

None
tournament_filter str | None

Optional tournament filter ("all" or specific rank key).

None
preset_id int | None

Optional preset id filter.

None
excluded_preset_ids tuple[int, ...]

Preset ids to exclude from the scope.

()
include_tournaments bool

Whether tournament runs are included in the scope.

False
include_hidden bool

Whether hidden Battle Reports are included in the scope.

False
patch_boundaries tuple[date, ...]

Patch boundary dates used to define included windows.

()

ChartScopeDTO dataclass

A scope used by two-scope chart comparisons.

Parameters:

Name Type Description Default
label str

Display label for the scope.

required
run_id int | None

Optional BattleReport id used for run-vs-run comparisons.

None
start_date date | None

Optional inclusive start date used for before/after comparisons.

None
end_date date | None

Optional inclusive end date used for before/after comparisons.

None

_encode_date(value)

Encode a date as an ISO string for JSON storage.

_parse_bool(value)

Best-effort bool parsing for snapshot payloads.

_parse_date(value)

Best-effort date parsing for snapshot payloads.

_parse_int(value)

Best-effort int parsing for snapshot payloads.

_parse_int_list(value)

Best-effort list parsing for integer arrays in snapshot payloads.

_parse_str(value)

Best-effort string parsing for snapshot payloads.

decode_chart_config_dto(payload)

Decode a ChartConfigDTO from a stored payload dictionary.

Parameters:

Name Type Description Default
payload dict[str, Any]

JSONField payload previously produced by encode_chart_config_dto.

required

Returns:

Type Description
ChartConfigDTO

ChartConfigDTO instance.

Raises:

Type Description
ValueError

When required fields are missing or invalid.

encode_chart_config_dto(config)

Encode a ChartConfigDTO into a JSON-serializable dictionary.

Parameters:

Name Type Description Default
config ChartConfigDTO

ChartConfigDTO to encode.

required

Returns:

Type Description
dict[str, Any]

Dict payload safe for JSONField storage.

core.charting.validator

Validation for ChartConfig definitions.

Chart configs are treated as user-editable output (eventually emitted by a Chart Builder), so validation is strict and fails fast.

ChartDomain = Literal['economy', 'damage', 'enemy_destruction', 'efficiency'] module-attribute

ChartGranularity = Literal['daily', 'per_run'] module-attribute

ChartSemanticType = Literal['absolute', 'distribution', 'contribution', 'comparative'] module-attribute

ChartConfig dataclass

Declarative chart definition for the dashboard.

Parameters:

Name Type Description Default
id str

Stable, unique identifier used by the dashboard selection control.

required
title str

Chart title displayed in the UI.

required
description str | None

Optional chart description shown in selection lists/tooltips.

required
category ChartCategory

Used for grouping charts in the selection UI.

required
domain ChartDomain

Taxonomy domain for the chart ("economy", "damage", "enemy_destruction", "efficiency").

required
semantic_type ChartSemanticType

Semantic chart type used for guardrails and validation.

required
chart_type ChartType

The visual chart type.

required
metric_series tuple[ChartSeriesConfig, ...]

One or more metric series definitions that feed the chart.

required
filters ChartFilters

Filter toggles, including default date range behavior.

required
comparison ChartComparison | None

Optional comparison behavior (generates multiple datasets).

None
derived ChartDerived | None

Optional derived metric definition (computed from series inputs).

None
multi_axis bool

Whether the chart may display multiple y-axes (comparative charts only).

False
donut_value_mode Literal['raw', 'percent']

When chart_type="donut", choose whether the donut uses raw values or percentages.

'raw'
default_granularity ChartGranularity

Default x-axis granularity used by the dashboard.

'daily'
stacked bool

Whether a bar chart renders as stacked bars.

False
ui ChartUI

UI behavior flags (default selection, ordering, etc).

ChartUI(show_by_default=False, selectable=True, order=999)

ComparisonScope dataclass

A concrete scope used by two-scope chart comparisons.

Parameters:

Name Type Description Default
label str

Display label for the scope (shown in chart legends).

required
run_id int | None

Optional BattleReport id for run-vs-run comparisons.

None
start_date date | None

Optional inclusive window start (used by before/after).

None
end_date date | None

Optional inclusive window end (used by before/after).

None

MetricCategory

Bases: StrEnum

Semantic category for a metric.

Values are stable identifiers used across the analysis registry and UI.

MetricSeriesRegistry

Lookup and validation helpers for metric series definitions.

__init__(specs)

Initialize a registry from a collection of specs.

formula_metric_keys(formula)

Return metric keys referenced by a derived formula.

Prefer inspect_formula when validation needs unknown identifier detection and safety guarantees.

get(key)

Return a spec for a metric key, or None when missing.

inspect_formula(formula)

Inspect a derived-metric formula for identifiers and safety.

The formula language matches analysis.derived_formula.evaluate_formula: constants, metric-key identifiers, unary +/- and binary + - * / only.

Parameters:

Name Type Description Default
formula str

An expression containing metric keys as variable names.

required

Returns:

Type Description
FormulaInspection

FormulaInspection describing referenced/unknown identifiers and whether

FormulaInspection

the expression is syntactically valid and safe.

list()

Return all specs in a stable order.

MetricSeriesSpec dataclass

Describe a chartable metric series.

Parameters:

Name Type Description Default
key str

Stable metric key referenced by ChartConfig.metric_series.metric_key.

required
label str

Human-friendly label.

required
description str | None

Optional description for Chart Builder / UI tooltips.

required
unit str

Display unit string.

required
category MetricCategory

Semantic category used for registry validation.

required
kind str

Either "observed" or "derived".

required
source_model SourceModel

Source model family backing the series.

required
aggregation Aggregation

Default aggregation used by dashboard charts.

required
time_index TimeIndex

The x-axis index used when charting the series.

required
value_field str

Field name or computed accessor label.

required
allowed_transforms frozenset[MetricTransform]

Allowed transforms for this metric.

required
supported_filters frozenset[FilterKey]

Filter dimensions supported by this metric.

required

ValidationResult dataclass

Result of validating a chart config.

_domain_for_category(category)

Map a MetricCategory to a ChartDomain.

_enabled_filter_keys(config)

Return enabled filter keys for compatibility validation.

_validate_comparison_dimension(config, *, registry, dimension, errors)

Ensure all metrics in a comparison chart support the comparison dimension.

_validate_derived_axes(config, *, registry, referenced, errors)

Validate derived metric axis compatibility against referenced series specs.

_validate_domain_exclusivity(config, *, resolved_specs, errors)

Enforce chart-domain guardrails on metric selection.

Comparative charts may mix damage and enemy destruction metrics, but: - economy metrics must never be mixed with non-economy domains - cash and coins units must never appear together in a single chart

_validate_two_scope_comparison(config, *, errors)

Validate before/after and run-vs-run comparisons.

Parameters:

Name Type Description Default
config ChartConfig

ChartConfig with a two-scope comparison mode.

required
errors list[str]

Mutable list of validation error strings.

required

_validate_units_and_categories(config, *, resolved_specs, errors)

Enforce unit/category compatibility across metric series.

Notes

The Charts dashboard renders a single shared y-axis per chart. For multi-series non-derived charts, unit and category mixing is not supported.

validate_chart_config(config, *, registry)

Validate a single ChartConfig against the MetricSeries registry.

Parameters:

Name Type Description Default
config ChartConfig

ChartConfig to validate.

required
registry MetricSeriesRegistry

MetricSeriesRegistry used for metric lookups/capabilities.

required

Returns:

Type Description
ValidationResult

ValidationResult containing errors and warnings.

validate_chart_configs(configs, *, registry)

Validate a collection of ChartConfig entries, enforcing uniqueness.

Parameters:

Name Type Description Default
configs Iterable[ChartConfig]

ChartConfig entries to validate.

required
registry MetricSeriesRegistry

MetricSeriesRegistry used for metric lookups/capabilities.

required

Returns:

Type Description
ValidationResult

ValidationResult covering all input configs.