Quick Start

This page scores a single BART session end to end.

1. Build an event log

The scoring engine consumes a flat, chronologically ordered list of GameEvent objects. Each event has a monotonic timestamp (milliseconds, e.g. from performance.now()), a type ("pump", "collect", or "explode"), and a payload carrying at least the balloon color.

A balloon is the run of pump events up to and including its terminal collect or explode event.

from scoring.schemas import GameEvent, EventPayload
from scoring.bart import score_bart

events = [
    # A purple balloon collected after 3 pumps
    GameEvent(timestamp=100, type="pump",    payload=EventPayload(color="purple")),
    GameEvent(timestamp=400, type="pump",    payload=EventPayload(color="purple")),
    GameEvent(timestamp=750, type="pump",    payload=EventPayload(color="purple")),
    GameEvent(timestamp=950, type="collect", payload=EventPayload(color="purple")),

    # An orange balloon that burst on the 3rd pump
    GameEvent(timestamp=1200, type="pump",    payload=EventPayload(color="orange")),
    GameEvent(timestamp=1500, type="pump",    payload=EventPayload(color="orange")),
    GameEvent(timestamp=1800, type="pump",    payload=EventPayload(color="orange")),
    GameEvent(timestamp=1850, type="explode", payload=EventPayload(color="orange")),
    # ... more balloons
]

2. Score it

metrics = score_bart(events)

print(f"EV ratio score:    {metrics.ev_ratio_score:.1f} / 100")
print(f"Adaptive strategy: {metrics.adaptive_strategy_score:.1f} / 100")
print(f"Money collected:   ${metrics.money_collected:.2f}")
print(f"RNG-norm pumps:    {metrics.rng_normalized_pumps:.2f}")
print(f"Risk style:        {metrics.behavioral_profile['risk_style']}")

score_bart() returns a BARTMetrics object — a pydantic model whose fields are the full metric set described in the Metrics Reference. Convert it to a dictionary or JSON with the usual pydantic methods (metrics.model_dump() / metrics.model_dump_json()).

A full 30-balloon session

A complete, valid session has 30 balloons (10 purple, 10 teal, 10 orange). With fewer than 15 balloons the session is marked invalid; see Validation for the full set of checks. The two-balloon example above will score, but its session_valid flag will be False and its session_warnings list will explain why.

3. Validate before scoring (optional)

score_bart() always runs validation internally and records the outcome on metrics.session_valid / metrics.session_warnings. If you want to inspect validity before scoring — for example to reject a session at intake — call validate_bart_session() directly:

from scoring.bart import validate_bart_session

report = validate_bart_session(events)
if not report["is_valid"]:
    print("Rejected:", report["warnings"])

4. Reading the result

A handful of fields carry most of the interpretive weight:

  • ev_ratio_score (0–100) — how close the participant came to expected-value-optimal play, weighted across the three hazard levels. This is the engine’s primary calibration measure.

  • explosion_penalty (0–1) — how much the participant over-pumped, measured as excess burst rate beyond what optimal play would produce. Reported separately from calibration to avoid double-penalizing.

  • rng_normalized_pumps (≥0) — mean stop as a fraction of the EV-optimal stop; 1.0 is optimal, <1 conservative, >1 over-pumping.

  • behavioral_profile — a narrative classification (risk_style, description, dominant_traits); see Behavioral profiles.

See The scoring engine for how each number is computed.