This commit is contained in:
2026-04-09 14:55:37 +02:00
parent 8be455ba98
commit 4a85efc270
30 changed files with 4895 additions and 1 deletions
+40
View File
@@ -0,0 +1,40 @@
{
"app.name": "Hermes",
"common.continue": "Continue",
"common.cancel": "Cancel",
"common.close": "Close",
"common.retry": "Retry",
"common.loading": "Loading",
"common.error_title": "Something went wrong",
"common.ok": "OK",
"onboarding.title": "Study intro",
"onboarding.subtitle": "Watch the clip, make your choice before lock, then see the reveal.",
"onboarding.consent_title": "Consent",
"onboarding.consent_body": "This prototype is for research and does not use real money.",
"onboarding.language_title": "Choose language",
"onboarding.start_session": "Start session",
"feed.next_round_title": "Next round",
"feed.next_round_body": "A new clip is ready for review.",
"feed.watch_preview": "Watch preview",
"feed.round_ready": "Round ready",
"round.countdown_label": "Lock in",
"round.locked_label": "Locked",
"round.selection_prompt": "Choose an outcome before lock.",
"round.selection_confirmed": "Selection accepted",
"round.selection_submitting": "Submitting selection",
"round.odds_label": "Odds",
"reveal.title": "Reveal",
"reveal.subtitle": "See what happened next.",
"result.title": "Result",
"result.user_selection": "Your selection",
"result.outcome": "Outcome",
"result.next_round": "Next round",
"settings.title": "Settings",
"settings.language": "Language",
"settings.haptics": "Haptics",
"settings.analytics": "Analytics",
"errors.generic": "Please try again.",
"errors.network": "Network error. Check your connection.",
"errors.playback": "Video playback failed.",
"errors.session_expired": "Session expired. Please start again."
}
+40
View File
@@ -0,0 +1,40 @@
{
"app.name": "Hermes",
"common.continue": "Fortsätt",
"common.cancel": "Avbryt",
"common.close": "Stäng",
"common.retry": "Försök igen",
"common.loading": "Laddar",
"common.error_title": "Något gick fel",
"common.ok": "OK",
"onboarding.title": "Studieintro",
"onboarding.subtitle": "Titta på klippet, gör ditt val före låsning och se sedan avslöjandet.",
"onboarding.consent_title": "Samtycke",
"onboarding.consent_body": "Denna prototyp är för forskning och använder inte riktiga pengar.",
"onboarding.language_title": "Välj språk",
"onboarding.start_session": "Starta session",
"feed.next_round_title": "Nästa runda",
"feed.next_round_body": "Ett nytt klipp är klart för granskning.",
"feed.watch_preview": "Titta på förhandsklipp",
"feed.round_ready": "Rundan är klar",
"round.countdown_label": "Låsning om",
"round.locked_label": "Låst",
"round.selection_prompt": "Välj ett utfall före låsning.",
"round.selection_confirmed": "Valet accepterat",
"round.selection_submitting": "Skickar val",
"round.odds_label": "Odds",
"reveal.title": "Avslöjande",
"reveal.subtitle": "Se vad som hände sedan.",
"result.title": "Resultat",
"result.user_selection": "Ditt val",
"result.outcome": "Utfall",
"result.next_round": "Nästa runda",
"settings.title": "Inställningar",
"settings.language": "Språk",
"settings.haptics": "Haptik",
"settings.analytics": "Analys",
"errors.generic": "Försök igen.",
"errors.network": "Nätverksfel. Kontrollera anslutningen.",
"errors.playback": "Videouppspelningen misslyckades.",
"errors.session_expired": "Sessionen har gått ut. Starta igen."
}
+584
View File
@@ -0,0 +1,584 @@
openapi: 3.1.0
info:
title: Hermes API
version: 0.1.0
description: Native betting study prototype API.
servers:
- url: http://localhost:3000
paths:
/health:
get:
summary: Health check
responses:
'200':
description: Service health
content:
application/json:
schema:
$ref: '#/components/schemas/HealthResponse'
/api/v1/session/start:
post:
summary: Start a session
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SessionStartRequest'
responses:
'201':
description: Session started
content:
application/json:
schema:
$ref: '#/components/schemas/SessionResponse'
/api/v1/session/end:
post:
summary: End the current session
responses:
'200':
description: Session ended
content:
application/json:
schema:
$ref: '#/components/schemas/SessionResponse'
/api/v1/session/me:
get:
summary: Inspect the current session
responses:
'200':
description: Current session snapshot
content:
application/json:
schema:
$ref: '#/components/schemas/SessionResponse'
/api/v1/feed/next:
get:
summary: Fetch the next round
responses:
'200':
description: Next event payload
content:
application/json:
schema:
$ref: '#/components/schemas/Event'
/api/v1/events/{event_id}:
get:
summary: Fetch an event
parameters:
- name: event_id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Event
content:
application/json:
schema:
$ref: '#/components/schemas/Event'
/api/v1/events/{event_id}/manifest:
get:
summary: Fetch the event manifest
parameters:
- name: event_id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Manifest
content:
application/json:
schema:
$ref: '#/components/schemas/EventManifest'
/api/v1/events/{event_id}/markets:
get:
summary: List markets for an event
parameters:
- name: event_id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Markets
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Market'
/api/v1/markets/{market_id}/odds/current:
get:
summary: Fetch current odds for a market
parameters:
- name: market_id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Current odds
content:
application/json:
schema:
$ref: '#/components/schemas/OddsVersion'
/api/v1/stream:
get:
summary: Live odds and state stream
responses:
'200':
description: Server-sent events stream
content:
text/event-stream:
schema:
type: string
/api/v1/bets/intent:
post:
summary: Submit a bet intent
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BetIntentRequest'
responses:
'201':
description: Bet intent result
content:
application/json:
schema:
$ref: '#/components/schemas/BetIntentResponse'
/api/v1/bets/{bet_intent_id}:
get:
summary: Fetch a bet intent
parameters:
- name: bet_intent_id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Bet intent
content:
application/json:
schema:
$ref: '#/components/schemas/BetIntentResponse'
/api/v1/events/{event_id}/result:
get:
summary: Fetch event result
parameters:
- name: event_id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Result payload
content:
application/json:
schema:
$ref: '#/components/schemas/Settlement'
/api/v1/analytics/batch:
post:
summary: Ingest analytics batch
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AnalyticsBatchRequest'
responses:
'202':
description: Accepted
/api/v1/experiments/config:
get:
summary: Fetch experiment config
responses:
'200':
description: Experiment config
content:
application/json:
schema:
$ref: '#/components/schemas/ExperimentConfig'
/api/v1/localization/{locale_code}:
get:
summary: Fetch a localization bundle
parameters:
- name: locale_code
in: path
required: true
schema:
type: string
enum: [en, sv]
responses:
'200':
description: Localization bundle
content:
application/json:
schema:
$ref: '#/components/schemas/LocalizationBundle'
/api/v1/admin/events:
post:
summary: Create an event
responses:
'201':
description: Created
/api/v1/admin/markets:
post:
summary: Create a market
responses:
'201':
description: Created
/api/v1/admin/odds:
post:
summary: Publish odds
responses:
'201':
description: Created
/api/v1/admin/settlements:
post:
summary: Publish a settlement
responses:
'201':
description: Created
components:
schemas:
HealthResponse:
type: object
required: [status, service_name, environment, version, uptime_ms, database_ready, redis_ready]
properties:
status:
type: string
service_name:
type: string
environment:
type: string
version:
type: string
uptime_ms:
type: integer
database_ready:
type: boolean
redis_ready:
type: boolean
SessionStartRequest:
type: object
properties:
locale_code:
type: string
device_platform:
type: string
device_model:
type: string
os_version:
type: string
app_version:
type: string
SessionResponse:
type: object
required: [session_id, user_id, started_at, experiment_variant, app_version, locale_code, device_platform]
properties:
session_id:
type: string
format: uuid
user_id:
type: string
format: uuid
started_at:
type: string
format: date-time
ended_at:
type: string
format: date-time
nullable: true
experiment_variant:
type: string
app_version:
type: string
device_model:
type: string
nullable: true
os_version:
type: string
nullable: true
locale_code:
type: string
device_platform:
type: string
Event:
type: object
required: [id, sport_type, source_ref, title_en, title_sv, status, lock_at, settle_at]
properties:
id:
type: string
format: uuid
sport_type:
type: string
source_ref:
type: string
title_en:
type: string
title_sv:
type: string
status:
type: string
preview_start_ms:
type: integer
preview_end_ms:
type: integer
reveal_start_ms:
type: integer
reveal_end_ms:
type: integer
lock_at:
type: string
format: date-time
settle_at:
type: string
format: date-time
EventManifest:
type: object
properties:
event:
$ref: '#/components/schemas/Event'
media:
type: array
items:
$ref: '#/components/schemas/EventMedia'
markets:
type: array
items:
$ref: '#/components/schemas/Market'
EventMedia:
type: object
required: [id, event_id, media_type, hls_master_url, duration_ms]
properties:
id:
type: string
format: uuid
event_id:
type: string
format: uuid
media_type:
type: string
hls_master_url:
type: string
poster_url:
type: string
nullable: true
duration_ms:
type: integer
preview_start_ms:
type: integer
preview_end_ms:
type: integer
reveal_start_ms:
type: integer
reveal_end_ms:
type: integer
Market:
type: object
required: [id, event_id, question_key, market_type, status, lock_at, settlement_rule_key]
properties:
id:
type: string
format: uuid
event_id:
type: string
format: uuid
question_key:
type: string
market_type:
type: string
status:
type: string
lock_at:
type: string
format: date-time
settlement_rule_key:
type: string
outcomes:
type: array
items:
$ref: '#/components/schemas/Outcome'
Outcome:
type: object
required: [id, market_id, outcome_code, label_key, sort_order]
properties:
id:
type: string
format: uuid
market_id:
type: string
format: uuid
outcome_code:
type: string
label_key:
type: string
sort_order:
type: integer
OddsVersion:
type: object
required: [id, market_id, version_no, created_at, is_current]
properties:
id:
type: string
format: uuid
market_id:
type: string
format: uuid
version_no:
type: integer
created_at:
type: string
format: date-time
is_current:
type: boolean
odds:
type: array
items:
$ref: '#/components/schemas/OutcomeOdds'
OutcomeOdds:
type: object
required: [id, odds_version_id, outcome_id, decimal_odds, fractional_num, fractional_den]
properties:
id:
type: string
format: uuid
odds_version_id:
type: string
format: uuid
outcome_id:
type: string
format: uuid
decimal_odds:
type: number
fractional_num:
type: integer
fractional_den:
type: integer
BetIntentRequest:
type: object
required: [session_id, event_id, market_id, outcome_id, idempotency_key, client_sent_at]
properties:
session_id:
type: string
format: uuid
event_id:
type: string
format: uuid
market_id:
type: string
format: uuid
outcome_id:
type: string
format: uuid
idempotency_key:
type: string
client_sent_at:
type: string
format: date-time
BetIntentResponse:
type: object
required: [id, accepted, acceptance_code, server_received_at]
properties:
id:
type: string
format: uuid
accepted:
type: boolean
acceptance_code:
type: string
accepted_odds_version_id:
type: string
format: uuid
nullable: true
server_received_at:
type: string
format: date-time
Settlement:
type: object
required: [id, market_id, settled_at, winning_outcome_id]
properties:
id:
type: string
format: uuid
market_id:
type: string
format: uuid
settled_at:
type: string
format: date-time
winning_outcome_id:
type: string
format: uuid
AnalyticsBatchRequest:
type: object
required: [events]
properties:
events:
type: array
items:
$ref: '#/components/schemas/AnalyticsEventInput'
AnalyticsEventInput:
type: object
required: [event_name, occurred_at]
properties:
event_name:
type: string
occurred_at:
type: string
format: date-time
attributes:
type: array
items:
$ref: '#/components/schemas/AttributeInput'
AttributeInput:
type: object
required: [key, value]
properties:
key:
type: string
value:
type: string
ExperimentConfig:
type: object
properties:
variant:
type: string
feature_flags:
type: object
additionalProperties:
type: boolean
LocalizationBundle:
type: object
required: [locale_code, values]
properties:
locale_code:
type: string
values:
type: object
additionalProperties:
type: string
ErrorResponse:
type: object
required: [code, message]
properties:
code:
type: string
message:
type: string