first
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# Analytics Taxonomy
|
||||
|
||||
## Rules
|
||||
|
||||
- All analytics events are structured
|
||||
- No JSONB payloads
|
||||
- Events live in `analytics_events`
|
||||
- Attributes live in `analytics_event_attributes`
|
||||
|
||||
## Event Groups
|
||||
|
||||
### Lifecycle
|
||||
|
||||
- `app_opened`
|
||||
- `app_backgrounded`
|
||||
- `app_closed`
|
||||
- `session_started`
|
||||
- `session_ended`
|
||||
|
||||
### Onboarding
|
||||
|
||||
- `language_selected`
|
||||
- `consent_viewed`
|
||||
- `consent_accepted`
|
||||
|
||||
### Feed and Round Load
|
||||
|
||||
- `feed_viewed`
|
||||
- `round_card_viewed`
|
||||
- `round_loaded`
|
||||
- `event_manifest_received`
|
||||
- `preview_prefetch_started`
|
||||
- `preview_prefetch_completed`
|
||||
|
||||
### Playback
|
||||
|
||||
- `preview_started`
|
||||
- `preview_paused`
|
||||
- `preview_resumed`
|
||||
- `preview_completed`
|
||||
- `reveal_started`
|
||||
- `reveal_completed`
|
||||
- `playback_error`
|
||||
- `stream_reconnected`
|
||||
|
||||
### Timing and Odds
|
||||
|
||||
- `countdown_visible`
|
||||
- `countdown_warning_threshold_hit`
|
||||
- `odds_panel_viewed`
|
||||
- `odds_version_received`
|
||||
- `odds_changed`
|
||||
|
||||
### Selection and Settlement
|
||||
|
||||
- `outcome_focused`
|
||||
- `outcome_selected`
|
||||
- `selection_submitted`
|
||||
- `selection_accepted`
|
||||
- `selection_rejected`
|
||||
- `duplicate_selection_attempt`
|
||||
- `market_locked`
|
||||
- `result_viewed`
|
||||
- `next_round_requested`
|
||||
|
||||
### UI and Input
|
||||
|
||||
- `screen_viewed`
|
||||
- `cta_pressed`
|
||||
- `gesture_swipe`
|
||||
- `gesture_tap`
|
||||
- `gesture_cancelled`
|
||||
- `haptic_triggered`
|
||||
|
||||
### Localization and Errors
|
||||
|
||||
- `localization_bundle_loaded`
|
||||
- `locale_changed`
|
||||
- `network_error`
|
||||
|
||||
## Common Attributes
|
||||
|
||||
- `screen_name`
|
||||
- `event_id`
|
||||
- `market_id`
|
||||
- `outcome_id`
|
||||
- `odds_version_id`
|
||||
- `countdown_ms_remaining`
|
||||
- `locale_code`
|
||||
- `experiment_variant`
|
||||
- `network_type`
|
||||
- `device_orientation`
|
||||
- `playback_position_ms`
|
||||
- `latency_ms`
|
||||
|
||||
## Research Metrics
|
||||
|
||||
- Decision latency
|
||||
- Round completion rate
|
||||
- Missed rounds
|
||||
- Post-lock selection attempts
|
||||
- Replay desire
|
||||
- Exit rate
|
||||
- Control vs modern comparison
|
||||
- Locale effects
|
||||
- Device platform effects
|
||||
@@ -0,0 +1,50 @@
|
||||
# Architecture
|
||||
|
||||
## System Shape
|
||||
|
||||
Hermes uses three layers:
|
||||
|
||||
- Native client apps for iOS and Android
|
||||
- A Rust backend API that owns timing and state
|
||||
- PostgreSQL plus Valkey for durable and short-lived data
|
||||
|
||||
## Client Responsibilities
|
||||
|
||||
- Render the study experience
|
||||
- Play preview and reveal video
|
||||
- Handle gestures and haptics
|
||||
- Show odds and lock timing
|
||||
- Keep local session state
|
||||
- Prefetch media
|
||||
- Sync clocks with the server
|
||||
- Capture structured analytics
|
||||
- Load English and Swedish strings from localization assets
|
||||
|
||||
## Backend Responsibilities
|
||||
|
||||
- Auth and session binding
|
||||
- Event feed and manifests
|
||||
- Markets and odds distribution
|
||||
- Bet intent validation and acceptance
|
||||
- Settlement and audit logging
|
||||
- Experiment assignment
|
||||
- Localization bundle serving
|
||||
- Analytics ingestion
|
||||
- Admin fixture publishing
|
||||
|
||||
## Core Rules
|
||||
|
||||
- The server decides lock time and settlement
|
||||
- Clients send intent, not fairness decisions
|
||||
- Video playback must not block overlay updates
|
||||
- Client and server state machines must match
|
||||
- Localization is mandatory for every user-facing string
|
||||
- Analytics and audit data stay relational
|
||||
|
||||
## Repo Order
|
||||
|
||||
1. Documents and contracts
|
||||
2. Backend foundation
|
||||
3. Domain modules and API coverage
|
||||
4. Fixture and admin workflows
|
||||
5. Native iOS and Android apps
|
||||
@@ -0,0 +1,52 @@
|
||||
# Localization Catalog
|
||||
|
||||
## Rules
|
||||
|
||||
- Every user-facing string must be localizable
|
||||
- Stable key names only
|
||||
- English is the fallback locale
|
||||
- Swedish must be present at launch
|
||||
- Keys are mirrored in `contracts/localization/en.json` and `contracts/localization/sv.json`
|
||||
|
||||
## Current Keys
|
||||
|
||||
| Key | Purpose |
|
||||
| --- | --- |
|
||||
| `app.name` | App name |
|
||||
| `common.continue` | Continue action |
|
||||
| `common.cancel` | Cancel action |
|
||||
| `common.close` | Close action |
|
||||
| `common.retry` | Retry action |
|
||||
| `common.loading` | Loading state |
|
||||
| `common.error_title` | Error title |
|
||||
| `common.ok` | OK action |
|
||||
| `onboarding.title` | Study intro title |
|
||||
| `onboarding.subtitle` | Study intro body |
|
||||
| `onboarding.consent_title` | Consent title |
|
||||
| `onboarding.consent_body` | Consent body |
|
||||
| `onboarding.language_title` | Language picker title |
|
||||
| `onboarding.start_session` | Start session CTA |
|
||||
| `feed.next_round_title` | Next round title |
|
||||
| `feed.next_round_body` | Next round body |
|
||||
| `feed.watch_preview` | Watch preview CTA |
|
||||
| `feed.round_ready` | Round ready label |
|
||||
| `round.countdown_label` | Countdown label |
|
||||
| `round.locked_label` | Locked label |
|
||||
| `round.selection_prompt` | Selection prompt |
|
||||
| `round.selection_confirmed` | Selection accepted |
|
||||
| `round.selection_submitting` | Selection submitting |
|
||||
| `round.odds_label` | Odds label |
|
||||
| `reveal.title` | Reveal title |
|
||||
| `reveal.subtitle` | Reveal subtitle |
|
||||
| `result.title` | Result title |
|
||||
| `result.user_selection` | User selection label |
|
||||
| `result.outcome` | Outcome label |
|
||||
| `result.next_round` | Next round CTA |
|
||||
| `settings.title` | Settings title |
|
||||
| `settings.language` | Language setting |
|
||||
| `settings.haptics` | Haptics setting |
|
||||
| `settings.analytics` | Analytics setting |
|
||||
| `errors.generic` | Generic error copy |
|
||||
| `errors.network` | Network error copy |
|
||||
| `errors.playback` | Playback error copy |
|
||||
| `errors.session_expired` | Session expired copy |
|
||||
@@ -0,0 +1,192 @@
|
||||
# Relational Schema
|
||||
|
||||
## Rules
|
||||
|
||||
- UUID primary keys
|
||||
- `timestamptz` for all timestamps
|
||||
- No JSONB columns
|
||||
- Child tables for attributes and event payloads
|
||||
- Foreign keys wherever possible
|
||||
|
||||
## Core Tables
|
||||
|
||||
### users
|
||||
|
||||
- `id`
|
||||
- `external_ref`
|
||||
- `created_at`
|
||||
- `preferred_language`
|
||||
- `device_platform`
|
||||
|
||||
### sessions
|
||||
|
||||
- `id`
|
||||
- `user_id`
|
||||
- `started_at`
|
||||
- `ended_at`
|
||||
- `experiment_variant`
|
||||
- `app_version`
|
||||
- `device_model`
|
||||
- `os_version`
|
||||
- `locale_code`
|
||||
|
||||
### events
|
||||
|
||||
- `id`
|
||||
- `sport_type`
|
||||
- `source_ref`
|
||||
- `title_en`
|
||||
- `title_sv`
|
||||
- `status`
|
||||
- `preview_start_ms`
|
||||
- `preview_end_ms`
|
||||
- `reveal_start_ms`
|
||||
- `reveal_end_ms`
|
||||
- `lock_at`
|
||||
- `settle_at`
|
||||
|
||||
### event_media
|
||||
|
||||
- `id`
|
||||
- `event_id`
|
||||
- `media_type`
|
||||
- `hls_master_url`
|
||||
- `poster_url`
|
||||
- `duration_ms`
|
||||
- `preview_start_ms`
|
||||
- `preview_end_ms`
|
||||
- `reveal_start_ms`
|
||||
- `reveal_end_ms`
|
||||
|
||||
### markets
|
||||
|
||||
- `id`
|
||||
- `event_id`
|
||||
- `question_key`
|
||||
- `market_type`
|
||||
- `status`
|
||||
- `lock_at`
|
||||
- `settlement_rule_key`
|
||||
|
||||
### outcomes
|
||||
|
||||
- `id`
|
||||
- `market_id`
|
||||
- `outcome_code`
|
||||
- `label_key`
|
||||
- `sort_order`
|
||||
|
||||
### odds_versions
|
||||
|
||||
- `id`
|
||||
- `market_id`
|
||||
- `version_no`
|
||||
- `created_at`
|
||||
- `is_current`
|
||||
|
||||
### outcome_odds
|
||||
|
||||
- `id`
|
||||
- `odds_version_id`
|
||||
- `outcome_id`
|
||||
- `decimal_odds`
|
||||
- `fractional_num`
|
||||
- `fractional_den`
|
||||
|
||||
### bet_intents
|
||||
|
||||
- `id`
|
||||
- `user_id`
|
||||
- `session_id`
|
||||
- `event_id`
|
||||
- `market_id`
|
||||
- `outcome_id`
|
||||
- `idempotency_key`
|
||||
- `client_sent_at`
|
||||
- `server_received_at`
|
||||
- `accepted`
|
||||
- `acceptance_code`
|
||||
- `accepted_odds_version_id`
|
||||
|
||||
### settlements
|
||||
|
||||
- `id`
|
||||
- `market_id`
|
||||
- `settled_at`
|
||||
- `winning_outcome_id`
|
||||
|
||||
### experiment_assignments
|
||||
|
||||
- `id`
|
||||
- `user_id`
|
||||
- `session_id`
|
||||
- `variant`
|
||||
- `assigned_at`
|
||||
|
||||
### localization_keys
|
||||
|
||||
- `id`
|
||||
- `key_name`
|
||||
- `description`
|
||||
|
||||
### localization_values
|
||||
|
||||
- `id`
|
||||
- `localization_key_id`
|
||||
- `locale_code`
|
||||
- `text_value`
|
||||
|
||||
### analytics_event_types
|
||||
|
||||
- `id`
|
||||
- `event_name`
|
||||
- `description`
|
||||
|
||||
### analytics_events
|
||||
|
||||
- `id`
|
||||
- `analytics_event_type_id`
|
||||
- `session_id`
|
||||
- `user_id`
|
||||
- `occurred_at`
|
||||
|
||||
### analytics_event_attributes
|
||||
|
||||
- `id`
|
||||
- `analytics_event_id`
|
||||
- `attribute_key`
|
||||
- `attribute_value`
|
||||
|
||||
### audit_logs
|
||||
|
||||
- `id`
|
||||
- `created_at`
|
||||
- `actor_type`
|
||||
- `actor_id`
|
||||
- `action_name`
|
||||
- `target_type`
|
||||
- `target_id`
|
||||
- `trace_id`
|
||||
- `note`
|
||||
|
||||
### audit_log_attributes
|
||||
|
||||
- `id`
|
||||
- `audit_log_id`
|
||||
- `attribute_key`
|
||||
- `attribute_value`
|
||||
|
||||
## Indexes
|
||||
|
||||
- `sessions.user_id`
|
||||
- `sessions.started_at`
|
||||
- `events.status`
|
||||
- `markets.event_id`
|
||||
- `markets.lock_at`
|
||||
- `bet_intents.session_id`
|
||||
- `bet_intents.market_id`
|
||||
- `bet_intents.idempotency_key`
|
||||
- `analytics_events.session_id`
|
||||
- `analytics_events.user_id`
|
||||
- `analytics_events.analytics_event_type_id`
|
||||
- `audit_logs.created_at`
|
||||
@@ -0,0 +1,31 @@
|
||||
# State Machines
|
||||
|
||||
## Event Lifecycle
|
||||
|
||||
`scheduled -> prefetch_ready -> preview_open -> locking -> locked -> reveal_open -> settled -> archived`
|
||||
|
||||
## Client Round State
|
||||
|
||||
`idle -> prefetching -> ready -> preview_playing -> selection_pending -> selection_submitting -> selection_accepted -> locked -> reveal_playing -> result_visible -> transitioning -> error`
|
||||
|
||||
## Bet Acceptance State
|
||||
|
||||
`received -> validated -> accepted`
|
||||
|
||||
Rejection states:
|
||||
|
||||
- `rejected_too_late`
|
||||
- `rejected_invalid_market`
|
||||
- `rejected_invalid_session`
|
||||
- `rejected_duplicate`
|
||||
|
||||
Terminal state:
|
||||
|
||||
- `settled`
|
||||
|
||||
## Notes
|
||||
|
||||
- Locking is server authoritative
|
||||
- Clients may display synced countdowns, but acceptance is decided by the server
|
||||
- Selection confirmation must be visually unambiguous
|
||||
- The locked state freezes the odds display for the user
|
||||
Reference in New Issue
Block a user