|
|
|
@ -14,7 +14,8 @@ This project is a Ruby on Rails application developed for the IKEA Foundation. I |
|
|
|
- **Last save / Results:** Final compost-vs-landfill choice, then the scored results screen. |
|
|
|
- **Scoring:** Answer scores live in `config/question_scores.json`, not in the database. See below. |
|
|
|
- **Analytics:** An admin dashboard at `/admin/:locale/analytics` reporting funnel drop-off, |
|
|
|
answer distribution and the results-screen thumbs vote. See below. |
|
|
|
answer distribution, the results-screen thumbs vote and who played — device, language and |
|
|
|
country. See below. |
|
|
|
- **Admin Interface:** A backend for managing nodes, assets (Active Storage), users, and translations. |
|
|
|
- **Search:** `pg_search` integration for content discovery. |
|
|
|
|
|
|
|
@ -35,8 +36,8 @@ This project is a Ruby on Rails application developed for the IKEA Foundation. I |
|
|
|
- **Level 3 (outcomes under a chance):** `good_answer`, `bad_answer` |
|
|
|
- `Player`: Tracks session state, `progress` (per-stage `answer_id` / `result_id`), the |
|
|
|
cumulative `score`, the `scores` hash keyed by `food_waste`, `emissions`, `income`, |
|
|
|
the furthest screen reached (`furthest_step`) and the results-screen thumbs vote |
|
|
|
(`rating` / `rated_at`). |
|
|
|
the furthest screen reached (`furthest_step`), the `device` class they played on and |
|
|
|
the results-screen thumbs vote (`rating` / `rated_at`). |
|
|
|
- `Asset` & `Attachment`: Handles media and its contextual content (body text, styling) associated with nodes. |
|
|
|
- `User`: Admin authentication and roles. |
|
|
|
|
|
|
|
@ -84,12 +85,14 @@ and check the three strings actually differ, not just that the key exists. |
|
|
|
|
|
|
|
## Analytics |
|
|
|
|
|
|
|
There is no event log. `GameAnalytics` derives everything from two columns on `players`: |
|
|
|
There is no event log. `GameAnalytics` derives everything from columns on `players`: |
|
|
|
|
|
|
|
- `progress` — what people *did* (the answer node they landed on for each stage). |
|
|
|
- `furthest_step` — where people *stopped*. Written by `GameController#track_step`, an |
|
|
|
`after_action` on the screen actions. `Player#record_step` only ever moves a player |
|
|
|
forward, so the browser back button can't rewind the funnel. |
|
|
|
- `device` — what they played *on*: `mobile`, `tablet` or `desktop`. |
|
|
|
- `locale` and `country` — who they are, feeding the two breakdown panels beside devices. |
|
|
|
|
|
|
|
Step names are `facts`, `intro`, `stage_<n>`, `stage_<n>_result`, `last_save`, `done`, |
|
|
|
`results`, ordered by `Player.step_rank`. |
|
|
|
@ -103,6 +106,22 @@ reported as its own panel rather than as a funnel step. |
|
|
|
gameplay to feed the leaderboard banner, which inflates every count and shows up as a huge |
|
|
|
drop before "Facts". The dashboard warns about this while `DemoActivity::ENABLED` is true. |
|
|
|
|
|
|
|
### Device |
|
|
|
|
|
|
|
`Player.device_from_user_agent` classifies the User-Agent once, when `GameController#start` |
|
|
|
creates the player; there is no client-side probe and nothing re-checks it later. The match |
|
|
|
order matters — tablets are tested first, because an Android tablet's UA also says "Android" |
|
|
|
and only phones add a `Mobi` token, so a mobile-first test would swallow every tablet. |
|
|
|
|
|
|
|
Crawlers and anything unrecognisable are stored as `nil` rather than falling through to |
|
|
|
`desktop`, and `GameAnalytics#by_device` drops `nil` rows — so the shares describe only the |
|
|
|
players we could actually place, and a bot run can't quietly pad the desktop column. |
|
|
|
|
|
|
|
**Known blind spot:** iPadOS 13+ sends a desktop Safari UA by default, so some iPads are |
|
|
|
counted as desktop. Nothing short of client-side probing fixes it; the tablet share is a |
|
|
|
floor, not an exact figure. Players created before this column existed are `nil` forever — |
|
|
|
the UA was never stored, so there is nothing to backfill from. |
|
|
|
|
|
|
|
### Thumbs up / down |
|
|
|
|
|
|
|
`Player#rate!` stores `rating` as `1` / `-1` (`Player::RATINGS`), one per player — voting |
|
|
|
|