Class: Sentiero::Analytics::EngagementAnalyzer
- Defined in:
- lib/sentiero/analytics/engagement_analyzer.rb
Overview
Scores each session 0–100 on a STRUGGLE score (higher = more friction): a weighted blend of eight signals, each saturating to 0.0..1.0 so one pathological session can't dominate. Signals: rage_clicks, dead_clicks, nav_churn, idle_ratio, thrashing_scroll, quick_bounce, form_refills, error_abandonment.
Constant Summary collapse
- WEIGHTS =
Signal weights; MUST sum to 1.00.
{ rage_clicks: 0.20, dead_clicks: 0.15, nav_churn: 0.15, idle_ratio: 0.10, thrashing_scroll: 0.10, quick_bounce: 0.10, form_refills: 0.10, error_abandonment: 0.10 }.freeze
- RAGE_SATURATION =
3+ rage clusters → full rage sub-score
3- DEAD_SATURATION =
3+ dead clicks → full dead sub-score
3- NAV_CHURN_SATURATION =
3+ revisits to already-seen URLs → full sub-score
3- THRASH_SATURATION =
3+ scroll reversals → full thrash sub-score
3- REFILL_SATURATION =
2+ field re-fills → full refill sub-score
2- IDLE_GAP_MS =
consecutive events farther apart than this are "idle"
10_000- THRASH_MIN_DELTA_PX =
a scroll delta below this is too small to be thrashing
100- THRASH_WINDOW_MS =
both deltas of a reversal must fall within this span
1_000- QUICK_BOUNCE_MS =
single-page sessions shorter than this bounced
5_000- ERROR_ABANDON_MS =
a JS error within this of the session end = abandonment
8_000- MAX_SESSIONS =
display cap on returned rows (does NOT set was_truncated)
500- DISTRIBUTION_BINS =
%w[0-20 20-40 40-60 60-80 80-100].freeze
- ERROR_TAG =
"error"- NAVIGATION_TAG =
"navigation"
Constants included from Events
Sentiero::Analytics::Events::CUSTOM, Sentiero::Analytics::Events::INCREMENTAL, Sentiero::Analytics::Events::META, Sentiero::Analytics::Events::SOURCE_INPUT, Sentiero::Analytics::Events::SOURCE_MOUSE_INTERACTION, Sentiero::Analytics::Events::SOURCE_SCROLL
Instance Attribute Summary
Attributes inherited from Analyzer
Class Method Summary collapse
-
.bin_for(score) ⇒ Object
Integer division pins boundaries: 19→"0-20", 20→"20-40", 100→"80-100".
Instance Method Summary collapse
-
#analyze(limit: nil, since: nil, until_time: nil) ⇒ Object
The MAX_SESSIONS row cap is a DISPLAY bound (keeps highest scores, does NOT set was_truncated); only the scan cap sets was_truncated.
Methods inherited from Analyzer
Methods included from EntryAttribution
#earlier?, #first_meta_href, #same_origin?, #update_entry_candidate
Methods included from Bounded
#bounded_fetch, #bounded_increment
Methods included from Stats
#mean, #offset_ms, #percentile, #top_counts
Constructor Details
This class inherits a constructor from Sentiero::Analytics::Analyzer
Class Method Details
.bin_for(score) ⇒ Object
Integer division pins boundaries: 19→"0-20", 20→"20-40", 100→"80-100".
43 44 45 |
# File 'lib/sentiero/analytics/engagement_analyzer.rb', line 43 def self.bin_for(score) DISTRIBUTION_BINS[[(score / 20), 4].min] end |
Instance Method Details
#analyze(limit: nil, since: nil, until_time: nil) ⇒ Object
The MAX_SESSIONS row cap is a DISPLAY bound (keeps highest scores, does NOT set was_truncated); only the scan cap sets was_truncated.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/sentiero/analytics/engagement_analyzer.rb', line 49 def analyze(limit: nil, since: nil, until_time: nil) accumulators = {} scanned, hit_cap = scan_sessions(limit: limit, since: since, until_time: until_time) do |summary, window_id, events| session_id = summary[:session_id] acc = (accumulators[session_id] ||= new_accumulator(summary, window_id)) accumulate_window(acc, events) end rows = accumulators.values.map { |acc| score_session(acc) } distribution = build_distribution(rows) rows.sort_by! { |row| [-row[:score], row[:session_id]] } { sessions: rows.first(MAX_SESSIONS), distribution: distribution, scanned: scanned, was_truncated: hit_cap } end |