scaffolding hermes flow and audit logging

This commit is contained in:
2026-04-09 18:54:10 +02:00
parent e401b6dbab
commit cf5316a2c1
59 changed files with 1830 additions and 593 deletions
@@ -25,4 +25,34 @@ final class HermesAnalyticsClient: ObservableObject, AnalyticsTracking {
)
)
}
func flush(using repository: HermesRepository) async {
guard repository.currentSession != nil else {
return
}
let pendingEvents = trackedEvents
guard !pendingEvents.isEmpty else {
return
}
do {
try await repository.submitAnalyticsBatch(
HermesAnalyticsBatchRequest(
events: pendingEvents.map { event in
HermesAnalyticsEventInput(
eventName: event.event,
occurredAt: event.timestamp,
attributes: event.attributes.map { HermesAnalyticsAttributeInput(key: $0.key, value: $0.value) }
)
}
)
)
let deliveredIds = Set(pendingEvents.map(\.id))
trackedEvents.removeAll { deliveredIds.contains($0.id) }
} catch {
return
}
}
}
@@ -36,6 +36,11 @@ final class LocalizationStore: ObservableObject {
return value
}
func localeName(for targetLocaleCode: String, displayLocaleCode: String? = nil) -> String {
let key = Self.normalize(targetLocaleCode) == "sv" ? "locale_swedish" : "locale_english"
return string(for: key, localeCode: displayLocaleCode ?? localeCode)
}
private func fallbackString(for key: String, localeCode: String) -> String {
guard localeCode != Self.fallbackLocaleCode else {
return key
@@ -1,7 +1,7 @@
import AVKit
import SwiftUI
struct StudyVideoPlayerView: View {
struct HermesVideoPlayerView: View {
@ObservedObject var coordinator: PlayerCoordinator
var body: some View {
@@ -9,13 +9,15 @@ final class PlayerCoordinator: ObservableObject {
@Published var isPlaying = false
@Published var playbackPositionMs: Int = 0
init(previewURL: URL = URL(string: "https://cdn.example.com/hermes/sample-event/master.m3u8")!) {
self.player = AVPlayer(url: previewURL)
init() {
self.player = AVPlayer()
self.player.actionAtItemEnd = .pause
}
func prepareForPreview() {
player.seek(to: .zero)
func prepareForPreview(url: URL, startTimeMs: Int = 0) {
player.replaceCurrentItem(with: AVPlayerItem(url: url))
let startTime = CMTime(seconds: Double(startTimeMs) / 1_000.0, preferredTimescale: 1_000)
player.seek(to: startTime)
player.play()
isPlaying = true
}
@@ -30,8 +32,7 @@ final class PlayerCoordinator: ObservableObject {
isPlaying = false
}
func restart() {
player.seek(to: .zero)
play()
func restart(url: URL, startTimeMs: Int = 0) {
prepareForPreview(url: url, startTimeMs: startTimeMs)
}
}
@@ -42,6 +42,10 @@ struct HermesAPIClient {
try await send(path: "api/v1/session/start", method: "POST", body: payload)
}
func health() async throws -> HermesHealthResponse {
try await send(path: "health")
}
func endSession() async throws -> HermesSessionResponse {
try await send(path: "api/v1/session/end", method: "POST")
}
@@ -23,6 +23,17 @@ struct HermesSessionResponse: Codable {
var devicePlatform: String
}
struct HermesHealthResponse: Codable {
var status: String
var serviceName: String
var environment: String
var version: String
var uptimeMs: Int
var serverTime: Date
var databaseReady: Bool
var redisReady: Bool
}
struct HermesEvent: Codable {
var id: UUID
var sportType: String
@@ -94,6 +105,22 @@ struct HermesEventManifest: Codable {
var markets: [HermesMarket]
}
struct HermesRound: Codable {
var event: HermesEvent
var media: HermesEventMedia
var market: HermesMarket
var oddsVersion: HermesOddsVersion
var settlement: HermesSettlement
}
struct HermesRound: Codable {
var event: HermesEvent
var media: HermesEventMedia
var market: HermesMarket
var oddsVersion: HermesOddsVersion
var settlement: HermesSettlement
}
struct HermesBetIntentRequest: Codable {
var sessionId: UUID
var eventId: UUID