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).
_KNOWN_SECTION_HEADINGS = frozenset({'records', 'damage', 'damage taken', 'bonus health gained', 'health regenerated', 'damage blocked', 'utility', 'counts', 'enemies hit by', 'killed with effect active', 'total enemies', 'enemies destroyed', 'enemies destroyed by', 'coins', 'cash', 'currencies', 'guardian', 'bots', 'fetch'})
module-attribute
¶
_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 |
MetricSelector
dataclass
¶
Describe how a metric line should be matched inside a Battle Report.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
label
|
str
|
Label text to match after normalization. |
required |
section
|
str | None
|
Optional normalized section heading. Use |
None
|
match_any_section
|
bool
|
When True, match the label regardless of section. |
False
|
ReportEntry
dataclass
¶
Single parsed Battle Report label/value row.
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.
_extract_raw_value_by_selector_scan(raw_text, *, selector)
¶
Best-effort fallback for single-space clipboard layouts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text. |
required |
selector
|
MetricSelector
|
Selector describing the target label and optional section. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The matched raw value when present; otherwise |
_extract_value_from_collapsed_prefix(collapsed_line, *, label)
¶
Return a value when a collapsed line begins with the selector label.
_iter_report_entries(raw_text)
¶
Parse Battle Report text into section-aware label/value rows.
_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).
extract_numeric_value_from_selectors(raw_text, *, selectors, unit_type)
¶
Extract and parse a numeric value from one of several label selectors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text. |
required |
selectors
|
tuple[MetricSelector, ...]
|
Ordered list of selector candidates. |
required |
unit_type
|
UnitType
|
Expected unit type for strict validation. |
required |
Returns:
| Type | Description |
|---|---|
ExtractedNumber | None
|
ExtractedNumber when any selector matches and parses; otherwise None. |
extract_raw_value_from_selectors(raw_text, *, selectors)
¶
Return the raw value for the first matching selector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text. |
required |
selectors
|
tuple[MetricSelector, ...]
|
Ordered selector candidates. |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
Raw string value when present; otherwise None. |
extract_sectioned_label_values(raw_text)
cached
¶
Extract normalized section+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[tuple[str | None, str], str]
|
Mapping of |
dict[tuple[str | None, str], str]
|
|
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_dissonance
|
bool
|
Whether the scope is limited to Dissonance runs. |
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 (derived)', 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='game_reported_coins_per_hour', label='Coins/hour (game-based)', description='Game-reported coins earned per hour from the Battle Report.', unit='coins/hour', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='game_reported_coins_per_hour', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cells_per_hour', label='Cells/hour (derived)', 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='game_reported_cells_per_hour', label='Cells/hour (game-based)', description='Game-reported cells per hour from the Battle Report.', unit='cells/hour', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='game_reported_cells_per_hour', 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='record_highest_coins_per_minute', label='Record: Highest Coins/Minute', description='Game-reported record metric from the Battle Report.', unit='coins/min', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_highest_coins_per_minute', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_wave_skip', label='Record: Largest Wave Skip', description='Largest single wave-skip event recorded by the game for the run.', unit='waves', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_wave_skip', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_most_coins_from_wave_skip', label='Record: Most Coins From Wave Skip', description='Most coins gained from a single wave-skip event in the run.', unit='coins', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_most_coins_from_wave_skip', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_most_cells_from_wave_skip', label='Record: Most Cells From Wave Skip', description='Most cells gained from a single wave-skip event in the run.', unit='cells', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_most_cells_from_wave_skip', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_smart_missile_stack', label='Record: Largest Smart Missile Stack', description='Largest Smart Missile stack recorded by the game for the run.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_smart_missile_stack', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_golden_combo', label='Record: Largest Golden Combo', description='Largest Golden Combo recorded by the game for the run.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_golden_combo', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_most_coins_from_golden_combo', label='Record: Most Coins From Golden Combo', description='Most coins gained from a Golden Combo event in the run.', unit='coins', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_most_coins_from_golden_combo', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_inner_landmine_charge', label='Record: Largest Inner Landmine Charge', description='Largest Inner Landmine charge recorded by the game for the run.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_inner_landmine_charge', allowed_transforms=(frozenset({'none', '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='tower_damage_taken', label='Tower Damage Taken', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='tower_damage_taken', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='wall_damage_taken', label='Wall Damage Taken', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='wall_damage_taken', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='lifesteal_healing', label='Lifesteal Healing', description=None, unit='health', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='lifesteal_healing', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='defense_percent_blocked_damage', label='Defense % Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='defense_percent_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='defense_absolute_blocked_damage', label='Defense Absolute Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='defense_absolute_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='chrono_field_blocked_damage', label='Chrono Field Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='chrono_field_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='chain_thunder_blocked_damage', label='Chain Thunder Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='chain_thunder_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='flame_bot_blocked_damage', label='Flame Bot Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='flame_bot_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='primordial_collapse_blocked_damage', label='Primordial Collapse Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='primordial_collapse_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='negative_mass_projector_blocked_damage', label='Negative Mass Projector Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='negative_mass_projector_blocked_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_by_projectiles', label='Destroyed by Projectiles', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_projectiles', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_chain_lightning', label='Destroyed by Chain Lightning', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_chain_lightning', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_smart_missiles', label='Destroyed by Smart Missiles', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_smart_missiles', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_inner_land_mines', label='Destroyed by Inner Land Mines', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_inner_land_mines', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_poison_swamp', label='Destroyed by Poison Swamp', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_poison_swamp', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_black_hole', label='Destroyed by Black Hole', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_black_hole', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_flame_bot', label='Destroyed by Flame Bot', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_flame_bot', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_other', label='Destroyed by Other', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_other', 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='coins_from_critical_coin', label='Coins From Critical Coin', description='Battle Report coins breakdown: coins earned from Critical Coin.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_critical_coin', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_golden_combo', label='Coins From Golden Combo', description='Battle Report coins breakdown: coins earned from Golden Combo.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_golden_combo', 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='gems_earned', label='Gems Earned', description='Battle Report currencies section: gems earned.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='gems_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='ad_gems_earned', label='Ad Gems Earned', description='Battle Report currencies section: ad gems earned.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='ad_gems_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='medals_earned', label='Medals Earned', description='Battle Report currencies section: medals earned.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='medals_earned', 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), MetricSeriesSpec(key='enemy_attack_levels_skipped', label='Enemy Attack Levels Skipped', description='Battle Report utility section: enemy attack levels skipped.', unit='levels', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemy_attack_levels_skipped', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemy_health_levels_skipped', label='Enemy Health Levels Skipped', description='Battle Report utility section: enemy health levels skipped.', unit='levels', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemy_health_levels_skipped', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='projectiles_count', label='Projectiles Count', description='Battle Report counts section: projectile count.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='projectiles_count', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='land_mines_spawned', label='Land Mines Spawned', description='Battle Report counts section: land mines spawned.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='land_mines_spawned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='death_defy_count', label='Death Defy', description='Battle Report counts section: death defy count.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='death_defy_count', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='energy_shield_hits_absorbed', label='Energy Shield Hits Absorbed', description='Battle Report counts section: energy shield hits absorbed.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='energy_shield_hits_absorbed', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='nuke_uses', label='Nuke Uses', description='Battle Report counts section: nuke uses.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='nuke_uses', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='second_wind_uses', label='Second Wind Uses', description='Battle Report counts section: second wind uses.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='second_wind_uses', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='demon_mode_uses', label='Demon Mode Uses', description='Battle Report counts section: demon mode uses.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='demon_mode_uses', 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 (derived)', unit='cells/hour', category=(MetricCategory.efficiency), kind='derived'), 'game_reported_cells_per_hour': MetricDefinition(key='game_reported_cells_per_hour', label='Cells/hour (game-based)', unit='cells/hour', category=(MetricCategory.efficiency), kind='observed'), '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 (derived)', unit='coins/hour', category=(MetricCategory.efficiency), kind='observed'), 'game_reported_coins_per_hour': MetricDefinition(key='game_reported_coins_per_hour', label='Coins/hour (game-based)', 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'), 'coins_from_critical_coin': MetricDefinition(key='coins_from_critical_coin', label='Coins From Critical Coin', unit='coins', category=(MetricCategory.economy), kind='observed'), 'coins_from_golden_combo': MetricDefinition(key='coins_from_golden_combo', label='Coins From Golden Combo', 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'), 'record_highest_coins_per_minute': MetricDefinition(key='record_highest_coins_per_minute', label='Record: Highest Coins/Minute', unit='coins/min', category=(MetricCategory.efficiency), kind='observed'), 'record_largest_wave_skip': MetricDefinition(key='record_largest_wave_skip', label='Record: Largest Wave Skip', unit='waves', category=(MetricCategory.utility), kind='observed'), 'record_most_coins_from_wave_skip': MetricDefinition(key='record_most_coins_from_wave_skip', label='Record: Most Coins From Wave Skip', unit='coins', category=(MetricCategory.utility), kind='observed'), 'record_most_cells_from_wave_skip': MetricDefinition(key='record_most_cells_from_wave_skip', label='Record: Most Cells From Wave Skip', unit='cells', category=(MetricCategory.utility), kind='observed'), 'record_largest_smart_missile_stack': MetricDefinition(key='record_largest_smart_missile_stack', label='Record: Largest Smart Missile Stack', unit='count', category=(MetricCategory.utility), kind='observed'), 'record_largest_golden_combo': MetricDefinition(key='record_largest_golden_combo', label='Record: Largest Golden Combo', unit='count', category=(MetricCategory.combat), kind='observed'), 'record_most_coins_from_golden_combo': MetricDefinition(key='record_most_coins_from_golden_combo', label='Record: Most Coins From Golden Combo', unit='coins', category=(MetricCategory.combat), kind='observed'), 'record_largest_inner_landmine_charge': MetricDefinition(key='record_largest_inner_landmine_charge', label='Record: Largest Inner Landmine Charge', unit='count', 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'), 'tower_damage_taken': MetricDefinition(key='tower_damage_taken', label='Tower Damage Taken', unit='damage', category=(MetricCategory.combat), kind='observed'), 'wall_damage_taken': MetricDefinition(key='wall_damage_taken', label='Wall Damage Taken', unit='damage', category=(MetricCategory.combat), kind='observed'), 'lifesteal_healing': MetricDefinition(key='lifesteal_healing', label='Lifesteal Healing', unit='health', category=(MetricCategory.combat), kind='observed'), 'defense_percent_blocked_damage': MetricDefinition(key='defense_percent_blocked_damage', label='Defense % Blocked Damage', unit='damage', category=(MetricCategory.combat), kind='observed'), 'defense_absolute_blocked_damage': MetricDefinition(key='defense_absolute_blocked_damage', label='Defense Absolute Blocked Damage', unit='damage', category=(MetricCategory.combat), kind='observed'), 'chrono_field_blocked_damage': MetricDefinition(key='chrono_field_blocked_damage', label='Chrono Field Blocked Damage', unit='damage', category=(MetricCategory.combat), kind='observed'), 'chain_thunder_blocked_damage': MetricDefinition(key='chain_thunder_blocked_damage', label='Chain Thunder Blocked Damage', unit='damage', category=(MetricCategory.combat), kind='observed'), 'flame_bot_blocked_damage': MetricDefinition(key='flame_bot_blocked_damage', label='Flame Bot Blocked Damage', unit='damage', category=(MetricCategory.combat), kind='observed'), 'primordial_collapse_blocked_damage': MetricDefinition(key='primordial_collapse_blocked_damage', label='Primordial Collapse Blocked Damage', unit='damage', category=(MetricCategory.combat), kind='observed'), 'negative_mass_projector_blocked_damage': MetricDefinition(key='negative_mass_projector_blocked_damage', label='Negative Mass Projector Blocked Damage', unit='damage', category=(MetricCategory.combat), 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_by_projectiles': MetricDefinition(key='enemies_destroyed_by_projectiles', label='Destroyed by Projectiles', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_chain_lightning': MetricDefinition(key='enemies_destroyed_by_chain_lightning', label='Destroyed by Chain Lightning', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_smart_missiles': MetricDefinition(key='enemies_destroyed_by_smart_missiles', label='Destroyed by Smart Missiles', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_inner_land_mines': MetricDefinition(key='enemies_destroyed_by_inner_land_mines', label='Destroyed by Inner Land Mines', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_poison_swamp': MetricDefinition(key='enemies_destroyed_by_poison_swamp', label='Destroyed by Poison Swamp', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_black_hole': MetricDefinition(key='enemies_destroyed_by_black_hole', label='Destroyed by Black Hole', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_flame_bot': MetricDefinition(key='enemies_destroyed_by_flame_bot', label='Destroyed by Flame Bot', unit='count', category=(MetricCategory.enemy_destruction), kind='observed'), 'enemies_destroyed_by_other': MetricDefinition(key='enemies_destroyed_by_other', label='Destroyed by Other', 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'), 'gems_earned': MetricDefinition(key='gems_earned', label='Gems Earned', unit='count', category=(MetricCategory.fetch), kind='observed'), 'ad_gems_earned': MetricDefinition(key='ad_gems_earned', label='Ad Gems Earned', unit='count', category=(MetricCategory.fetch), kind='observed'), 'medals_earned': MetricDefinition(key='medals_earned', label='Medals Earned', 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'), 'enemy_attack_levels_skipped': MetricDefinition(key='enemy_attack_levels_skipped', label='Enemy Attack Levels Skipped', unit='levels', category=(MetricCategory.utility), kind='observed'), 'enemy_health_levels_skipped': MetricDefinition(key='enemy_health_levels_skipped', label='Enemy Health Levels Skipped', unit='levels', category=(MetricCategory.utility), kind='observed'), 'projectiles_count': MetricDefinition(key='projectiles_count', label='Projectiles Count', unit='count', category=(MetricCategory.utility), kind='observed'), 'land_mines_spawned': MetricDefinition(key='land_mines_spawned', label='Land Mines Spawned', unit='count', category=(MetricCategory.utility), kind='observed'), 'death_defy_count': MetricDefinition(key='death_defy_count', label='Death Defy', unit='count', category=(MetricCategory.utility), kind='observed'), 'energy_shield_hits_absorbed': MetricDefinition(key='energy_shield_hits_absorbed', label='Energy Shield Hits Absorbed', unit='count', category=(MetricCategory.utility), kind='observed'), 'nuke_uses': MetricDefinition(key='nuke_uses', label='Nuke Uses', unit='count', category=(MetricCategory.utility), kind='observed'), 'second_wind_uses': MetricDefinition(key='second_wind_uses', label='Second Wind Uses', unit='count', category=(MetricCategory.utility), kind='observed'), 'demon_mode_uses': MetricDefinition(key='demon_mode_uses', label='Demon Mode Uses', unit='count', category=(MetricCategory.utility), 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 = {'game_reported_coins_per_hour': RawTextMetricSpec(selectors=(_top_level('Coins Per Hour'),), unit_type=(UnitType.coins)), 'game_reported_cells_per_hour': RawTextMetricSpec(selectors=(_top_level('Cells Per Hour'),), unit_type=(UnitType.count)), 'record_highest_coins_per_minute': RawTextMetricSpec(selectors=(_scoped('Records', 'Highest Coins / Minute'),), unit_type=(UnitType.coins)), 'record_largest_wave_skip': RawTextMetricSpec(selectors=(_scoped('Records', 'Largest Wave Skip'),), unit_type=(UnitType.count)), 'record_most_coins_from_wave_skip': RawTextMetricSpec(selectors=(_scoped('Records', 'Most Coins From Wave Skip'),), unit_type=(UnitType.coins)), 'record_most_cells_from_wave_skip': RawTextMetricSpec(selectors=(_scoped('Records', 'Most Cells From Wave Skip'),), unit_type=(UnitType.count)), 'record_largest_smart_missile_stack': RawTextMetricSpec(selectors=(_scoped('Records', 'Largest Smart Missile Stack'),), unit_type=(UnitType.count)), 'record_largest_golden_combo': RawTextMetricSpec(selectors=(_scoped('Records', 'Largest Golden Combo'),), unit_type=(UnitType.count)), 'record_most_coins_from_golden_combo': RawTextMetricSpec(selectors=(_scoped('Records', 'Most Coins From Golden Combo'),), unit_type=(UnitType.coins)), 'record_largest_inner_landmine_charge': RawTextMetricSpec(selectors=(_scoped('Records', 'Largest Inner Landmine Charge'),), unit_type=(UnitType.count)), 'coins_from_death_wave': RawTextMetricSpec(selectors=(_legacy('Coins From Death Wave'), _scoped('Coins', 'Death Wave')), unit_type=(UnitType.coins)), 'interest_earned': RawTextMetricSpec(selectors=(_legacy('Interest earned'), _scoped('Cash', 'Interest earned')), unit_type=(UnitType.cash)), 'cash_from_golden_tower': RawTextMetricSpec(selectors=(_legacy('Cash From Golden Tower'), _scoped('Cash', 'Golden Tower')), unit_type=(UnitType.cash)), 'coins_from_golden_tower': RawTextMetricSpec(selectors=(_legacy('Coins From Golden Tower'), _scoped('Coins', 'Golden Tower')), unit_type=(UnitType.coins)), 'coins_from_black_hole': RawTextMetricSpec(selectors=(_legacy('Coins From Black Hole'), _scoped('Coins', 'Black Hole')), unit_type=(UnitType.coins)), 'coins_from_spotlight': RawTextMetricSpec(selectors=(_legacy('Coins From Spotlight'), _scoped('Coins', 'Spotlight')), unit_type=(UnitType.coins)), 'coins_from_orb': RawTextMetricSpec(selectors=(_legacy('Coins From Orb'), _scoped('Coins', 'Orbs')), unit_type=(UnitType.coins)), 'coins_from_coin_upgrade': RawTextMetricSpec(selectors=(_legacy('Coins from Coin Upgrade'), _scoped('Coins', 'Coin Bonus Upgrade')), unit_type=(UnitType.coins)), 'coins_from_coin_bonuses': RawTextMetricSpec(selectors=(_legacy('Coins from Coin Bonuses'), _scoped('Coins', 'Coins From Coin Bonuses')), unit_type=(UnitType.coins)), 'coins_from_critical_coin': RawTextMetricSpec(selectors=(_legacy('Coins From Critical Coin'), _scoped('Coins', 'Critical Coin')), unit_type=(UnitType.coins)), 'coins_from_golden_combo': RawTextMetricSpec(selectors=(_legacy('Coins From Golden Combo'), _scoped('Coins', 'Golden Combo')), unit_type=(UnitType.coins)), 'free_attack_upgrades': RawTextMetricSpec(selectors=(_legacy('Free Attack Upgrade'), _scoped('Utility', 'Free Attack Upgrade')), unit_type=(UnitType.count)), 'free_defense_upgrades': RawTextMetricSpec(selectors=(_legacy('Free Defense Upgrade'), _scoped('Utility', 'Free Defense Upgrade')), unit_type=(UnitType.count)), 'free_utility_upgrades': RawTextMetricSpec(selectors=(_legacy('Free Utility Upgrade'), _scoped('Utility', 'Free Utility Upgrade')), unit_type=(UnitType.count)), 'recovery_packages': RawTextMetricSpec(selectors=(_legacy('Recovery Packages'), _scoped('Utility', 'Recovery Packages')), unit_type=(UnitType.count)), 'damage_dealt': RawTextMetricSpec(selectors=(_legacy('Damage dealt'), _scoped('Damage', 'Damage Dealt')), unit_type=(UnitType.damage)), 'projectiles_damage': RawTextMetricSpec(selectors=(_legacy('Projectiles Damage'), _scoped('Damage', 'Projectiles')), unit_type=(UnitType.damage)), 'thorn_damage': RawTextMetricSpec(selectors=(_legacy('Thorn Damage'), _legacy('Thorn damage'), _scoped('Damage', 'Thorns')), unit_type=(UnitType.damage)), 'orb_damage': RawTextMetricSpec(selectors=(_legacy('Orb Damage'), _scoped('Damage', 'Orbs')), unit_type=(UnitType.damage)), 'land_mine_damage': RawTextMetricSpec(selectors=(_legacy('Land Mine Damage'), _scoped('Damage', 'Land Mines')), unit_type=(UnitType.damage)), 'inner_land_mine_damage': RawTextMetricSpec(selectors=(_legacy('Inner Land Mine Damage'), _scoped('Damage', 'Inner Land Mines')), unit_type=(UnitType.damage)), 'chain_lightning_damage': RawTextMetricSpec(selectors=(_legacy('Chain Lightning Damage'), _scoped('Damage', 'Chain Lightning')), unit_type=(UnitType.damage)), 'death_wave_damage': RawTextMetricSpec(selectors=(_legacy('Death Wave Damage'), _scoped('Damage', 'Death Wave')), unit_type=(UnitType.damage)), 'death_ray_damage': RawTextMetricSpec(selectors=(_legacy('Death Ray Damage'), _scoped('Damage', 'Death Ray')), unit_type=(UnitType.damage)), 'smart_missile_damage': RawTextMetricSpec(selectors=(_legacy('Smart Missile Damage'), _scoped('Damage', 'Smart Missiles')), unit_type=(UnitType.damage)), 'black_hole_damage': RawTextMetricSpec(selectors=(_legacy('Black Hole Damage'), _legacy('Blackhole Damage'), _scoped('Damage', 'Black Hole')), unit_type=(UnitType.damage)), 'swamp_damage': RawTextMetricSpec(selectors=(_legacy('Swamp Damage'), _scoped('Damage', 'Poison Swamp')), unit_type=(UnitType.damage)), 'electrons_damage': RawTextMetricSpec(selectors=(_legacy('Electrons Damage'), _scoped('Damage', 'Electrons')), unit_type=(UnitType.damage)), 'rend_armor_damage': RawTextMetricSpec(selectors=(_legacy('Rend Armor Damage'), _scoped('Damage', 'Rend Armor')), unit_type=(UnitType.damage)), 'flame_bot_damage': RawTextMetricSpec(selectors=(_legacy('Flame Bot Damage'), _scoped('Damage', 'Flame Bot')), unit_type=(UnitType.damage)), 'golden_bot_coins_earned': RawTextMetricSpec(selectors=(_legacy('Golden Bot Coins Earned'), _scoped('Coins', 'Golden Bot')), unit_type=(UnitType.coins)), 'guardian_damage': RawTextMetricSpec(selectors=(_legacy('Damage'), _scoped('Guardian', 'Damage'), _scoped('Damage', 'Attack Chip')), unit_type=(UnitType.damage)), 'tower_damage_taken': RawTextMetricSpec(selectors=(_scoped('Damage Taken', 'Tower'),), unit_type=(UnitType.damage)), 'wall_damage_taken': RawTextMetricSpec(selectors=(_scoped('Damage Taken', 'Wall'),), unit_type=(UnitType.damage)), 'hp_from_death_wave': RawTextMetricSpec(selectors=(_legacy('HP From Death Wave'), _scoped('Bonus Health Gained', 'From Death Wave')), unit_type=(UnitType.count)), 'lifesteal_healing': RawTextMetricSpec(selectors=(_legacy('Lifesteal'), _scoped('Health Regenerated', 'Lifesteal')), unit_type=(UnitType.count)), 'tower_health_regen': RawTextMetricSpec(selectors=(_scoped('Health Regenerated', 'Tower Health Regen'),), unit_type=(UnitType.count)), 'wall_health_regen': RawTextMetricSpec(selectors=(_scoped('Health Regenerated', 'Wall Health Regen'),), unit_type=(UnitType.count)), 'defense_percent_blocked_damage': RawTextMetricSpec(selectors=(_scoped('Damage Blocked', 'Defense %'),), unit_type=(UnitType.damage)), 'defense_absolute_blocked_damage': RawTextMetricSpec(selectors=(_scoped('Damage Blocked', 'Defense Absolute'),), unit_type=(UnitType.damage)), 'chrono_field_blocked_damage': RawTextMetricSpec(selectors=(_scoped('Damage Blocked', 'Chrono Field'),), unit_type=(UnitType.damage)), 'chain_thunder_blocked_damage': RawTextMetricSpec(selectors=(_scoped('Damage Blocked', 'Chain Thunder'),), unit_type=(UnitType.damage)), 'flame_bot_blocked_damage': RawTextMetricSpec(selectors=(_scoped('Damage Blocked', 'Flame Bot'),), unit_type=(UnitType.damage)), 'primordial_collapse_blocked_damage': RawTextMetricSpec(selectors=(_scoped('Damage Blocked', 'Primordial Collapse'),), unit_type=(UnitType.damage)), 'negative_mass_projector_blocked_damage': RawTextMetricSpec(selectors=(_scoped('Damage Blocked', 'Negative Mass Projector'),), unit_type=(UnitType.damage)), 'enemy_attack_levels_skipped': RawTextMetricSpec(selectors=(_scoped('Utility', 'Enemy Attack Levels Skipped'),), unit_type=(UnitType.count)), 'enemy_health_levels_skipped': RawTextMetricSpec(selectors=(_scoped('Utility', 'Enemy Health Levels Skipped'),), unit_type=(UnitType.count)), 'projectiles_count': RawTextMetricSpec(selectors=(_legacy('Projectiles Count'), _scoped('Counts', 'Projectiles Count')), unit_type=(UnitType.count)), 'land_mines_spawned': RawTextMetricSpec(selectors=(_legacy('Land Mines Spawned'), _scoped('Counts', 'Land Mines Spawned')), unit_type=(UnitType.count)), 'thunder_bot_stuns': RawTextMetricSpec(selectors=(_legacy('Thunder Bot Stuns'), _scoped('Counts', 'Thunder Bot Stuns')), unit_type=(UnitType.count)), 'waves_skipped': RawTextMetricSpec(selectors=(_legacy('Waves Skipped'), _scoped('Counts', 'Waves Skipped')), unit_type=(UnitType.count)), 'death_defy_count': RawTextMetricSpec(selectors=(_scoped('Counts', 'Death Defy'),), unit_type=(UnitType.count)), 'energy_shield_hits_absorbed': RawTextMetricSpec(selectors=(_scoped('Counts', 'Hits Absorbed By Energy Shield'),), unit_type=(UnitType.count)), 'nuke_uses': RawTextMetricSpec(selectors=(_scoped('Counts', 'Nuke'),), unit_type=(UnitType.count)), 'second_wind_uses': RawTextMetricSpec(selectors=(_scoped('Counts', 'Second Wind'),), unit_type=(UnitType.count)), 'demon_mode_uses': RawTextMetricSpec(selectors=(_scoped('Counts', 'Demon Mode'),), unit_type=(UnitType.count)), 'enemies_hit_by_projectiles': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Projectiles'),), unit_type=(UnitType.count)), 'enemies_hit_by_thorns': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Thorns'),), unit_type=(UnitType.count)), 'enemies_hit_by_orbs': RawTextMetricSpec(selectors=(_legacy('Enemies Hit by Orbs'), _scoped('Enemies Hit By', 'Orbs')), unit_type=(UnitType.count)), 'enemies_hit_by_death_ray': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Death Ray'),), unit_type=(UnitType.count)), 'enemies_hit_by_chain_lightning': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Chain Lightning'),), unit_type=(UnitType.count)), 'enemies_hit_by_smart_missiles': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Smart Missiles'),), unit_type=(UnitType.count)), 'enemies_hit_by_inner_land_mines': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Inner Land Mines'),), unit_type=(UnitType.count)), 'enemies_hit_by_poison_swamp': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Poison Swamp'),), unit_type=(UnitType.count)), 'enemies_hit_by_death_wave': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Death Wave'),), unit_type=(UnitType.count)), 'enemies_hit_by_black_hole': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Black Hole'),), unit_type=(UnitType.count)), 'enemies_hit_by_chrono_field': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Chrono Field'),), unit_type=(UnitType.count)), 'enemies_hit_by_land_mines': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Land Mines'),), unit_type=(UnitType.count)), 'enemies_hit_by_thunder_bot': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Thunder Bot'),), unit_type=(UnitType.count)), 'enemies_hit_by_flame_bot': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Flame Bot'),), unit_type=(UnitType.count)), 'enemies_hit_by_attack_chip': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Attack Chip'),), unit_type=(UnitType.count)), 'enemies_hit_by_orbital_augment': RawTextMetricSpec(selectors=(_scoped('Enemies Hit By', 'Orbital Augment'),), unit_type=(UnitType.count)), 'kills_with_golden_tower_active': RawTextMetricSpec(selectors=(_scoped('Killed With Effect Active', 'Golden Tower'),), unit_type=(UnitType.count)), 'kills_with_death_wave_active': RawTextMetricSpec(selectors=(_scoped('Killed With Effect Active', 'Death Wave'),), unit_type=(UnitType.count)), 'kills_with_spotlight_active': RawTextMetricSpec(selectors=(_scoped('Killed With Effect Active', 'Spotlight'),), unit_type=(UnitType.count)), 'kills_with_amplify_bot_active': RawTextMetricSpec(selectors=(_scoped('Killed With Effect Active', 'Amplify Bot'),), unit_type=(UnitType.count)), 'kills_with_golden_bot_active': RawTextMetricSpec(selectors=(_scoped('Killed With Effect Active', 'Golden Bot'),), unit_type=(UnitType.count)), 'kills_with_death_penalty_active': RawTextMetricSpec(selectors=(_scoped('Killed With Effect Active', 'Death Penalty'),), unit_type=(UnitType.count)), 'enemies_destroyed_basic': RawTextMetricSpec(selectors=(_legacy('Basic'), _scoped('Total Enemies', 'Basic'), _scoped('Enemies Destroyed', 'Basic')), unit_type=(UnitType.count)), 'enemies_destroyed_fast': RawTextMetricSpec(selectors=(_legacy('Fast'), _scoped('Total Enemies', 'Fast'), _scoped('Enemies Destroyed', 'Fast')), unit_type=(UnitType.count)), 'enemies_destroyed_tank': RawTextMetricSpec(selectors=(_legacy('Tank'), _scoped('Total Enemies', 'Tank'), _scoped('Enemies Destroyed', 'Tank')), unit_type=(UnitType.count)), 'enemies_destroyed_ranged': RawTextMetricSpec(selectors=(_legacy('Ranged'), _scoped('Total Enemies', 'Ranged'), _scoped('Enemies Destroyed', 'Ranged')), unit_type=(UnitType.count)), 'enemies_destroyed_boss': RawTextMetricSpec(selectors=(_legacy('Boss'), _scoped('Total Enemies', 'Boss'), _scoped('Enemies Destroyed', 'Boss')), unit_type=(UnitType.count)), 'enemies_destroyed_protector': RawTextMetricSpec(selectors=(_legacy('Protector'), _scoped('Total Enemies', 'Protector'), _scoped('Enemies Destroyed', 'Protector')), unit_type=(UnitType.count)), 'enemies_destroyed_vampires': RawTextMetricSpec(selectors=(_legacy('Vampires'), _scoped('Total Enemies', 'Vampires'), _scoped('Enemies Destroyed', 'Vampires')), unit_type=(UnitType.count)), 'enemies_destroyed_rays': RawTextMetricSpec(selectors=(_legacy('Rays'), _scoped('Total Enemies', 'Rays'), _scoped('Enemies Destroyed', 'Rays')), unit_type=(UnitType.count)), 'enemies_destroyed_scatters': RawTextMetricSpec(selectors=(_legacy('Scatters'), _scoped('Total Enemies', 'Scatters'), _scoped('Enemies Destroyed', 'Scatters')), unit_type=(UnitType.count)), 'enemies_destroyed_saboteur': RawTextMetricSpec(selectors=(_legacy('Saboteur'), _scoped('Total Enemies', 'Saboteur'), _scoped('Enemies Destroyed', 'Saboteur')), unit_type=(UnitType.count)), 'enemies_destroyed_commander': RawTextMetricSpec(selectors=(_legacy('Commander'), _scoped('Total Enemies', 'Commander'), _scoped('Enemies Destroyed', 'Commander')), unit_type=(UnitType.count)), 'enemies_destroyed_overcharge': RawTextMetricSpec(selectors=(_legacy('Overcharge'), _scoped('Total Enemies', 'Overcharge'), _scoped('Enemies Destroyed', 'Overcharge')), unit_type=(UnitType.count)), 'enemies_destroyed_by_projectiles': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Projectiles'),), unit_type=(UnitType.count)), 'enemies_destroyed_by_orbs': RawTextMetricSpec(selectors=(_legacy('Destroyed By Orbs'), _scoped('Enemies Destroyed By', 'Orbs')), unit_type=(UnitType.count)), 'enemies_destroyed_by_thorns': RawTextMetricSpec(selectors=(_legacy('Destroyed by Thorns'), _scoped('Enemies Destroyed By', 'Thorns')), unit_type=(UnitType.count)), 'enemies_destroyed_by_death_ray': RawTextMetricSpec(selectors=(_legacy('Destroyed by Death Ray'), _scoped('Enemies Destroyed By', 'Death Ray')), unit_type=(UnitType.count)), 'enemies_destroyed_by_land_mine': RawTextMetricSpec(selectors=(_legacy('Destroyed by Land Mine'), _scoped('Enemies Destroyed By', 'Land Mines')), unit_type=(UnitType.count)), 'enemies_destroyed_by_chain_lightning': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Chain Lightning'),), unit_type=(UnitType.count)), 'enemies_destroyed_by_smart_missiles': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Smart Missiles'),), unit_type=(UnitType.count)), 'enemies_destroyed_by_inner_land_mines': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Inner Land Mines'),), unit_type=(UnitType.count)), 'enemies_destroyed_by_poison_swamp': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Poison Swamp'),), unit_type=(UnitType.count)), 'enemies_destroyed_by_black_hole': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Black Hole'),), unit_type=(UnitType.count)), 'enemies_destroyed_by_flame_bot': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Flame Bot'),), unit_type=(UnitType.count)), 'enemies_destroyed_by_other': RawTextMetricSpec(selectors=(_scoped('Enemies Destroyed By', 'Other'),), unit_type=(UnitType.count)), 'enemies_destroyed_in_spotlight': RawTextMetricSpec(selectors=(_legacy('Destroyed in Spotlight'),), unit_type=(UnitType.count)), 'enemies_destroyed_in_golden_bot': RawTextMetricSpec(selectors=(_legacy('Destroyed in Golden Bot'),), unit_type=(UnitType.count)), 'guardian_summoned_enemies': RawTextMetricSpec(selectors=(_legacy('Summoned enemies'), _scoped('Total Enemies', 'Summoned Enemies'), _scoped('Guardian', 'Summoned enemies')), unit_type=(UnitType.count)), 'guardian_coins_stolen': RawTextMetricSpec(selectors=(_legacy('Guardian coins stolen'), _scoped('Guardian', 'Guardian coins stolen'), _scoped('Coins', 'Bounty Coins')), unit_type=(UnitType.coins)), 'guardian_coins_fetched': RawTextMetricSpec(selectors=(_legacy('Coins Fetched'), _scoped('Coins', 'Coins Fetched'), _scoped('Guardian', 'Coins Fetched')), unit_type=(UnitType.coins)), 'gems_earned': RawTextMetricSpec(selectors=(_scoped('Currencies', 'Gems'),), unit_type=(UnitType.count)), 'ad_gems_earned': RawTextMetricSpec(selectors=(_scoped('Currencies', 'Ad Gems'),), unit_type=(UnitType.count)), 'medals_earned': RawTextMetricSpec(selectors=(_scoped('Currencies', 'Medals'),), unit_type=(UnitType.count)), 'guardian_gems_fetched': RawTextMetricSpec(selectors=(_scoped('Currencies', 'Fetch Gems'), _scoped('Guardian', 'Gems')), unit_type=(UnitType.count)), 'guardian_medals_fetched': RawTextMetricSpec(selectors=(_scoped('Guardian', 'Medals'),), unit_type=(UnitType.count)), 'guardian_reroll_shards_fetched': RawTextMetricSpec(selectors=(_legacy('Reroll Shards'), _scoped('Guardian', 'Reroll Shards'), _scoped('Currencies', 'Reroll Shards Fetched')), unit_type=(UnitType.count)), 'guardian_cannon_shards_fetched': RawTextMetricSpec(selectors=(_legacy('Cannon Shards'), _scoped('Guardian', 'Cannon Shards'), _scoped('Currencies', 'Cannon Shards')), unit_type=(UnitType.count)), 'guardian_armor_shards_fetched': RawTextMetricSpec(selectors=(_legacy('Armor Shards'), _scoped('Guardian', 'Armor Shards'), _scoped('Currencies', 'Armor Shards')), unit_type=(UnitType.count)), 'guardian_generator_shards_fetched': RawTextMetricSpec(selectors=(_legacy('Generator Shards'), _scoped('Guardian', 'Generator Shards'), _scoped('Currencies', 'Generator Shards')), unit_type=(UnitType.count)), 'guardian_core_shards_fetched': RawTextMetricSpec(selectors=(_legacy('Core Shards'), _scoped('Guardian', 'Core Shards'), _scoped('Currencies', 'Core Shards')), unit_type=(UnitType.count)), 'guardian_common_modules_fetched': RawTextMetricSpec(selectors=(_legacy('Common Modules'), _scoped('Guardian', 'Common Modules'), _scoped('Currencies', 'Common Modules')), unit_type=(UnitType.count)), 'guardian_rare_modules_fetched': RawTextMetricSpec(selectors=(_legacy('Rare Modules'), _scoped('Guardian', 'Rare Modules'), _scoped('Currencies', 'Rare Modules')), unit_type=(UnitType.count))}
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 derived metric values, backfilling missing raw-text metrics on demand.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
object
|
Run-like object that may expose |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Combined metric values where persisted derived metrics remain authoritative |
dict[str, float]
|
and any missing raw-text metrics are filled from the current Battle Report |
dict[str, float]
|
text when available. |
_record_raw_text_metric_values(record)
¶
Parse raw Battle Report metrics once per record for fallback reads.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
object
|
Run-like object that may expose |
required |
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
Parsed raw-text metric values, or an empty mapping when parsing is not possible. |
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. |
extract_raw_text_metrics(raw_text)
¶
Return all parseable metric values from Battle Report text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw_text
|
str
|
Raw Battle Report text to parse. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, ExtractedNumber]
|
Mapping of metric_key -> ExtractedNumber for every metric present in the |
dict[str, ExtractedNumber]
|
text. Missing or invalid labels are omitted, keeping extraction |
dict[str, ExtractedNumber]
|
defensive and non-fatal. |
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), 's': Decimal(1000000000000000000000), 'S': Decimal(1000000000000000000000000), 'o': Decimal(1000000000000000000000000000), 'O': Decimal(1000000000000000000000000000000)}
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 upper-tier pairs.
- Supported suffixes include lowercase
k..q, uppercaseQ, lowercases/o, and uppercaseS/O.
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 (derived)', 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='game_reported_coins_per_hour', label='Coins/hour (game-based)', description='Game-reported coins earned per hour from the Battle Report.', unit='coins/hour', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='game_reported_coins_per_hour', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='cells_per_hour', label='Cells/hour (derived)', 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='game_reported_cells_per_hour', label='Cells/hour (game-based)', description='Game-reported cells per hour from the Battle Report.', unit='cells/hour', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='game_reported_cells_per_hour', 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='record_highest_coins_per_minute', label='Record: Highest Coins/Minute', description='Game-reported record metric from the Battle Report.', unit='coins/min', category=(MetricCategory.efficiency), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_highest_coins_per_minute', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_wave_skip', label='Record: Largest Wave Skip', description='Largest single wave-skip event recorded by the game for the run.', unit='waves', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_wave_skip', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_most_coins_from_wave_skip', label='Record: Most Coins From Wave Skip', description='Most coins gained from a single wave-skip event in the run.', unit='coins', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_most_coins_from_wave_skip', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_most_cells_from_wave_skip', label='Record: Most Cells From Wave Skip', description='Most cells gained from a single wave-skip event in the run.', unit='cells', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_most_cells_from_wave_skip', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_smart_missile_stack', label='Record: Largest Smart Missile Stack', description='Largest Smart Missile stack recorded by the game for the run.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_smart_missile_stack', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_golden_combo', label='Record: Largest Golden Combo', description='Largest Golden Combo recorded by the game for the run.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_golden_combo', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_most_coins_from_golden_combo', label='Record: Most Coins From Golden Combo', description='Most coins gained from a Golden Combo event in the run.', unit='coins', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_most_coins_from_golden_combo', allowed_transforms=(frozenset({'none', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='record_largest_inner_landmine_charge', label='Record: Largest Inner Landmine Charge', description='Largest Inner Landmine charge recorded by the game for the run.', unit='count', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='avg', time_index='timestamp', value_field='record_largest_inner_landmine_charge', allowed_transforms=(frozenset({'none', '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='tower_damage_taken', label='Tower Damage Taken', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='tower_damage_taken', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='wall_damage_taken', label='Wall Damage Taken', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='wall_damage_taken', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='lifesteal_healing', label='Lifesteal Healing', description=None, unit='health', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='lifesteal_healing', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='defense_percent_blocked_damage', label='Defense % Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='defense_percent_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='defense_absolute_blocked_damage', label='Defense Absolute Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='defense_absolute_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='chrono_field_blocked_damage', label='Chrono Field Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='chrono_field_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='chain_thunder_blocked_damage', label='Chain Thunder Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='chain_thunder_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='flame_bot_blocked_damage', label='Flame Bot Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='flame_bot_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='primordial_collapse_blocked_damage', label='Primordial Collapse Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='primordial_collapse_blocked_damage', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='negative_mass_projector_blocked_damage', label='Negative Mass Projector Blocked Damage', description=None, unit='damage', category=(MetricCategory.combat), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='negative_mass_projector_blocked_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_by_projectiles', label='Destroyed by Projectiles', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_projectiles', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_chain_lightning', label='Destroyed by Chain Lightning', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_chain_lightning', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_smart_missiles', label='Destroyed by Smart Missiles', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_smart_missiles', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_inner_land_mines', label='Destroyed by Inner Land Mines', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_inner_land_mines', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_poison_swamp', label='Destroyed by Poison Swamp', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_poison_swamp', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_black_hole', label='Destroyed by Black Hole', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_black_hole', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_flame_bot', label='Destroyed by Flame Bot', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_flame_bot', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemies_destroyed_by_other', label='Destroyed by Other', description=None, unit='enemies', category=(MetricCategory.enemy_destruction), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemies_destroyed_by_other', 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='coins_from_critical_coin', label='Coins From Critical Coin', description='Battle Report coins breakdown: coins earned from Critical Coin.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_critical_coin', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='coins_from_golden_combo', label='Coins From Golden Combo', description='Battle Report coins breakdown: coins earned from Golden Combo.', unit='coins', category=(MetricCategory.economy), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='coins_from_golden_combo', 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='gems_earned', label='Gems Earned', description='Battle Report currencies section: gems earned.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='gems_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='ad_gems_earned', label='Ad Gems Earned', description='Battle Report currencies section: ad gems earned.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='ad_gems_earned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='medals_earned', label='Medals Earned', description='Battle Report currencies section: medals earned.', unit='count', category=(MetricCategory.fetch), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='medals_earned', 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), MetricSeriesSpec(key='enemy_attack_levels_skipped', label='Enemy Attack Levels Skipped', description='Battle Report utility section: enemy attack levels skipped.', unit='levels', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemy_attack_levels_skipped', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='enemy_health_levels_skipped', label='Enemy Health Levels Skipped', description='Battle Report utility section: enemy health levels skipped.', unit='levels', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='enemy_health_levels_skipped', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='projectiles_count', label='Projectiles Count', description='Battle Report counts section: projectile count.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='projectiles_count', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='land_mines_spawned', label='Land Mines Spawned', description='Battle Report counts section: land mines spawned.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='land_mines_spawned', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='death_defy_count', label='Death Defy', description='Battle Report counts section: death defy count.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='death_defy_count', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='energy_shield_hits_absorbed', label='Energy Shield Hits Absorbed', description='Battle Report counts section: energy shield hits absorbed.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='energy_shield_hits_absorbed', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='nuke_uses', label='Nuke Uses', description='Battle Report counts section: nuke uses.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='nuke_uses', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='second_wind_uses', label='Second Wind Uses', description='Battle Report counts section: second wind uses.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='second_wind_uses', allowed_transforms=(frozenset({'none', 'cumulative', 'moving_average'})), supported_filters=_COMMON_FILTERS), MetricSeriesSpec(key='demon_mode_uses', label='Demon Mode Uses', description='Battle Report counts section: demon mode uses.', unit='count', category=(MetricCategory.utility), kind='observed', source_model='BattleReport', aggregation='sum', time_index='timestamp', value_field='demon_mode_uses', 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 upper-tier pairs.
- Supported suffixes include lowercase
k..q, uppercaseQ, lowercases/o, and uppercaseS/O.
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. |