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 |
None
|
scope_b
|
ComparisonScope | None
|
Scope B when |
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 |
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
¶
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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. |