Analytics¶
Developer-facing API reference for the analysis package.
Package¶
analysis
¶
Pure analysis package for theTowerStats.
This package contains deterministic, testable computations that operate on in-memory inputs and return DTOs. It must not import Django or perform any database I/O.
Modules¶
analysis.aggregations
¶
Aggregation helpers for the Analysis Engine.
This module provides deterministic, reusable aggregation functions used by the UI (charts, comparisons) without introducing Django dependencies.
RunAnalysis
dataclass
¶
Per-run analysis result.
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
int | None
|
Optional identifier for the underlying persisted record. |
battle_date |
datetime
|
The battle date used as a time-series x-axis. |
tier |
int | None
|
Optional tier value when available on the input. |
preset_name |
str | None
|
Optional preset label when available on the input. |
coins_per_hour |
float
|
Derived rate metric for Phase 1 charts. |
WindowSummary
dataclass
¶
A summarized view of runs within a date window.
Attributes:
| Name | Type | Description |
|---|---|---|
start_date |
date
|
Window start date (inclusive). |
end_date |
date
|
Window end date (inclusive). |
run_count |
int
|
Number of runs included in the window. |
average_coins_per_hour |
float | None
|
Average coins/hour across runs, if any. |
average_coins_per_hour(runs)
¶
Compute average coins/hour across runs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runs
|
Iterable[RunAnalysis]
|
Per-run analysis results. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
The arithmetic mean of |
average_metric(runs, *, value_getter)
¶
Compute an average across runs for a selected metric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runs
|
Iterable[RunAnalysis]
|
Per-run analysis results. |
required |
value_getter
|
Callable[[RunAnalysis], float | None]
|
Callable that extracts a float value from a run. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Arithmetic mean across extracted values, or None when no values exist. |
daily_average_series(runs, *, value_getter=None)
¶
Aggregate runs into a daily average series keyed by ISO date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runs
|
Iterable[RunAnalysis]
|
Per-run analysis results. |
required |
value_getter
|
Callable[[RunAnalysis], float | None] | None
|
Optional callable extracting the metric value from a run.
Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Mapping of |
filter_runs_by_date(runs, *, start_date, end_date)
¶
Filter analyzed runs by an inclusive date range.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runs
|
Iterable[RunAnalysis]
|
Per-run analysis results. |
required |
start_date
|
date | None
|
Optional start date (inclusive). |
required |
end_date
|
date | None
|
Optional end date (inclusive). |
required |
Returns:
| Type | Description |
|---|---|
tuple[RunAnalysis, ...]
|
A tuple of runs whose |
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. |
summarize_window(runs, *, start_date, end_date)
¶
Summarize coins/hour metrics for a date window.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runs
|
Iterable[RunAnalysis]
|
Per-run analysis results. |
required |
start_date
|
date
|
Window start date (inclusive). |
required |
end_date
|
date
|
Window end date (inclusive). |
required |
Returns:
| Type | Description |
|---|---|
WindowSummary
|
WindowSummary including run count and average coins/hour. |
analysis.battle_report_extract
¶
Battle Report value extraction for canonical Phase 6 metrics.
This module extracts additional observed values from raw Battle Report text. It intentionally stays within the analysis layer: - pure (no Django imports), - deterministic and testable, - defensive on unknown labels (missing labels return None unless a caller chooses a default).
_LABEL_SEPARATOR = '(?:[ \\t]*:[ \\t]*|\\t+[ \\t]*|[ \\t]{2,})'
module-attribute
¶
_LABEL_VALUE_RE = re.compile(f'(?im)^[ \t]*(?P<label>.+?){_LABEL_SEPARATOR}(?P<value>.*?)[ \t]*$')
module-attribute
¶
ExtractedNumber
dataclass
¶
Extracted numeric value from a Battle Report line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_value
|
str
|
Raw value string from the report. |
required |
value
|
float
|
Parsed numeric value as a float (unit-normalized). |
required |
UnitContract
dataclass
¶
Contract describing the expected unit type for a parsed value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_type
|
UnitType
|
Expected UnitType for the value. |
required |
allow_zero
|
bool
|
Whether a numeric zero is considered valid. |
True
|
UnitType
¶
Bases: Enum
Supported unit categories for Phase 1.5.
UnitValidationError
¶
Bases: ValueError
Raised when a quantity does not satisfy a unit contract.
_normalize_label(label)
¶
Normalize labels for dictionary lookup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
Raw label text. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Normalized label key suitable for dictionary matching. |
extract_label_values(raw_text)
cached
¶
Extract normalized label/value pairs from raw Battle Report text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Mapping of normalized label -> raw value string. |
extract_numeric_value(raw_text, *, label, unit_type)
¶
Extract and parse a numeric value for a specific Battle Report label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text. |
required |
label
|
str
|
Exact label as shown in Battle Reports. |
required |
unit_type
|
UnitType
|
Expected unit type for strict validation. |
required |
Returns:
| Type | Description |
|---|---|
ExtractedNumber | None
|
ExtractedNumber when the label is present and parseable; otherwise None. |
Notes
The parsing rules come from analysis.quantity.parse_quantity. This
wrapper additionally enforces that the raw string cannot represent a
different unit type (e.g. 15% for a coins metric).
parse_validated_quantity(raw_value, *, contract)
¶
Parse and validate a quantity string against a strict unit contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_value
|
str
|
Raw Battle Report value (e.g. |
required |
contract
|
UnitContract
|
UnitContract describing the expected unit type. |
required |
Returns:
| Type | Description |
|---|---|
ValidatedQuantity
|
ValidatedQuantity with a non-None Decimal value. |
Raises:
| Type | Description |
|---|---|
UnitValidationError
|
When the parsed unit type does not match the contract. |
ValueError
|
When the value cannot be parsed into a numeric Decimal. |
analysis.categories
¶
Shared metric category definitions.
MetricCategory is a semantic classification (not purely visual) used to ensure new metrics are registered consistently and scoped predictably.
MetricCategory
¶
Bases: StrEnum
Semantic category for a metric.
Values are stable identifiers used across the analysis registry and UI.
analysis.chart_config_dto
¶
DTO schema for Phase 7 Chart Builder configurations.
The Chart Builder emits a constrained configuration that is: - schema-driven (no free-form expressions), - serializable for snapshots, - validated before execution, - consumed by the analysis layer to produce chart-ready DTO outputs.
AggregationMode = Literal['sum', 'avg']
module-attribute
¶
ChartType = Literal['line', 'bar', 'area', 'scatter', 'donut']
module-attribute
¶
ComparisonMode = Literal['none', 'before_after', 'run_vs_run']
module-attribute
¶
GroupBy = Literal['time', 'tier', 'preset']
module-attribute
¶
SmoothingMode = Literal['none', 'rolling_avg']
module-attribute
¶
XAxisMode = Literal['time', 'metric']
module-attribute
¶
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
|
analysis.chart_config_engine
¶
Chart execution for Phase 7 ChartConfigDTO.
This module consumes ChartConfigDTO and produces deterministic DTO outputs that the UI can render without performing calculations inline.
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'
|
ChartDataDTO
dataclass
¶
Chart output DTO produced from a ChartConfigDTO.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
labels
|
list[str]
|
ISO date labels for time-based charts. |
required |
datasets
|
list[ChartDatasetDTO]
|
Datasets aligned to labels or metric-vs-metric points. |
required |
chart_type
|
str
|
Config chart type (line/bar/area/scatter/donut). |
required |
x_axis
|
str
|
X-axis mode ("time" or "metric"). |
'time'
|
x_label
|
str | None
|
X-axis label for metric-vs-metric charts. |
None
|
x_unit
|
str | None
|
X-axis unit for metric-vs-metric charts. |
None
|
y_label
|
str | None
|
Y-axis label for metric-vs-metric charts. |
None
|
y_unit
|
str | None
|
Y-axis unit for metric-vs-metric charts. |
None
|
run_ids
|
list[int | None] | None
|
Run id list aligned to metric-vs-metric points. |
None
|
ChartDatasetDTO
dataclass
¶
A chart dataset produced from a ChartConfigDTO.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
Dataset label shown in legends. |
required |
metric_key
|
str
|
MetricSeries key used for the dataset. |
required |
unit
|
str
|
Display unit string. |
required |
values
|
list[float | None] | list[dict[str, float | int | None]]
|
Values aligned to |
required |
run_counts
|
list[int]
|
Count of non-null contributing points aligned to labels. |
required |
scope_label
|
str | None
|
Optional scope label when produced from a two-scope comparison. |
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.
_aggregate_points(points, labels, *, aggregation)
¶
Aggregate points into daily series aligned to label dates.
_analyze_donut(records, *, config, registry)
¶
Analyze a donut chart by aggregating selected metrics across records.
_analyze_metric_axis(records, *, config, registry)
¶
Analyze a metric-vs-metric chart configuration into point datasets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
Iterable[object]
|
Iterable/QuerySet of battle report records already filtered by context. |
required |
config
|
ChartConfigDTO
|
ChartConfigDTO to execute (metric-vs-metric mode). |
required |
registry
|
MetricSeriesRegistry
|
MetricSeriesRegistry for labels/units. |
required |
Returns:
| Type | Description |
|---|---|
ChartDataDTO
|
ChartDataDTO with x/y labels and point datasets. |
_group_points(points, *, config)
¶
Group points according to config grouping/comparison settings.
_iterable_has_any(records)
¶
Return True when an iterable contains at least one record.
analyze_chart_config_dto(records, *, config, registry, moving_average_window, entity_selections)
¶
Analyze a ChartConfigDTO into chart-ready datasets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
Iterable[object]
|
Iterable/QuerySet of battle report records already filtered by context. |
required |
config
|
ChartConfigDTO
|
ChartConfigDTO to execute. |
required |
registry
|
MetricSeriesRegistry
|
MetricSeriesRegistry for labels/units and aggregation semantics. |
required |
moving_average_window
|
int | None
|
Optional rolling window for smoothing. |
required |
entity_selections
|
dict[str, str | None]
|
Mapping for entity selections (unused for Phase 7 builder config). |
required |
Returns:
| Type | Description |
|---|---|
ChartDataDTO
|
ChartDataDTO suitable for conversion into Chart.js data. |
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.
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. |
analysis.chart_config_validator
¶
Validation for Phase 7 ChartConfigDTO values.
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'
|
ChartConfigValidationResult
dataclass
¶
Validation result for ChartConfigDTO.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
is_valid
|
bool
|
True when no errors exist. |
required |
errors
|
tuple[str, ...]
|
Fatal validation errors. |
()
|
warnings
|
tuple[str, ...]
|
Non-fatal warnings intended for UI display. |
()
|
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.
allowed_chart_builder_aggregations(spec)
¶
Return the allowed aggregations for Chart Builder selections.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
MetricSeriesSpec
|
MetricSeriesSpec to evaluate. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Aggregation, ...]
|
Tuple of allowed aggregation keys for Chart Builder use. |
validate_chart_config_dto(config, *, registry)
¶
Validate a ChartConfigDTO against the MetricSeries registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ChartConfigDTO
|
ChartConfigDTO from the Chart Builder. |
required |
registry
|
MetricSeriesRegistry
|
MetricSeriesRegistry for unit/category/transform capability checks. |
required |
Returns:
| Type | Description |
|---|---|
ChartConfigValidationResult
|
ChartConfigValidationResult containing errors and warnings. |
analysis.context
¶
DTOs for passing player context and parameter tables into analysis.
The Analysis Engine must remain pure: no Django imports, no database access, and no side effects. The core app is responsible for building these DTOs from ORM models.
ParameterInput
dataclass
¶
A single parameter value selected for analysis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Stable parameter key (caller-defined). |
required |
raw_value
|
str
|
Raw string as captured from wiki or user input. |
required |
parsed
|
Quantity
|
Best-effort parsed quantity containing normalized value. |
required |
wiki_revision_id
|
int | None
|
Optional |
required |
PlayerBotInput
dataclass
¶
Player bot context for analysis computations.
PlayerCardInput
dataclass
¶
Player card context for analysis computations.
PlayerContextInput
dataclass
¶
Container for all player context provided to analysis.
The Analysis Engine must handle missing context gracefully. Callers may pass
None instead of a PlayerContextInput when no player state is available.
PlayerGuardianChipInput
dataclass
¶
Player guardian chip context for analysis computations.
PlayerUltimateWeaponInput
dataclass
¶
Player ultimate weapon context for analysis computations.
Quantity
dataclass
¶
A parsed quantity with both raw and normalized representations.
Attributes:
| Name | Type | Description |
|---|---|---|
raw_value |
str
|
The original raw string value (trimmed). |
normalized_value |
Decimal | None
|
The parsed numeric value as a Decimal, or None if the value could not be parsed. |
magnitude |
str | None
|
The compact magnitude suffix (e.g. |
unit_type |
UnitType
|
The category of unit this value represents. |
analysis.deltas
¶
Delta calculations for the Analysis Engine.
This module computes deterministic differences between two values. Deltas are computed on-demand and are never persisted.
MetricDelta
dataclass
¶
A deterministic delta between two numeric metric values.
Attributes:
| Name | Type | Description |
|---|---|---|
baseline |
float
|
Baseline value (A). |
comparison |
float
|
Comparison value (B). |
absolute |
float
|
|
percent |
float | None
|
|
delta(baseline, comparison)
¶
Compute absolute and percentage delta between two values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
baseline
|
float
|
Baseline value (A). |
required |
comparison
|
float
|
Comparison value (B). |
required |
Returns:
| Type | Description |
|---|---|
MetricDelta
|
MetricDelta with absolute and percentage changes. Percentage delta is |
MetricDelta
|
None when the baseline is 0. |
analysis.derived
¶
Derived metric helpers based on parameterized game mechanics.
This module contains deterministic, testable computations that combine observations (run data) with parameterized effects (wiki-derived tables and player context). It must remain pure and defensive: callers should receive partial results instead of exceptions when inputs are missing.
MonteCarloConfig
dataclass
¶
Configuration for deterministic Monte Carlo computations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trials
|
int
|
Number of simulated trials (must be > 0). |
required |
seed
|
int
|
RNG seed used to ensure deterministic results in tests and UI. |
required |
apply_multiplier(value, *, multiplier)
¶
Apply a multiplier to a numeric value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float | None
|
Baseline value. |
required |
multiplier
|
float | None
|
Multiplier factor. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
|
effective_cooldown_seconds(*, base_seconds, reduction_fractions)
¶
Compute an effective cooldown in seconds after reductions.
Formula (assumption): effective = base_seconds * (1 - sum(reduction_fractions))
Reductions are treated as additive fractions (e.g. 10% -> 0.10). The result is clamped to >= 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_seconds
|
float | None
|
Base cooldown in seconds. |
required |
reduction_fractions
|
tuple[float, ...]
|
Sequence of fractional reductions (0..1). |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Effective cooldown in seconds, or None if base is missing/invalid. |
expected_multiplier_bernoulli(*, proc_chance, proc_multiplier)
¶
Compute the expected multiplier for a Bernoulli proc.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proc_chance
|
float | None
|
Probability of proc in [0, 1]. |
required |
proc_multiplier
|
float | None
|
Multiplier applied on proc (>= 0). |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Expected multiplier E[M] = (1-p)1 + pm, or None if inputs are invalid. |
monte_carlo_expected_multiplier_bernoulli(*, proc_chance, proc_multiplier, config)
¶
Estimate expected multiplier via deterministic Monte Carlo simulation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
proc_chance
|
float | None
|
Probability of proc in [0, 1]. |
required |
proc_multiplier
|
float | None
|
Multiplier applied on proc (>= 0). |
required |
config
|
MonteCarloConfig
|
MonteCarloConfig controlling trial count and RNG seed. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Estimated expected multiplier, or None if inputs are invalid. |
analysis.derived_formula
¶
Safe evaluation for simple derived-metric formulas.
Derived chart configs reference base metric keys in a small expression language. This module evaluates those expressions safely (no attribute access, no calls, no comprehensions) and returns None when inputs are missing.
_eval_node(node, variables)
¶
Recursively evaluate an AST node with strict safety rules.
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. |
analysis.dto
¶
DTO types returned by the Analysis Engine.
DTOs are plain data containers used to transport analysis results to the UI. They intentionally avoid any Django/ORM dependencies.
AnalysisResult
dataclass
¶
Container for analysis results.
Attributes:
| Name | Type | Description |
|---|---|---|
runs |
tuple['RunAnalysis', ...]
|
Per-run analysis results. |
MetricCategory
¶
Bases: StrEnum
Semantic category for a metric.
Values are stable identifiers used across the analysis registry and UI.
MetricDefinition
dataclass
¶
Definition for an observed or derived metric.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
Stable metric key used by UI selection and charting. |
label |
str
|
Human-friendly label. |
unit |
str
|
Display unit string (e.g. "coins/hour", "seconds"). |
category |
MetricCategory
|
Semantic category used for filtering and validation. |
kind |
str
|
Either "observed" or "derived". |
MetricDelta
dataclass
¶
A deterministic delta between two numeric metric values.
Attributes:
| Name | Type | Description |
|---|---|---|
baseline |
float
|
Baseline value (A). |
comparison |
float
|
Comparison value (B). |
absolute |
float
|
|
percent |
float | 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. |
MetricSeriesResult
dataclass
¶
A computed time series for a selected metric.
Attributes:
| Name | Type | Description |
|---|---|---|
metric |
MetricDefinition
|
MetricDefinition describing the series. |
points |
tuple[MetricPoint, ...]
|
Per-run metric points. |
used_parameters |
tuple[UsedParameter, ...]
|
Parameters referenced (for derived metrics and UI transparency). |
assumptions |
tuple[str, ...]
|
Human-readable, non-prescriptive notes about formulas/policies. |
RunAnalysis
dataclass
¶
Per-run analysis result.
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
int | None
|
Optional identifier for the underlying persisted record. |
battle_date |
datetime
|
The battle date used as a time-series x-axis. |
tier |
int | None
|
Optional tier value when available on the input. |
preset_name |
str | None
|
Optional preset label when available on the input. |
coins_per_hour |
float
|
Derived rate metric for Phase 1 charts. |
RunProgressInput
dataclass
¶
Minimal run-progress input used by Phase 1 analysis.
Attributes:
| Name | Type | Description |
|---|---|---|
battle_date |
datetime
|
The battle date to use as a time-series x-axis. |
coins |
int | None
|
Total coins earned for the run. |
wave |
int
|
Final wave reached. |
real_time_seconds |
int
|
Run duration (seconds). |
UsedParameter
dataclass
¶
A parameter value referenced during derived metric computation.
Attributes:
| Name | Type | Description |
|---|---|---|
entity_type |
str
|
High-level entity type (card, ultimate_weapon, guardian_chip, bot). |
entity_name |
str
|
Human-friendly entity name. |
key |
str
|
Parameter key used by the computation. |
raw_value |
str
|
Raw string as stored. |
normalized_value |
float | None
|
Best-effort normalized float value, if parseable. |
wiki_revision_id |
int | None
|
Optional wiki revision id (core.WikiData pk) used. |
WindowSummary
dataclass
¶
A summarized view of runs within a date window.
Attributes:
| Name | Type | Description |
|---|---|---|
start_date |
date
|
Window start date (inclusive). |
end_date |
date
|
Window end date (inclusive). |
run_count |
int
|
Number of runs included in the window. |
average_coins_per_hour |
float | None
|
Average coins/hour across runs, if any. |
analysis.effects
¶
Parameterized effects for wiki-derived entities.
Effects are deterministic computations that transform a selected entity's raw wiki parameters into derived numeric metrics. Effects must remain pure and defensive: missing inputs should yield partial outputs (None values) rather than exceptions.
EffectResult
dataclass
¶
Result container for an effect computation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float | None
|
Derived numeric value for the effect, or None when inputs are missing/invalid. |
required |
used_parameters
|
tuple[UsedParameter, ...]
|
Parameters referenced during evaluation (for UI transparency). |
required |
ParameterInput
dataclass
¶
A single parameter value selected for analysis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Stable parameter key (caller-defined). |
required |
raw_value
|
str
|
Raw string as captured from wiki or user input. |
required |
parsed
|
Quantity
|
Best-effort parsed quantity containing normalized value. |
required |
wiki_revision_id
|
int | None
|
Optional |
required |
UsedParameter
dataclass
¶
A parameter value referenced during derived metric computation.
Attributes:
| Name | Type | Description |
|---|---|---|
entity_type |
str
|
High-level entity type (card, ultimate_weapon, guardian_chip, bot). |
entity_name |
str
|
Human-friendly entity name. |
key |
str
|
Parameter key used by the computation. |
raw_value |
str
|
Raw string as stored. |
normalized_value |
float | None
|
Best-effort normalized float value, if parseable. |
wiki_revision_id |
int | None
|
Optional wiki revision id (core.WikiData pk) used. |
_float_or_none(value)
¶
Convert a Decimal to float, returning None when missing.
_uptime_percent(*, duration_seconds, cooldown_seconds)
¶
Return uptime percent for (duration, cooldown) inputs.
_used(*, entity_type, entity_name, param)
¶
Build a UsedParameter record for a parameter input.
activations_per_minute_from_parameters(*, entity_type, entity_name, parameters, cooldown_key='cooldown')
¶
Compute activations/minute from a cooldown parameter.
Formula
activations_per_minute = 60 / cooldown_seconds
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_type
|
str
|
Entity category label (e.g. "guardian_chip"). |
required |
entity_name
|
str
|
Human-readable entity name for trace output. |
required |
parameters
|
tuple[ParameterInput, ...]
|
ParameterInput entries (raw + parsed values). |
required |
cooldown_key
|
str
|
Parameter key used for cooldown seconds. |
'cooldown'
|
Returns:
| Type | Description |
|---|---|
EffectResult
|
EffectResult with activations/minute and referenced parameters. |
effective_cooldown_seconds_from_parameters(*, entity_type, entity_name, parameters, cooldown_key='cooldown')
¶
Compute an effective cooldown in seconds from a cooldown parameter.
This is a validation-focused effect used to prove that: - wiki-derived parameter revisions are referenced by analysis at request time, and - derived metrics can be charted alongside observed metrics with explicit units.
Formula
effective_cooldown_seconds = cooldown_seconds
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_type
|
str
|
Entity category label (e.g. "ultimate_weapon"). |
required |
entity_name
|
str
|
Human-readable entity name for trace output. |
required |
parameters
|
tuple[ParameterInput, ...]
|
ParameterInput entries (raw + parsed values). |
required |
cooldown_key
|
str
|
Parameter key used for cooldown seconds. |
'cooldown'
|
Returns:
| Type | Description |
|---|---|
EffectResult
|
EffectResult with effective cooldown seconds and referenced parameters. |
uptime_percent_from_parameters(*, entity_type, entity_name, parameters, duration_key='duration', cooldown_key='cooldown')
¶
Compute uptime percent from duration and cooldown parameters.
Formula
uptime_percent = 100 * clamp(duration_seconds / cooldown_seconds, 0..1)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_type
|
str
|
Entity category label (e.g. "ultimate_weapon", "bot"). |
required |
entity_name
|
str
|
Human-readable entity name for trace output. |
required |
parameters
|
tuple[ParameterInput, ...]
|
ParameterInput entries (raw + parsed values). |
required |
duration_key
|
str
|
Parameter key used for duration seconds. |
'duration'
|
cooldown_key
|
str
|
Parameter key used for cooldown seconds. |
'cooldown'
|
Returns:
| Type | Description |
|---|---|
EffectResult
|
EffectResult with uptime percent and referenced parameters. |
analysis.engine
¶
Orchestration entry points for the Analysis Engine.
The Analysis Engine is a pure, non-Django module that accepts in-memory inputs and returns DTOs. It must not import Django or perform database writes.
_COINS_LINE_RE = re.compile(f'(?im)^[ \t]*(?:Coins|Coins Earned){_LABEL_SEPARATOR}([0-9][0-9,]*(?:\.[0-9]+)?[kmbtq]?)\b[ \t]*.*$')
module-attribute
¶
_LABEL_SEPARATOR = '(?:[ \\t]*:[ \\t]*|\\t+[ \\t]*|[ \\t]{2,})'
module-attribute
¶
AnalysisResult
dataclass
¶
Container for analysis results.
Attributes:
| Name | Type | Description |
|---|---|---|
runs |
tuple['RunAnalysis', ...]
|
Per-run analysis results. |
MetricComputeConfig
dataclass
¶
Configuration for metric computations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
monte_carlo
|
MonteCarloConfig | None
|
Optional MonteCarloConfig for metrics that use simulation. |
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. |
MetricSeriesResult
dataclass
¶
A computed time series for a selected metric.
Attributes:
| Name | Type | Description |
|---|---|---|
metric |
MetricDefinition
|
MetricDefinition describing the series. |
points |
tuple[MetricPoint, ...]
|
Per-run metric points. |
used_parameters |
tuple[UsedParameter, ...]
|
Parameters referenced (for derived metrics and UI transparency). |
assumptions |
tuple[str, ...]
|
Human-readable, non-prescriptive notes about formulas/policies. |
MonteCarloConfig
dataclass
¶
Configuration for deterministic Monte Carlo computations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trials
|
int
|
Number of simulated trials (must be > 0). |
required |
seed
|
int
|
RNG seed used to ensure deterministic results in tests and UI. |
required |
PlayerContextInput
dataclass
¶
Container for all player context provided to analysis.
The Analysis Engine must handle missing context gracefully. Callers may pass
None instead of a PlayerContextInput when no player state is available.
RunAnalysis
dataclass
¶
Per-run analysis result.
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
int | None
|
Optional identifier for the underlying persisted record. |
battle_date |
datetime
|
The battle date used as a time-series x-axis. |
tier |
int | None
|
Optional tier value when available on the input. |
preset_name |
str | None
|
Optional preset label when available on the input. |
coins_per_hour |
float
|
Derived rate metric for Phase 1 charts. |
UnitContract
dataclass
¶
Contract describing the expected unit type for a parsed value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_type
|
UnitType
|
Expected UnitType for the value. |
required |
allow_zero
|
bool
|
Whether a numeric zero is considered valid. |
True
|
UnitType
¶
Bases: Enum
Supported unit categories for Phase 1.5.
UnitValidationError
¶
Bases: ValueError
Raised when a quantity does not satisfy a unit contract.
UsedParameter
dataclass
¶
A parameter value referenced during derived metric computation.
Attributes:
| Name | Type | Description |
|---|---|---|
entity_type |
str
|
High-level entity type (card, ultimate_weapon, guardian_chip, bot). |
entity_name |
str
|
Human-friendly entity name. |
key |
str
|
Parameter key used by the computation. |
raw_value |
str
|
Raw string as stored. |
normalized_value |
float | None
|
Best-effort normalized float value, if parseable. |
wiki_revision_id |
int | None
|
Optional wiki revision id (core.WikiData pk) used. |
_RunProgressLike
¶
Bases: Protocol
Protocol for Phase 1 run-progress inputs (duck-typed).
_coerce_datetime(value)
¶
Coerce an object into a datetime when safe.
_coerce_int(value)
¶
Coerce an object into an int when safe.
_coins_from_raw_text(raw_text)
¶
Extract total coins from raw Battle Report text (best-effort).
_looks_like_run_progress(obj)
¶
Return True if an object exposes the Phase 1 RunProgress interface.
_preset_name_from_progress(progress)
¶
Extract an optional preset name from a run-progress-like object.
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.
analyze_runs(records)
¶
Analyze runs and return rate metrics (Phase 1).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
Iterable[object]
|
An iterable of |
required |
Returns:
| Type | Description |
|---|---|
AnalysisResult
|
AnalysisResult containing a per-run coins-per-hour series. |
Notes
Any record missing required fields is skipped. If no records contain the required data, an empty result is returned.
coins_per_hour(coins, real_time_seconds)
¶
Compute coins per hour for a single run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coins
|
int
|
Total coins earned. |
required |
real_time_seconds
|
int
|
Run duration in seconds. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Coins per hour, or None when inputs are invalid. |
compute_metric_value(metric_key, *, record, coins, cash, interest_earned, cells, reroll_shards, wave, real_time_seconds, context, entity_type, entity_name, config)
¶
Compute a metric value and return used parameters + assumptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metric_key
|
str
|
Metric key to compute. |
required |
record
|
object
|
Run-like object used for relationship-based metrics (e.g. usage presence). |
required |
coins
|
int | None
|
Observed coins for the run. |
required |
cash
|
int | None
|
Observed cash for the run. |
required |
interest_earned
|
int | None
|
Observed interest earned for the run. |
required |
cells
|
int | None
|
Observed cells for the run. |
required |
reroll_shards
|
int | None
|
Observed reroll shards for the run. |
required |
wave
|
int | None
|
Observed wave reached for the run. |
required |
real_time_seconds
|
int | None
|
Observed duration seconds for the run. |
required |
context
|
PlayerContextInput | None
|
Optional player context + selected parameters. |
required |
entity_type
|
str | None
|
Optional entity category for entity-scoped derived metrics. |
required |
entity_name
|
str | None
|
Optional entity name for entity-scoped derived metrics. |
required |
config
|
MetricComputeConfig
|
MetricComputeConfig. |
required |
Returns:
| Type | Description |
|---|---|
tuple[float | None, tuple[UsedParameter, ...], tuple[str, ...]]
|
Tuple of (value, used_parameters, assumptions). |
get_metric_definition(metric_key)
¶
Return a MetricDefinition for a key, defaulting to observed coins/hour.
parse_validated_quantity(raw_value, *, contract)
¶
Parse and validate a quantity string against a strict unit contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_value
|
str
|
Raw Battle Report value (e.g. |
required |
contract
|
UnitContract
|
UnitContract describing the expected unit type. |
required |
Returns:
| Type | Description |
|---|---|
ValidatedQuantity
|
ValidatedQuantity with a non-None Decimal value. |
Raises:
| Type | Description |
|---|---|
UnitValidationError
|
When the parsed unit type does not match the contract. |
ValueError
|
When the value cannot be parsed into a numeric Decimal. |
analysis.event_windows
¶
Event-window helpers for charts and dashboards.
The Tower in-game Events run in fixed 14-day windows. This module provides pure helpers (no Django imports) to compute and shift those windows deterministically.
DEFAULT_EVENT_WINDOW_ANCHOR = date(2025, 12, 9)
module-attribute
¶
EVENT_WINDOW_DAYS = 14
module-attribute
¶
EventWindow
dataclass
¶
A 14-day inclusive date window used by in-game Events.
Attributes:
| Name | Type | Description |
|---|---|---|
start |
date
|
Inclusive window start date. |
end |
date
|
Inclusive window end date. |
coerce_window_bounds(*, start, end, window_days=EVENT_WINDOW_DAYS)
¶
Coerce partial start/end inputs into a full inclusive window.
This helper is used by event navigation controls so that shifting always has both a start and an end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
date | None
|
Optional inclusive start date. |
required |
end
|
date | None
|
Optional inclusive end date. |
required |
window_days
|
int
|
Window size in days (defaults to 14). |
EVENT_WINDOW_DAYS
|
Returns:
| Type | Description |
|---|---|
EventWindow
|
EventWindow whose bounds match provided inputs when possible. |
current_event_window(*, target=None, anchor=DEFAULT_EVENT_WINDOW_ANCHOR, window_days=EVENT_WINDOW_DAYS)
¶
Return the current Event window using the shared app anchor date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
date | None
|
Date to evaluate. Defaults to today's date when omitted. |
None
|
anchor
|
date
|
Known Event start date used as the shared stepping origin. |
DEFAULT_EVENT_WINDOW_ANCHOR
|
window_days
|
int
|
Window size in days (defaults to 14). |
EVENT_WINDOW_DAYS
|
Returns:
| Type | Description |
|---|---|
EventWindow
|
EventWindow containing the target date. |
event_window_for_date(*, target, anchor, window_days=EVENT_WINDOW_DAYS)
¶
Return the Event window containing a target date.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
date
|
The date to place into an Event window. |
required |
anchor
|
date
|
A known Event start date (inclusive) used as the stepping origin. |
required |
window_days
|
int
|
Window size in days (defaults to 14). |
EVENT_WINDOW_DAYS
|
Returns:
| Type | Description |
|---|---|
EventWindow
|
EventWindow containing |
shift_event_window(window, *, shift, window_days=EVENT_WINDOW_DAYS)
¶
Shift an Event window forward/backward by N windows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
EventWindow
|
The base EventWindow to shift. |
required |
shift
|
int
|
Number of windows to shift; negative means previous. |
required |
window_days
|
int
|
Window size in days (defaults to 14). |
EVENT_WINDOW_DAYS
|
Returns:
| Type | Description |
|---|---|
EventWindow
|
Shifted EventWindow. |
analysis.goals
¶
Goal-oriented cost computations for upgradeable parameters.
This module is intentionally pure (no Django imports) so it can be unit-tested and reused across views without database coupling.
GoalCostBreakdown
dataclass
¶
Computed remaining cost and optional per-level breakdown for a goal.
PerLevelCost
dataclass
¶
A single upgrade step cost from from_level to to_level.
compute_goal_cost_breakdown(*, costs_by_level, currency, current_level_display, current_level_for_calc, current_is_assumed, target_level)
¶
Compute remaining upgrade cost and per-level breakdown.
Cost rows are expected to be keyed by the resulting level: the cost at level N is the price to upgrade from N-1 -> N (matching typical wiki tables).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
costs_by_level
|
dict[int, str]
|
Mapping of resulting level -> cost_raw. |
required |
currency
|
str
|
Currency label (e.g. "stones", "medals", "bits"). |
required |
current_level_display
|
int
|
Level to display in UI (may be a fallback). |
required |
current_level_for_calc
|
int
|
Level used for remaining-cost calculation. |
required |
current_is_assumed
|
bool
|
Whether current level is a fallback value. |
required |
target_level
|
int
|
Desired target level. |
required |
Returns:
| Type | Description |
|---|---|
GoalCostBreakdown
|
GoalCostBreakdown with per-level costs and total remaining amount. |
parse_cost_amount(*, cost_raw)
¶
Parse an integer amount from a raw cost string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cost_raw
|
str | None
|
Raw cost string (e.g. "50", "1,250", or "50 Medals"). |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
Parsed integer amount when a number token is present, otherwise None. |
analysis.metrics
¶
Metric registry and computation helpers for the Analysis Engine.
This module centralizes the set of chartable metrics (observed and derived) and their labels/units so the UI can offer consistent selections.
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
¶
METRICS = {'coins_earned': MetricDefinition(key='coins_earned', label='Coins earned', unit='coins', category=(MetricCategory.economy), kind='observed'), 'cash_earned': MetricDefinition(key='cash_earned', label='Cash earned', unit='cash', category=(MetricCategory.economy), kind='observed'), 'interest_earned': MetricDefinition(key='interest_earned', label='Interest earned', unit='cash', category=(MetricCategory.economy), kind='observed'), 'cells_earned': MetricDefinition(key='cells_earned', label='Cells earned', unit='cells', category=(MetricCategory.economy), kind='observed'), 'reroll_shards_earned': MetricDefinition(key='reroll_shards_earned', label='Reroll shards earned', unit='shards', category=(MetricCategory.economy), kind='observed'), 'reroll_dice_earned': MetricDefinition(key='reroll_dice_earned', label='Reroll dice earned', unit='shards', category=(MetricCategory.economy), kind='observed'), 'cells_per_hour': MetricDefinition(key='cells_per_hour', label='Cells/hour', unit='cells/hour', category=(MetricCategory.efficiency), kind='derived'), 'reroll_shards_per_hour': MetricDefinition(key='reroll_shards_per_hour', label='Reroll shards/hour', unit='shards/hour', category=(MetricCategory.efficiency), kind='derived'), 'waves_reached': MetricDefinition(key='waves_reached', label='Waves reached', unit='waves', category=(MetricCategory.utility), kind='observed'), 'coins_per_wave': MetricDefinition(key='coins_per_wave', label='Coins per wave', unit='coins/wave', category=(MetricCategory.economy), kind='derived'), 'uw_runs_count': MetricDefinition(key='uw_runs_count', label='Runs using selected ultimate weapon', unit='runs', category=(MetricCategory.utility), kind='observed'), 'guardian_runs_count': MetricDefinition(key='guardian_runs_count', label='Runs using selected guardian chip', unit='runs', category=(MetricCategory.utility), kind='observed'), 'bot_runs_count': MetricDefinition(key='bot_runs_count', label='Runs using selected bot', unit='runs', category=(MetricCategory.utility), kind='observed'), 'coins_per_hour': MetricDefinition(key='coins_per_hour', label='Coins/hour', unit='coins/hour', category=(MetricCategory.efficiency), kind='observed'), 'real_time_hours': MetricDefinition(key='real_time_hours', label='Run duration (hours)', unit='hours', category=(MetricCategory.efficiency), kind='derived'), 'waves_per_hour': MetricDefinition(key='waves_per_hour', label='Waves/hour', unit='waves/hour', category=(MetricCategory.efficiency), kind='derived'), 'enemies_destroyed_per_hour': MetricDefinition(key='enemies_destroyed_per_hour', label='Enemies destroyed/hour', unit='enemies/hour', category=(MetricCategory.efficiency), kind='derived'), 'coins_from_death_wave': MetricDefinition(key='coins_from_death_wave', label='Coins From Death Wave', unit='coins', category=(MetricCategory.economy), kind='observed'), 'coins_from_golden_tower': MetricDefinition(key='coins_from_golden_tower', label='Coins From Golden Tower', unit='coins', category=(MetricCategory.economy), kind='observed'), 'cash_from_golden_tower': MetricDefinition(key='cash_from_golden_tower', label='Cash From Golden Tower', unit='cash', category=(MetricCategory.economy), kind='observed'), 'cash_from_other_sources': MetricDefinition(key='cash_from_other_sources', label='Other cash', unit='cash', category=(MetricCategory.economy), kind='derived'), 'coins_from_black_hole': MetricDefinition(key='coins_from_black_hole', label='Coins From Black Hole', unit='coins', category=(MetricCategory.economy), kind='observed'), 'coins_from_spotlight': MetricDefinition(key='coins_from_spotlight', label='Coins From Spotlight', unit='coins', category=(MetricCategory.economy), kind='observed'), 'coins_from_orb': MetricDefinition(key='coins_from_orb', label='Coins From Orb', unit='coins', category=(MetricCategory.economy), kind='observed'), 'coins_from_coin_upgrade': MetricDefinition(key='coins_from_coin_upgrade', label='Coins from Coin Upgrade', unit='coins', category=(MetricCategory.economy), kind='observed'), 'coins_from_coin_bonuses': MetricDefinition(key='coins_from_coin_bonuses', label='Coins from Coin Bonuses', unit='coins', category=(MetricCategory.economy), kind='observed'), 'free_attack_upgrades': MetricDefinition(key='free_attack_upgrades', label='Free Attack Upgrade', unit='upgrades', category=(MetricCategory.economy), kind='observed'), 'free_defense_upgrades': MetricDefinition(key='free_defense_upgrades', label='Free Defense Upgrade', unit='upgrades', category=(MetricCategory.economy), kind='observed'), 'free_utility_upgrades': MetricDefinition(key='free_utility_upgrades', label='Free Utility Upgrade', unit='upgrades', category=(MetricCategory.economy), kind='observed'), 'free_upgrades_total': MetricDefinition(key='free_upgrades_total', label='Free Upgrades (Total)', unit='upgrades', category=(MetricCategory.economy), kind='derived'), 'recovery_packages': MetricDefinition(key='recovery_packages', label='Recovery Packages', unit='count', category=(MetricCategory.utility), kind='derived'), 'coins_from_other_sources': MetricDefinition(key='coins_from_other_sources', label='Other coins', unit='coins', category=(MetricCategory.economy), kind='observed'), 'guardian_damage': MetricDefinition(key='guardian_damage', label='Guardian Damage', unit='damage', category=(MetricCategory.combat), kind='observed'), 'damage_dealt': MetricDefinition(key='damage_dealt', label='Damage dealt', unit='damage', category=(MetricCategory.damage), kind='observed'), 'projectiles_damage': MetricDefinition(key='projectiles_damage', label='Projectiles Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'thorn_damage': MetricDefinition(key='thorn_damage', label='Thorn Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'orb_damage': MetricDefinition(key='orb_damage', label='Orb Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'land_mine_damage': MetricDefinition(key='land_mine_damage', label='Land Mine Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'inner_land_mine_damage': MetricDefinition(key='inner_land_mine_damage', label='Inner Land Mine Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'chain_lightning_damage': MetricDefinition(key='chain_lightning_damage', label='Chain Lightning Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'death_wave_damage': MetricDefinition(key='death_wave_damage', label='Death Wave Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'death_ray_damage': MetricDefinition(key='death_ray_damage', label='Death Ray Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'smart_missile_damage': MetricDefinition(key='smart_missile_damage', label='Smart Missile Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'black_hole_damage': MetricDefinition(key='black_hole_damage', label='Black Hole Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'swamp_damage': MetricDefinition(key='swamp_damage', label='Swamp Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'electrons_damage': MetricDefinition(key='electrons_damage', label='Electrons Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'rend_armor_damage': MetricDefinition(key='rend_armor_damage', label='Rend Armor Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'enemies_hit_by_orbs': MetricDefinition(key='enemies_hit_by_orbs', label='Enemies Hit by Orbs', unit='count', category=(MetricCategory.damage), kind='observed'), 'enemies_destroyed_total': MetricDefinition(key='enemies_destroyed_total', label='Enemies destroyed (derived total)', unit='count', category=(MetricCategory.enemy_destruction), kind='derived'), 'enemies_destroyed_common': MetricDefinition(key='enemies_destroyed_common', label='Enemies destroyed (common)', unit='count', category=(MetricCategory.enemy_destruction), kind='derived'), 'enemies_destroyed_elite': MetricDefinition(key='enemies_destroyed_elite', label='Enemies destroyed (elite)', unit='count', category=(MetricCategory.enemy_destruction), kind='derived'), 'enemies_destroyed_fleet': MetricDefinition(key='enemies_destroyed_fleet', label='Enemies destroyed (fleet)', unit='count', category=(MetricCategory.enemy_destruction), kind='derived'), 'enemies_destroyed_basic': MetricDefinition(key='enemies_destroyed_basic', label='Basic', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_fast': MetricDefinition(key='enemies_destroyed_fast', label='Fast', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_tank': MetricDefinition(key='enemies_destroyed_tank', label='Tank', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_ranged': MetricDefinition(key='enemies_destroyed_ranged', label='Ranged', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_boss': MetricDefinition(key='enemies_destroyed_boss', label='Boss', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_protector': MetricDefinition(key='enemies_destroyed_protector', label='Protector', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_vampires': MetricDefinition(key='enemies_destroyed_vampires', label='Vampires', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_rays': MetricDefinition(key='enemies_destroyed_rays', label='Rays', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_scatters': MetricDefinition(key='enemies_destroyed_scatters', label='Scatters', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_saboteur': MetricDefinition(key='enemies_destroyed_saboteur', label='Saboteur', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_commander': MetricDefinition(key='enemies_destroyed_commander', label='Commander', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_overcharge': MetricDefinition(key='enemies_destroyed_overcharge', label='Overcharge', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_orbs': MetricDefinition(key='enemies_destroyed_by_orbs', label='Destroyed By Orbs', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_thorns': MetricDefinition(key='enemies_destroyed_by_thorns', label='Destroyed by Thorns', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_death_ray': MetricDefinition(key='enemies_destroyed_by_death_ray', label='Destroyed by Death Ray', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_land_mine': MetricDefinition(key='enemies_destroyed_by_land_mine', label='Destroyed by Land Mine', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_in_spotlight': MetricDefinition(key='enemies_destroyed_in_spotlight', label='Destroyed in Spotlight', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_in_golden_bot': MetricDefinition(key='enemies_destroyed_in_golden_bot', label='Destroyed in Golden Bot', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'guardian_summoned_enemies': MetricDefinition(key='guardian_summoned_enemies', label='Guardian Summoned Enemies', unit='count', category=(MetricCategory.combat), kind='observed'), 'guardian_coins_stolen': MetricDefinition(key='guardian_coins_stolen', label='Guardian coins stolen', unit='coins', category=(MetricCategory.economy), kind='observed'), 'guardian_coins_fetched': MetricDefinition(key='guardian_coins_fetched', label='Coins Fetched', unit='coins', category=(MetricCategory.economy), kind='observed'), 'guardian_gems_fetched': MetricDefinition(key='guardian_gems_fetched', label='Guardian gems fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_medals_fetched': MetricDefinition(key='guardian_medals_fetched', label='Guardian medals fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_reroll_shards_fetched': MetricDefinition(key='guardian_reroll_shards_fetched', label='Guardian reroll shards fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_cannon_shards_fetched': MetricDefinition(key='guardian_cannon_shards_fetched', label='Guardian cannon shards fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_armor_shards_fetched': MetricDefinition(key='guardian_armor_shards_fetched', label='Guardian armor shards fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_generator_shards_fetched': MetricDefinition(key='guardian_generator_shards_fetched', label='Guardian generator shards fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_core_shards_fetched': MetricDefinition(key='guardian_core_shards_fetched', label='Guardian core shards fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_common_modules_fetched': MetricDefinition(key='guardian_common_modules_fetched', label='Guardian common modules fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'guardian_rare_modules_fetched': MetricDefinition(key='guardian_rare_modules_fetched', label='Guardian rare modules fetched', unit='count', category=(MetricCategory.fetch), kind='observed'), 'flame_bot_damage': MetricDefinition(key='flame_bot_damage', label='Flame Bot Damage', unit='damage', category=(MetricCategory.damage), kind='observed'), 'thunder_bot_stuns': MetricDefinition(key='thunder_bot_stuns', label='Thunder Bot Stuns', unit='count', category=(MetricCategory.combat), kind='observed'), 'golden_bot_coins_earned': MetricDefinition(key='golden_bot_coins_earned', label='Golden Bot Coins Earned', unit='coins', category=(MetricCategory.economy), kind='observed'), 'uw_uptime_percent': MetricDefinition(key='uw_uptime_percent', label='Ultimate Weapon uptime', unit='percent', category=(MetricCategory.utility), kind='derived'), 'guardian_activations_per_minute': MetricDefinition(key='guardian_activations_per_minute', label='Guardian activations/minute', unit='activations/min', category=(MetricCategory.utility), kind='derived'), 'uw_effective_cooldown_seconds': MetricDefinition(key='uw_effective_cooldown_seconds', label='Ultimate Weapon effective cooldown', unit='seconds', category=(MetricCategory.utility), kind='derived'), 'cooldown_reduction_effective': MetricDefinition(key='cooldown_reduction_effective', label='Effective cooldown', unit='seconds', category=(MetricCategory.utility), kind='derived'), 'bot_uptime_percent': MetricDefinition(key='bot_uptime_percent', label='Bot uptime', unit='percent', category=(MetricCategory.utility), kind='derived')}
module-attribute
¶
RAW_TEXT_METRIC_SPECS = {'coins_from_death_wave': ('Coins From Death Wave', UnitType.coins), 'interest_earned': ('Interest earned', UnitType.cash), 'cash_from_golden_tower': ('Cash From Golden Tower', UnitType.cash), 'coins_from_golden_tower': ('Coins From Golden Tower', UnitType.coins), 'coins_from_black_hole': ('Coins From Black Hole', UnitType.coins), 'coins_from_spotlight': ('Coins From Spotlight', UnitType.coins), 'coins_from_orb': ('Coins From Orb', UnitType.coins), 'coins_from_coin_upgrade': ('Coins from Coin Upgrade', UnitType.coins), 'coins_from_coin_bonuses': ('Coins from Coin Bonuses', UnitType.coins), 'free_attack_upgrades': ('Free Attack Upgrade', UnitType.count), 'free_defense_upgrades': ('Free Defense Upgrade', UnitType.count), 'free_utility_upgrades': ('Free Utility Upgrade', UnitType.count), 'recovery_packages': ('Recovery Packages', UnitType.count), 'damage_dealt': ('Damage dealt', UnitType.damage), 'projectiles_damage': ('Projectiles Damage', UnitType.damage), 'thorn_damage': ('Thorn Damage', UnitType.damage), 'orb_damage': ('Orb Damage', UnitType.damage), 'land_mine_damage': ('Land Mine Damage', UnitType.damage), 'inner_land_mine_damage': ('Inner Land Mine Damage', UnitType.damage), 'chain_lightning_damage': ('Chain Lightning Damage', UnitType.damage), 'death_wave_damage': ('Death Wave Damage', UnitType.damage), 'death_ray_damage': ('Death Ray Damage', UnitType.damage), 'smart_missile_damage': ('Smart Missile Damage', UnitType.damage), 'black_hole_damage': ('Black Hole Damage', UnitType.damage), 'swamp_damage': ('Swamp Damage', UnitType.damage), 'electrons_damage': ('Electrons Damage', UnitType.damage), 'rend_armor_damage': ('Rend Armor Damage', UnitType.damage), 'enemies_hit_by_orbs': ('Enemies Hit by Orbs', UnitType.count), 'enemies_destroyed_basic': ('Basic', UnitType.count), 'enemies_destroyed_fast': ('Fast', UnitType.count), 'enemies_destroyed_tank': ('Tank', UnitType.count), 'enemies_destroyed_ranged': ('Ranged', UnitType.count), 'enemies_destroyed_boss': ('Boss', UnitType.count), 'enemies_destroyed_protector': ('Protector', UnitType.count), 'enemies_destroyed_vampires': ('Vampires', UnitType.count), 'enemies_destroyed_rays': ('Rays', UnitType.count), 'enemies_destroyed_scatters': ('Scatters', UnitType.count), 'enemies_destroyed_saboteur': ('Saboteur', UnitType.count), 'enemies_destroyed_commander': ('Commander', UnitType.count), 'enemies_destroyed_overcharge': ('Overcharge', UnitType.count), 'enemies_destroyed_by_orbs': ('Destroyed By Orbs', UnitType.count), 'enemies_destroyed_by_thorns': ('Destroyed by Thorns', UnitType.count), 'enemies_destroyed_by_death_ray': ('Destroyed by Death Ray', UnitType.count), 'enemies_destroyed_by_land_mine': ('Destroyed by Land Mine', UnitType.count), 'enemies_destroyed_in_spotlight': ('Destroyed in Spotlight', UnitType.count), 'enemies_destroyed_in_golden_bot': ('Destroyed in Golden Bot', UnitType.count), 'guardian_damage': ('Damage', UnitType.damage), 'guardian_summoned_enemies': ('Summoned enemies', UnitType.count), 'guardian_coins_stolen': ('Guardian coins stolen', UnitType.coins), 'guardian_coins_fetched': ('Coins Fetched', UnitType.coins), 'guardian_gems_fetched': ('Gems', UnitType.count), 'guardian_medals_fetched': ('Medals', UnitType.count), 'guardian_reroll_shards_fetched': ('Reroll Shards', UnitType.count), 'guardian_cannon_shards_fetched': ('Cannon Shards', UnitType.count), 'guardian_armor_shards_fetched': ('Armor Shards', UnitType.count), 'guardian_generator_shards_fetched': ('Generator Shards', UnitType.count), 'guardian_core_shards_fetched': ('Core Shards', UnitType.count), 'guardian_common_modules_fetched': ('Common Modules', UnitType.count), 'guardian_rare_modules_fetched': ('Rare Modules', UnitType.count), 'flame_bot_damage': ('Flame Bot Damage', UnitType.damage), 'thunder_bot_stuns': ('Thunder Bot Stuns', UnitType.count), 'golden_bot_coins_earned': ('Golden Bot Coins Earned', UnitType.coins)}
module-attribute
¶
MetricCategory
¶
Bases: StrEnum
Semantic category for a metric.
Values are stable identifiers used across the analysis registry and UI.
MetricComputeConfig
dataclass
¶
Configuration for metric computations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
monte_carlo
|
MonteCarloConfig | None
|
Optional MonteCarloConfig for metrics that use simulation. |
None
|
MetricDefinition
dataclass
¶
Definition for an observed or derived metric.
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
Stable metric key used by UI selection and charting. |
label |
str
|
Human-friendly label. |
unit |
str
|
Display unit string (e.g. "coins/hour", "seconds"). |
category |
MetricCategory
|
Semantic category used for filtering and validation. |
kind |
str
|
Either "observed" or "derived". |
MonteCarloConfig
dataclass
¶
Configuration for deterministic Monte Carlo computations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trials
|
int
|
Number of simulated trials (must be > 0). |
required |
seed
|
int
|
RNG seed used to ensure deterministic results in tests and UI. |
required |
ParameterInput
dataclass
¶
A single parameter value selected for analysis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Stable parameter key (caller-defined). |
required |
raw_value
|
str
|
Raw string as captured from wiki or user input. |
required |
parsed
|
Quantity
|
Best-effort parsed quantity containing normalized value. |
required |
wiki_revision_id
|
int | None
|
Optional |
required |
PlayerContextInput
dataclass
¶
Container for all player context provided to analysis.
The Analysis Engine must handle missing context gracefully. Callers may pass
None instead of a PlayerContextInput when no player state is available.
UsedParameter
dataclass
¶
A parameter value referenced during derived metric computation.
Attributes:
| Name | Type | Description |
|---|---|---|
entity_type |
str
|
High-level entity type (card, ultimate_weapon, guardian_chip, bot). |
entity_name |
str
|
Human-friendly entity name. |
key |
str
|
Parameter key used by the computation. |
raw_value |
str
|
Raw string as stored. |
normalized_value |
float | None
|
Best-effort normalized float value, if parseable. |
wiki_revision_id |
int | None
|
Optional wiki revision id (core.WikiData pk) used. |
_compute_enemies_destroyed_group_from_values(derived_values, *, keys)
¶
Compute enemies destroyed totals for a group of per-type metrics.
_compute_enemies_destroyed_total_from_values(derived_values)
¶
Compute enemies destroyed total from persisted derived metrics.
_compute_other_coins_from_sources(*, coins, derived_values)
¶
Return residual coins not covered by named sources from persisted values.
_entity_parameters(context, *, entity_type, entity_name)
¶
Return parameters for a specific entity selection, or None when missing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
PlayerContextInput
|
PlayerContextInput containing unlocked/owned entities. |
required |
entity_type
|
str
|
One of "ultimate_weapon", "guardian_chip", "bot". |
required |
entity_name
|
str | None
|
Display name to match against context entries. |
required |
Returns:
| Type | Description |
|---|---|
tuple[ParameterInput, ...] | None
|
Tuple of ParameterInput entries, or None when selection/context is missing. |
_record_derived_values(record)
¶
Return persisted derived metric values from a record when available.
activations_per_minute_from_parameters(*, entity_type, entity_name, parameters, cooldown_key='cooldown')
¶
Compute activations/minute from a cooldown parameter.
Formula
activations_per_minute = 60 / cooldown_seconds
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_type
|
str
|
Entity category label (e.g. "guardian_chip"). |
required |
entity_name
|
str
|
Human-readable entity name for trace output. |
required |
parameters
|
tuple[ParameterInput, ...]
|
ParameterInput entries (raw + parsed values). |
required |
cooldown_key
|
str
|
Parameter key used for cooldown seconds. |
'cooldown'
|
Returns:
| Type | Description |
|---|---|
EffectResult
|
EffectResult with activations/minute and referenced parameters. |
category_for_metric(metric_key)
¶
Return the MetricCategory for a metric key, when registered.
coins_per_hour(coins, real_time_seconds)
¶
Compute coins per hour for a single run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coins
|
int
|
Total coins earned. |
required |
real_time_seconds
|
int
|
Run duration in seconds. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Coins per hour, or None when inputs are invalid. |
compute_metric_value(metric_key, *, record, coins, cash, interest_earned, cells, reroll_shards, wave, real_time_seconds, context, entity_type, entity_name, config)
¶
Compute a metric value and return used parameters + assumptions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metric_key
|
str
|
Metric key to compute. |
required |
record
|
object
|
Run-like object used for relationship-based metrics (e.g. usage presence). |
required |
coins
|
int | None
|
Observed coins for the run. |
required |
cash
|
int | None
|
Observed cash for the run. |
required |
interest_earned
|
int | None
|
Observed interest earned for the run. |
required |
cells
|
int | None
|
Observed cells for the run. |
required |
reroll_shards
|
int | None
|
Observed reroll shards for the run. |
required |
wave
|
int | None
|
Observed wave reached for the run. |
required |
real_time_seconds
|
int | None
|
Observed duration seconds for the run. |
required |
context
|
PlayerContextInput | None
|
Optional player context + selected parameters. |
required |
entity_type
|
str | None
|
Optional entity category for entity-scoped derived metrics. |
required |
entity_name
|
str | None
|
Optional entity name for entity-scoped derived metrics. |
required |
config
|
MetricComputeConfig
|
MetricComputeConfig. |
required |
Returns:
| Type | Description |
|---|---|
tuple[float | None, tuple[UsedParameter, ...], tuple[str, ...]]
|
Tuple of (value, used_parameters, assumptions). |
compute_observed_coins_per_hour(*, coins, real_time_seconds)
¶
Compute observed coins/hour from raw run fields.
effective_cooldown_seconds_from_parameters(*, entity_type, entity_name, parameters, cooldown_key='cooldown')
¶
Compute an effective cooldown in seconds from a cooldown parameter.
This is a validation-focused effect used to prove that: - wiki-derived parameter revisions are referenced by analysis at request time, and - derived metrics can be charted alongside observed metrics with explicit units.
Formula
effective_cooldown_seconds = cooldown_seconds
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_type
|
str
|
Entity category label (e.g. "ultimate_weapon"). |
required |
entity_name
|
str
|
Human-readable entity name for trace output. |
required |
parameters
|
tuple[ParameterInput, ...]
|
ParameterInput entries (raw + parsed values). |
required |
cooldown_key
|
str
|
Parameter key used for cooldown seconds. |
'cooldown'
|
Returns:
| Type | Description |
|---|---|
EffectResult
|
EffectResult with effective cooldown seconds and referenced parameters. |
get_metric_definition(metric_key)
¶
Return a MetricDefinition for a key, defaulting to observed coins/hour.
is_ultimate_weapon_observed_active(raw_text, *, ultimate_weapon_name)
¶
Return True when a Battle Report shows evidence of an Ultimate Weapon being active.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text as imported/stored. |
required |
ultimate_weapon_name
|
str
|
Display name for the Ultimate Weapon (e.g. "Black Hole"). |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the mapped Battle Report metric parses and is > 0; otherwise False. |
list_metric_definitions()
¶
Return the available metric definitions in a stable order.
uptime_percent_from_parameters(*, entity_type, entity_name, parameters, duration_key='duration', cooldown_key='cooldown')
¶
Compute uptime percent from duration and cooldown parameters.
Formula
uptime_percent = 100 * clamp(duration_seconds / cooldown_seconds, 0..1)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entity_type
|
str
|
Entity category label (e.g. "ultimate_weapon", "bot"). |
required |
entity_name
|
str
|
Human-readable entity name for trace output. |
required |
parameters
|
tuple[ParameterInput, ...]
|
ParameterInput entries (raw + parsed values). |
required |
duration_key
|
str
|
Parameter key used for duration seconds. |
'duration'
|
cooldown_key
|
str
|
Parameter key used for cooldown seconds. |
'cooldown'
|
Returns:
| Type | Description |
|---|---|
EffectResult
|
EffectResult with uptime percent and referenced parameters. |
validate_metric_registry()
¶
Validate that MetricDefinition keys and series registry keys match.
Raises:
| Type | Description |
|---|---|
ValueError
|
When registered metric keys drift between METRICS and the DEFAULT_REGISTRY. |
analysis.quantity
¶
Best-effort unit/quantity parsing utilities.
Phase 1.5 introduces a small normalization layer for compact numeric strings
commonly found in Battle Reports (e.g. 7.67M, x1.15, 15%).
This module is intentionally: - pure (no Django imports, no database writes), - defensive (never raises on unknown formats), - minimal (only supports formats needed by Phase 1 inputs so far).
_MAGNITUDE_MULTIPLIERS = {'': Decimal(1), 'k': Decimal(1000), 'm': Decimal(1000000), 'b': Decimal(1000000000), 't': Decimal(1000000000000), 'q': Decimal(1000000000000000), 'Q': Decimal(1000000000000000000)}
module-attribute
¶
Quantity
dataclass
¶
A parsed quantity with both raw and normalized representations.
Attributes:
| Name | Type | Description |
|---|---|---|
raw_value |
str
|
The original raw string value (trimmed). |
normalized_value |
Decimal | None
|
The parsed numeric value as a Decimal, or None if the value could not be parsed. |
magnitude |
str | None
|
The compact magnitude suffix (e.g. |
unit_type |
UnitType
|
The category of unit this value represents. |
UnitType
¶
Bases: Enum
Supported unit categories for Phase 1.5.
_parse_compact_number(value, *, unit_type)
¶
Parse 7.67M-style compact numbers.
_parse_decimal(number_text)
¶
Parse a Decimal from a Battle Report-style numeric string.
_parse_multiplier(value)
¶
Parse x1.15 multiplier strings.
_parse_percent(value)
¶
Parse percent strings like 15% into fractional multipliers.
is_known_magnitude_suffix(suffix)
¶
Return True when a compact magnitude suffix is recognized.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
suffix
|
str
|
Raw magnitude suffix (e.g. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the suffix is supported by the compact number parser. |
parse_quantity(raw_value, *, unit_type=UnitType.count)
¶
Parse a compact quantity string into a normalized Decimal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_value
|
str
|
Raw value string (e.g. |
required |
unit_type
|
UnitType
|
Unit category to assign for non-annotated values. |
count
|
Returns:
| Type | Description |
|---|---|
Quantity
|
Quantity where |
Notes
- A leading
xforcesunit_type=multiplierand parses the remainder. - A trailing
%forcesunit_type=multiplierand normalizes as a fraction (e.g.15%->0.15). - Magnitude suffixes are case-insensitive except for
Q(quintillion). - Supported suffixes include lowercase
k..qand uppercaseQ.
analysis.rates
¶
Rate calculations for the Analysis Engine.
Phase 1 intentionally ships a single derived rate metric: coins per hour.
coins_per_hour(coins, real_time_seconds)
¶
Compute coins per hour for a single run.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coins
|
int
|
Total coins earned. |
required |
real_time_seconds
|
int
|
Run duration in seconds. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
Coins per hour, or None when inputs are invalid. |
analysis.series_registry
¶
MetricSeries registry used by declarative chart configs.
This registry is a thin, analysis-layer description of which metric keys exist, which filter dimensions they support, and which transforms are allowed. It is used by the ChartConfig validator and the dashboard renderer.
The registry remains Django-free and describes capabilities only. Query scoping (date range, tier, preset, entity selection) happens at the view layer before records are passed into the Analysis Engine.
Aggregation = Literal['sum', 'avg']
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
¶
FilterKey = Literal['date_range', 'tier', 'preset', 'uw', 'guardian', 'bot']
module-attribute
¶
METRIC_AGGREGATION_OVERRIDES = {'guardian_damage': ('sum', 'avg'), 'guardian_summoned_enemies': ('sum', 'avg'), 'flame_bot_damage': ('sum', 'avg'), 'thunder_bot_stuns': ('sum', 'avg'), 'golden_bot_coins_earned': ('sum', 'avg')}
module-attribute
¶
MetricTransform = Literal['none', 'moving_average', 'cumulative', 'rate_per_hour']
module-attribute
¶
SourceModel = Literal['BattleReport', 'RunCombat', 'RunGuardian', 'RunBots']
module-attribute
¶
TimeIndex = Literal['timestamp', 'wave_number']
module-attribute
¶
_COMMON_FILTERS = frozenset({'date_range', 'tier', 'preset'})
module-attribute
¶
FormulaInspection
dataclass
¶
Result of inspecting a derived-metric formula.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
referenced_metric_keys
|
frozenset[str]
|
Metric keys referenced by name in the formula. |
required |
unknown_identifiers
|
frozenset[str]
|
Identifiers present in the formula that are not registered metric keys. |
required |
is_valid_syntax
|
bool
|
Whether the formula parses as a Python expression. |
required |
is_safe
|
bool
|
Whether the formula uses only the supported safe expression subset. |
required |
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 |
allowed_chart_builder_aggregations(spec)
¶
Return the allowed aggregations for Chart Builder selections.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
MetricSeriesSpec
|
MetricSeriesSpec to evaluate. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Aggregation, ...]
|
Tuple of allowed aggregation keys for Chart Builder use. |
analysis.units
¶
Unit validation and formatting helpers.
Phase 6 introduces a strict unit contract for any metric that is displayed in
dashboards. Unlike analysis.quantity.parse_quantity, this module is allowed
to fail fast when a value violates the expected unit contract.
Quantity
dataclass
¶
A parsed quantity with both raw and normalized representations.
Attributes:
| Name | Type | Description |
|---|---|---|
raw_value |
str
|
The original raw string value (trimmed). |
normalized_value |
Decimal | None
|
The parsed numeric value as a Decimal, or None if the value could not be parsed. |
magnitude |
str | None
|
The compact magnitude suffix (e.g. |
unit_type |
UnitType
|
The category of unit this value represents. |
UnitContract
dataclass
¶
Contract describing the expected unit type for a parsed value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit_type
|
UnitType
|
Expected UnitType for the value. |
required |
allow_zero
|
bool
|
Whether a numeric zero is considered valid. |
True
|
UnitType
¶
Bases: Enum
Supported unit categories for Phase 1.5.
UnitValidationError
¶
Bases: ValueError
Raised when a quantity does not satisfy a unit contract.
ValidatedQuantity
dataclass
¶
A validated quantity value that satisfies a UnitContract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_value
|
str
|
Raw value string (trimmed). |
required |
normalized_value
|
Decimal
|
Parsed Decimal value. |
required |
unit_type
|
UnitType
|
UnitType that was validated against the contract. |
required |
magnitude
|
str | None
|
Magnitude suffix (k/m/b/t/q) when present. |
required |
coerce_non_negative_int(quantity)
¶
Coerce a parsed Quantity to a non-negative integer if possible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quantity
|
Quantity
|
Quantity returned from parsing routines. |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
An int when normalized_value is present and non-negative; otherwise None. |
parse_quantity(raw_value, *, unit_type=UnitType.count)
¶
Parse a compact quantity string into a normalized Decimal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_value
|
str
|
Raw value string (e.g. |
required |
unit_type
|
UnitType
|
Unit category to assign for non-annotated values. |
count
|
Returns:
| Type | Description |
|---|---|
Quantity
|
Quantity where |
Notes
- A leading
xforcesunit_type=multiplierand parses the remainder. - A trailing
%forcesunit_type=multiplierand normalizes as a fraction (e.g.15%->0.15). - Magnitude suffixes are case-insensitive except for
Q(quintillion). - Supported suffixes include lowercase
k..qand uppercaseQ.
parse_validated_quantity(raw_value, *, contract)
¶
Parse and validate a quantity string against a strict unit contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_value
|
str
|
Raw Battle Report value (e.g. |
required |
contract
|
UnitContract
|
UnitContract describing the expected unit type. |
required |
Returns:
| Type | Description |
|---|---|
ValidatedQuantity
|
ValidatedQuantity with a non-None Decimal value. |
Raises:
| Type | Description |
|---|---|
UnitValidationError
|
When the parsed unit type does not match the contract. |
ValueError
|
When the value cannot be parsed into a numeric Decimal. |
analysis.uw_sync
¶
Deterministic Ultimate Weapon sync timeline helpers.
This module provides descriptive calculations for cooldown/duration alignment. It does not recommend timing changes and does not require database access.
UWSyncTimeline
dataclass
¶
Computed sync timeline suitable for charting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
labels
|
list[str]
|
Timeline labels ("t=0s", ...). |
required |
active_by_uw
|
dict[str, list[int]]
|
Mapping of UW name to 0/1 activity list aligned to labels. |
required |
overlap_all
|
list[int]
|
0/1 list aligned to labels where all entries are active. |
required |
overlap_percent_cumulative
|
list[float]
|
Cumulative overlap percent (0–100) aligned to labels. |
required |
horizon_seconds
|
int
|
Total modeled horizon in seconds. |
required |
UWTiming
dataclass
¶
Timing inputs for a single Ultimate Weapon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Display name. |
required |
cooldown_seconds
|
int
|
Cooldown in seconds (must be > 0). |
required |
duration_seconds
|
int
|
Active duration in seconds (must be >= 0). |
required |
_lcm(a, b)
¶
Return least common multiple for positive integers.
compute_uw_sync_timeline(timings, *, overlap_excluded_names=frozenset(), max_horizon_seconds=1800, step_seconds=1)
¶
Compute a descriptive sync timeline for a set of UWs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timings
|
Iterable[UWTiming]
|
Iterable of UWTiming entries (typically 3). |
required |
overlap_excluded_names
|
frozenset[str]
|
UW names excluded from overlap calculations (e.g. Death Wave). |
frozenset()
|
max_horizon_seconds
|
int
|
Upper bound for the modeled horizon. |
1800
|
step_seconds
|
int
|
Step size in seconds for timeline sampling. |
1
|
Returns:
| Type | Description |
|---|---|
UWSyncTimeline
|
UWSyncTimeline suitable for rendering with a line/step chart. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When timings are invalid (non-positive cooldowns, negative durations). |
analysis.uw_usage
¶
Observed Ultimate Weapon usage detection from Battle Report text.
This module provides a deterministic, best-effort mapping from Ultimate Weapon names to Battle Report metrics that can be used as evidence that a weapon was active during a run.
It intentionally stays in the analysis layer: - pure (no Django imports, no DB writes), - defensive on missing labels (missing -> inactive), - testable (stable inputs/outputs).
_UW_RULES_BY_NAME = {'black hole': UWUsageRule(label='Black Hole Damage', unit_type=(UnitType.damage)), 'chain lightning': UWUsageRule(label='Chain Lightning Damage', unit_type=(UnitType.damage)), 'death wave': UWUsageRule(label='Death Wave Damage', unit_type=(UnitType.damage)), 'golden tower': UWUsageRule(label='Coins From Golden Tower', unit_type=(UnitType.coins)), 'inner land mines': UWUsageRule(label='Inner Land Mine Damage', unit_type=(UnitType.damage)), 'poison swamp': UWUsageRule(label='Swamp Damage', unit_type=(UnitType.damage)), 'smart missiles': UWUsageRule(label='Smart Missile Damage', unit_type=(UnitType.damage)), 'spotlight': UWUsageRule(label='Destroyed in Spotlight', unit_type=(UnitType.count))}
module-attribute
¶
UWUsageRule
dataclass
¶
Rule describing how to infer UW activity from a Battle Report.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
Exact Battle Report label to read. |
required |
unit_type
|
UnitType
|
Unit category to validate when parsing the value. |
required |
UnitType
¶
Bases: Enum
Supported unit categories for Phase 1.5.
_title_case_uw_name(name_casefold)
¶
Return the canonical display casing for known Ultimate Weapon names.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name_casefold
|
str
|
Casefolded Ultimate Weapon name. |
required |
Returns:
| Type | Description |
|---|---|
str
|
Display name matching the known title case stored in definitions. |
extract_numeric_value(raw_text, *, label, unit_type)
¶
Extract and parse a numeric value for a specific Battle Report label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text. |
required |
label
|
str
|
Exact label as shown in Battle Reports. |
required |
unit_type
|
UnitType
|
Expected unit type for strict validation. |
required |
Returns:
| Type | Description |
|---|---|
ExtractedNumber | None
|
ExtractedNumber when the label is present and parseable; otherwise None. |
Notes
The parsing rules come from analysis.quantity.parse_quantity. This
wrapper additionally enforces that the raw string cannot represent a
different unit type (e.g. 15% for a coins metric).
is_ultimate_weapon_observed_active(raw_text, *, ultimate_weapon_name)
¶
Return True when a Battle Report shows evidence of an Ultimate Weapon being active.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text as imported/stored. |
required |
ultimate_weapon_name
|
str
|
Display name for the Ultimate Weapon (e.g. "Black Hole"). |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the mapped Battle Report metric parses and is > 0; otherwise False. |
observed_active_ultimate_weapons(raw_text)
¶
Return a set of Ultimate Weapon names observed as active in a Battle Report.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text as imported/stored. |
required |
Returns:
| Type | Description |
|---|---|
frozenset[str]
|
Frozen set of Ultimate Weapon display names that appear active in the run. |