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:
- Fetch Roanuz featured matches + fixtures
- For each Roanuz match, compare team names against provided homeTeam/awayTeam
- Return Roanuz match key if match found
- Cache the mapping
Token Management
- Tokens obtained via
POST /v5/core/{project_key}/auth/with{"api_key": "..."} - Response includes
data.tokenanddata.expires(Unix timestamp) - Auto-refresh when token expires in < 5 minutes
- Token passed via
rs-tokenheader on all REST requests - WebSocket manager receives updated token via
updateToken()and re-subscribes active matches
Caching Strategy
| Data | TTL | Key Pattern |
|---|---|---|
| Fixtures list | 5min | roanuz:fixtures:* |
| Match state (live) | 1min | roanuz:state:{matchId} |
| Match state (completed) | 1hr | roanuz:state:{matchId} |
| Scorecard (live) | 30s | roanuz:scorecard:{matchId} |
| Scorecard (completed) | 1hr | roanuz:scorecard:{matchId} |
| Ball-by-ball | 10s | — |
| Commentary | 30s | — |
| Venue statistics | 24hr | roanuz:venue-stats:{venue} |
| WebSocket match state | 1min | roanuz: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
Ball Event Format (related_balls)
{
"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:
- Object:
{ key: 'team-key', name: 'Team Name' } - String:
'a'or'b'(team reference) - Team key:
'sl','eng', etc.
The adapter handles all three formats.