Skip to main content

Roanuz — Hannibal Integration Details

Role in Architecture

Roanuz is Hannibal's cricket data provider. It sits in the exchanges/adapters/ layer as an IDataProvider (NOT an IExchangeAdapter — it doesn't handle betting).

User → Frontend → Backend API → RoanuzDataAdapter → Roanuz REST API
→ RoanuzWebSocketManager → Roanuz Socket.IO
→ BifrostAdapter → Bifrost (betting)
→ BetfairAdapter → Betfair (betting)

Data Flow

1. Match Discovery (REST)

Frontend requests fixtures → /api/cricket routes
→ cricketAnalysisService / ballByBallService
→ RoanuzDataAdapter.getMatches()
→ GET /v5/cricket/{key}/featured-matches-2/ (live/curated)
→ GET /v5/cricket/{key}/fixtures/ (scheduled)
→ Normalize to CricketMatch[] canonical model
→ Cache in Redis (5min TTL)

2. Match State (REST)

Frontend requests match details
→ RoanuzDataAdapter.getMatchState(matchId)
→ GET /v5/cricket/{key}/match/{matchId}/
→ Normalize to CricketMatchState
→ Cache: 1min (live), 1hr (completed)

3. Ball-by-Ball (REST + WebSocket)

REST fallback:

RoanuzDataAdapter.getBallByBall(matchId)
→ GET /v5/cricket/{key}/match/{matchId}/ball-by-ball/
→ Normalize to BallEvent[]

Related balls (preferred for live):

RoanuzDataAdapter.getRelatedBalls(matchId)
→ GET /v5/cricket/{key}/match/{matchId}/
→ Extract play.related_balls from response
→ Normalize each ball using same format as WebSocket

WebSocket (real-time):

POST /api/cricket/ball-by-ball/subscribe
→ cricketIdResolver.resolveToRoanuz(betfairId, homeTeam, awayTeam)
→ ballByBallService.subscribeToMatch(roanuzMatchId)
→ RoanuzWebSocketManager.subscribe(matchId, handler)
→ HTTP POST to subscribe endpoint with {"method": "web_socket"}
→ Socket.IO connect to socket.sports.roanuz.com/cricket
→ Emit 'connect_to_match' with token + match_key
→ Listen for 'on_match_update' events
→ Decompress gzipped data
→ Normalize to CricketStreamEvent
→ Emit to ballByBallService → AI commentary → clientWs → Frontend

4. Commentary & Scorecard (REST)

RoanuzDataAdapter.getCommentary(matchId)
→ GET /v5/cricket/{key}/match/{matchId}/commentary/

RoanuzDataAdapter.getScorecard(matchId)
→ GET /v5/cricket/{key}/match/{matchId}/scorecard/
→ Cache: 30s (live), 1hr (completed)

5. Venue Statistics (REST, computed)

RoanuzDataAdapter.getVenueStatistics(venueName, format?)
→ Fetch fixtures + featured matches
→ Filter by venue name (fuzzy match)
→ Fetch detailed match data for each (up to 20)
→ Calculate bat-first vs chase win %, avg scores
→ Cache 24hr

ID Resolution (Betfair ↔ Roanuz)

cricketIdResolver.ts maps between Betfair fixture IDs and Roanuz match keys using team name fuzzy matching:

  1. Fetch Roanuz featured matches + fixtures
  2. For each Roanuz match, compare team names against provided homeTeam/awayTeam
  3. Return Roanuz match key if match found
  4. Cache the mapping

Token Management

  • Tokens obtained via POST /v5/core/{project_key}/auth/ with {"api_key": "..."}
  • Response includes data.token and data.expires (Unix timestamp)
  • Auto-refresh when token expires in < 5 minutes
  • Token passed via rs-token header on all REST requests
  • WebSocket manager receives updated token via updateToken() and re-subscribes active matches

Caching Strategy

DataTTLKey Pattern
Fixtures list5minroanuz:fixtures:*
Match state (live)1minroanuz:state:{matchId}
Match state (completed)1hrroanuz:state:{matchId}
Scorecard (live)30sroanuz:scorecard:{matchId}
Scorecard (completed)1hrroanuz:scorecard:{matchId}
Ball-by-ball10s
Commentary30s
Venue statistics24hrroanuz:venue-stats:{venue}
WebSocket match state1minroanuz:ws:state:{matchId}

WebSocket Architecture

  • Protocol: Socket.IO (NOT raw WebSocket)
  • URL: http://socket.sports.roanuz.com/cricket (note: HTTP, not HTTPS)
  • Path: /v5/websocket
  • Single connection: Multiplexed — one Socket.IO connection, multiple match subscriptions
  • Max connections: 20 per project
  • Events: connect_to_match, on_match_joined, on_match_update, on_error
  • Data format: Gzipped JSON (decompress with gunzipSync)
  • Reconnection: Exponential backoff, max 10 attempts, 1s→60s delay
  • Heartbeat: 30s interval, 2min stale threshold
  • Deduplication: Tracks processed ball IDs per match to avoid duplicate events

Roanuz Data Formats

Match Keys

Format: {competition}_{year}_{format}_{number} or similar Examples: ipl_2026_t20_01, wc_2026_odi_final

Player Keys

Long format: c__player__rahul_yadav__fc84b Short format: p_salt, shi_dube, s_samson Adapter extracts names from keys via regex.

Team Structure

Teams labeled as a (home) and b (away) in API responses. Each has: key, name, short_name, logo_url

Innings Identifiers

Format: a_1 = team A, 1st innings; b_1 = team B, 1st innings In normalized form: 1 = first innings, 2 = second innings

{
"overs": [14, 6], // [over_number, ball_number]
"batsman": {
"player_key": "c__player__...",
"name": "Player Name",
"runs": 4,
"is_four": true,
"is_six": false,
"is_dot_ball": false
},
"bowler": {
"player_key": "c__player__...",
"name": "Bowler Name"
},
"runs": 4,
"extras": { "total": 0, "wides": 0, "no_balls": 0 },
"wicket": null,
"score": { "runs": 156, "wickets": 3, "overs": [14, 6] },
"innings": "a_1",
"key": "529536",
"comment": "FOUR! Short and wide..."
}

Toss Data Formats

Winner field varies:

  1. Object: { key: 'team-key', name: 'Team Name' }
  2. String: 'a' or 'b' (team reference)
  3. Team key: 'sl', 'eng', etc.

The adapter handles all three formats.