The Scoring Engine¶
The scoring engine (scoring/bart.py) turns a raw event log into a
BARTMetrics object. This page explains how the
headline metrics are computed; for the exhaustive field-by-field list see the
Metrics Reference.
Pipeline overview¶
score_bart() runs the following stages:
Validate the session (
validate_bart_session()) and recordsession_valid/session_warnings. See Validation.Segment the flat event list into per-balloon event groups (
_segment_balloons()). A balloon ends at itscollectorexplodeevent.Flag auto-repeat balloons (
_is_autorepeat_balloon()) — runs of pumps at OS key-repeat speed (median inter-pump interval < 80 ms) are excluded from behavioral-intention metrics.Aggregate per-color pump counts, explosions, collections, latencies, and money.
Compute the calibration, learning, consistency, and composite metrics.
Classify the session into a narrative behavioral profile (
_generate_behavioral_profile()).
The censoring correction (collected-only metrics)¶
The single most important methodological choice in the engine is that all behavioral-intention metrics are computed from collected (non-exploded) balloons only.
On an exploded balloon the recorded pump count is the point at which the random
number generator ended the trial — not the participant’s intended stopping
point. Including these right-censored values would bias intention metrics
sharply downward. The engine therefore prefers collected balloons and only falls
back to all balloons when a color has fewer than
MIN_COLLECTED_FALLBACK (2) collected trials, recording
an RNG fallback warning when it does
(_prefer_collected()).
Two fields deliberately keep the uncensored view for contrast:
avg_pumps_all_balloons (mean over every balloon) and explosion_rate (gross
burst fraction).
Calibration metrics (the sequential EV core)¶
EV ratio score¶
ev_ratio_score (_compute_ev_ratio_score()) is the
engine’s primary calibration measure. For each color it computes the
participant’s expected value at their mean collected stop (linearly interpolated
between the two bracketing integer pumps), divides by the optimal
\(\mathrm{EV}(s^*)\), and clamps to 1.0:
The overall score weights each color by its reward potential \(\mathrm{EV}(s^*_c)\):
Because \(\mathrm{EV}(s^*)\) is 6.46 / 3.04 / 1.31 for purple / teal / orange, the
weights are approximately 60% / 28% / 12%. A value of 100 means perfectly
EV-optimal play across all hazard levels. risk_calibration_score is the same
quantity, exposed under a second name; the explosion penalty is kept separate so
calibration and over-pumping are not conflated.
Explosion penalty¶
explosion_penalty (_compute_explosion_penalty()) is the
mean across colors of the excess burst rate beyond what EV-optimal play would
produce:
The expected-at-optimal rates are about 0.41 / 0.39 / 0.34. A penalty of 0 means no excess explosions; higher values flag over-pumping.
RNG-normalized pumps¶
rng_normalized_pumps expresses each color’s mean collected stop as a fraction
of its EV-optimal stop, averaged across colors:
1.0 is exactly optimal; below 1 is conservative, above 1 is over-pumping.
EV-efficiency uniformity¶
ev_efficiency_uniformity (_compute_ev_efficiency_uniformity())
is 1 - CV of the per-color EV efficiencies — a measure of how evenly a
participant performs across hazard levels (high = consistent across colors, not
necessarily high-scoring). A color with too few collected balloons contributes a
zero efficiency. Returns None if fewer than two colors have usable data.
Money efficiency¶
money_collected is simply \(0.25 \times\) banked pumps. money_efficiency
divides it by the simulated median earnings under optimal play (27.25),
clamped to [0, 2]. The median — rather than the analytic mean EV — is used
because roughly half of optimally played sessions earn below the mean EV, so the
median is the fairer reference point.
Learning and adaptation¶
Because colors at different risk levels reward different responses, the directional meaning of a learning slope depends on a color’s risk role. The engine ranks the study’s colors by EV-optimal stop (issue 56): the highest-risk color rewards fewer pumps over time, the lowest-risk color rewards more, and the mid-risk colors are excluded because their direction is ambiguous. This resolves by risk role rather than by literal color name, so a renamed or re-ordered study is scored the same. The engine offers three complementary learning estimators, all computed on collected balloons:
learning_rate(_calculate_learning_rate()) — per-color linear regression of pumps on trial number, each slope weighted by its \(R^2\) to suppress noise, sign-adjusted by risk role, then averaged.half_split_learning_rate— first-half vs. second-half mean pumps per color (more robust than regression at ~10 trials per color, since no single outlier dominates).tercile_learning_rate— first-third vs. last-third, dropping the noisy middle third to sharpen detection of late learners.
Two further adaptation metrics capture within-session dynamics:
color_discrimination_trajectory— the change in safest-minus-riskiest pump separation from the first to the last third of the session, normalized by the EV-optimal spread between those two colors (~9 pumps for the default study).post_explosion_sensitivity— the mean pump reduction on the next same-color balloon following a burst, normalized by that color’s \(s^*\). Positive values indicate adaptive risk reduction.
Consistency and timing¶
_calculate_consistency_breakdown() decomposes response
consistency into:
within_balloon_consistency— mean coefficient of variation (CV) of inter-pump latencies inside a single balloon (immune to between-balloon strategy shifts); andbetween_balloon_consistency— CV of pump counts across balloons (high = erratic strategy).
impulsivity_index is a latency index, \(1 - \mathrm{clip}(\text{mean latency} /
800\text{ ms}, 0, 1)\) — higher means faster, more reflexive pumping. This follows
Lejuez et al. (2002), who identify pump latency as the primary BART correlate of
trait impulsivity.
The composite score¶
adaptive_strategy_score (0–100) is a fixed-weight blend:
Component |
Source |
Weight |
|---|---|---|
Calibration |
|
0.35 |
Learning |
|
0.25 |
Uniformity |
|
0.25 |
Money |
|
0.15 |
The learning term is rescaled from its \([-1, 1]\) range onto \([0, 1]\) before weighting.
Flat-strategy detection¶
flat_strategy_detected (_detect_flat_strategy()) flags
participants who pump nearly identically across colors — forgoing
reward on safe balloons and over-exploding on risky ones. The detector exempts
participants who show genuine adaptation (positive tercile learning, color
discrimination growth, post-explosion sensitivity, or high between-balloon
variability) so that active explorers are not misclassified as flat.
Behavioral profiles¶
Finally, _generate_behavioral_profile() assigns a
narrative risk_style from the computed metrics, evaluated in priority order
(first match wins):
Risk style |
Triggered when… |
|---|---|
Undifferentiated Risk Approach |
|
Calibrated Risk Optimizer |
|
Selective Over-Optimizer |
Strong on the safest color (eff ≥ 0.70) but weak on the riskiest (eff < 0.30); low uniformity; penalty > 0.25. |
Persistent Risk Taker |
|
Context-Insensitive Risk Taker |
Uniformity < 0.35, not selectively strong, penalty > 0.15. |
Loss-Averse Responder |
|
Emerging Optimizer |
Selective strength, |
Adaptive Risk Learner |
Strong learning and discrimination growth across the session. |
Conservative Strategist |
|
Balanced Explorer |
Catch-all when no other style matches. |
The profile also includes a plain-language description and a list of
dominant_traits (e.g. Highly Consistent, Improving Over Time, Impulsive on
High-Risk, Near-Optimal on Safe Balloons).