Guia de Exportação S3 por Domínio
Estrutura de particionamento (todos os domínios)
s3://seu-bucket/{domain}_events/
organization_id={org_id}/
brand_id={brand_id}/
year={YYYY}/
month={MM}/
day={DD}/
events_part_0001.parquet
events_part_0002.parquet
Cada arquivo Parquet deve ter no máximo 500 MB. Um arquivo por dia é aceitável; vários arquivos por dia também funcionam.
Domínio: User (register / update) [#user]
Schema obrigatório
| Coluna | Tipo Parquet | Obrigatório | Descrição |
|---|---|---|---|
eid | STRING | ✓ | ID único do evento (UUID v4 ou v7) |
organization_id | STRING | ✓ | ID da organização |
brand_id | STRING | ✓ | ID da brand |
user_ext_id | STRING | ✓ | ID do usuário no seu sistema |
event | STRING | ✓ | register ou update |
timestamp | TIMESTAMP (UTC) | ✓ | Momento do evento |
status | STRING | ACTIVE, BLOCKED, SUSPENDED, BANNED, SELF_EXCLUDED, DEACTIVATED, PENDING | |
full_name | STRING | ||
first_name | STRING | ||
last_name | STRING | ||
email | STRING | ||
phone | STRING | ||
document | STRING | CPF/documento | |
birthdate | TIMESTAMP (UTC) | ||
gender | INT32 | 1=M, 2=F, 9=outro (ISO/IEC 5218) | |
country | STRING (2 chars) | ISO 3166 Alpha-2, ex: BR | |
state | STRING (2 chars) | Sigla do estado | |
city | STRING | ||
post_code | STRING | ||
registered_at | TIMESTAMP (UTC) | Data de cadastro original | |
updated_at | TIMESTAMP (UTC) | ||
registration_platform | STRING | DESKTOP, MOBILE, APP | |
kyc_status | STRING | VERIFIED, PENDING, REJECTED | |
is_test_account | BOOL | ||
ip | STRING | ||
geolocation_lat | FLOAT64 | ||
geolocation_long | FLOAT64 |
Não inclua
ingestion_source ou ingested_at — são preenchidos automaticamente pela plataforma.Exemplo Python (pandas + pyarrow)
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
import s3fs
from datetime import datetime, timezone
df = pd.DataFrame([{
"eid": "b7c3e8a0-1234-7bcd-8901-abcdef012345",
"organization_id": "org_123",
"brand_id": "brand_456",
"user_ext_id": "user_789",
"event": "register",
"timestamp": datetime(2025, 10, 15, 14, 30, 0, tzinfo=timezone.utc),
"status": "ACTIVE",
"full_name": "João Silva",
"email": "joao@exemplo.com",
"document": "12345678900",
"country": "BR",
"state": "SP",
"city": "São Paulo",
"registered_at": datetime(2025, 10, 15, 14, 30, 0, tzinfo=timezone.utc),
"registration_platform": "MOBILE",
"kyc_status": "VERIFIED",
"is_test_account": False,
}])
schema = pa.schema([
pa.field("eid", pa.string()),
pa.field("organization_id", pa.string()),
pa.field("brand_id", pa.string()),
pa.field("user_ext_id", pa.string()),
pa.field("event", pa.string()),
pa.field("timestamp", pa.timestamp("ms", tz="UTC")),
pa.field("status", pa.string(), nullable=True),
pa.field("full_name", pa.string(), nullable=True),
pa.field("email", pa.string(), nullable=True),
pa.field("document", pa.string(), nullable=True),
pa.field("country", pa.string(), nullable=True),
pa.field("state", pa.string(), nullable=True),
pa.field("city", pa.string(), nullable=True),
pa.field("registered_at", pa.timestamp("ms", tz="UTC"), nullable=True),
pa.field("registration_platform", pa.string(), nullable=True),
pa.field("kyc_status", pa.string(), nullable=True),
pa.field("is_test_account", pa.bool_(), nullable=True),
])
table = pa.Table.from_pandas(df, schema=schema, preserve_index=False)
pq.write_table(
table,
"s3://seu-bucket/user_events/organization_id=org_123/brand_id=brand_456/year=2025/month=10/day=15/events_part_0001.parquet",
filesystem=s3fs.S3FileSystem(),
)
Domínio: Casino (open / win / lose) [#casino]
Schema obrigatório
| Coluna | Tipo Parquet | Obrigatório | Descrição |
|---|---|---|---|
eid | STRING | ✓ | |
organization_id | STRING | ✓ | |
brand_id | STRING | ✓ | |
user_ext_id | STRING | ✓ | |
event | STRING | ✓ | open, win, lose |
timestamp | TIMESTAMP (UTC) | ✓ | |
currency | STRING | ✓ | BRL, USD, etc. |
bet_id | STRING | ||
casino_session_id | STRING | ||
bet_dt | TIMESTAMP (UTC) | ||
bet_status | STRING | open/win/lose (normalizado para OPEN/WIN/LOSS internamente) | |
bet_amount | DECIMAL(18,2) | ||
bet_amount_bonus | DECIMAL(18,2) | ||
win_amount | DECIMAL(18,2) | ||
win_amount_bonus | DECIMAL(18,2) | ||
after_balance | DECIMAL(18,2) | ||
before_balance | DECIMAL(18,2) | ||
game_ext_id | STRING | ||
game_name | STRING | ||
game_provider | STRING | ||
game_provider_ext_id | STRING | ||
game_type | STRING | SLOT, TABLE, LIVE | |
is_free_bet | BOOL | ||
platform | STRING | WEB, MOBILE, APP |
bet_amount e win_amount devem ser Decimal (não Float) para evitar erros de arredondamento.Domínio: Transaction (deposit / withdraw) [#transaction]
Schema obrigatório
| Coluna | Tipo Parquet | Obrigatório | Descrição |
|---|---|---|---|
eid | STRING | ✓ | |
organization_id | STRING | ✓ | |
brand_id | STRING | ✓ | |
user_ext_id | STRING | ✓ | |
event | STRING | ✓ | deposit, withdraw |
timestamp | TIMESTAMP (UTC) | ✓ | |
transaction_id | STRING | ||
transaction_dt | TIMESTAMP (UTC) | ||
amount | DECIMAL(18,2) | ||
status | STRING | RECEIVED, APPROVED, PENDING, DENIED | |
currency | STRING | ||
payment_method | STRING | PIX, TED, CREDIT_CARD | |
payment_provider | STRING | ||
bonus_credited | BOOL | ||
bonus_code | STRING | ||
kyc_verified | BOOL | ||
is_first_transaction | BOOL | ||
after_balance | DECIMAL(18,2) | ||
before_balance | DECIMAL(18,2) |
Domínio: Sportsbook (open / win / lose / cashout / refund / cancel / pending) [#sportsbook]
Campos principais
| Coluna | Tipo Parquet | Obrigatório | Descrição |
|---|---|---|---|
eid | STRING | ✓ | |
organization_id | STRING | ✓ | |
brand_id | STRING | ✓ | |
user_ext_id | STRING | ✓ | |
event | STRING | ✓ | open, win, lose, cashout, refund, cancel, pending |
timestamp | TIMESTAMP (UTC) | ✓ | |
currency | STRING | ✓ | |
bet_id | STRING | ||
bet_dt | TIMESTAMP (UTC) | ||
ticket_status | STRING | open, win, lose, cancel, cashout, refund, reject | |
bet_type | STRING | SINGLE, MULTIPLE, SYSTEM | |
bet_amount | DECIMAL(18,2) | ||
bet_amount_bonus | DECIMAL(18,2) | ||
bet_odds | FLOAT64 | ||
bet_platform | STRING | WEB, MOBILE, APP | |
bet_timing | STRING | LIVE, PREMATCH | |
bet_virtual | BOOL | ||
win_amount | DECIMAL(18,2) | ||
after_balance | DECIMAL(18,2) | ||
before_balance | DECIMAL(18,2) |
Campos de selections (formato COLUMNAR — obrigatório)
As selections devem ser exportadas em formato columnar (uma coluna por atributo, cada coluna é um array). NÃO use array de objetos JSON. Prefixo das colunas:
selection_* (singular).| Coluna | Tipo Parquet | Descrição |
|---|---|---|
selection_id | LIST(STRING) | |
selection_status | LIST(STRING) | |
selection_choice | LIST(STRING) | Ex: OVER_2.5 |
selection_sport_type | LIST(STRING) | Ex: SOCCER |
selection_league | LIST(STRING) | |
selection_market | LIST(STRING) | |
selection_odds | LIST(FLOAT64) | |
selection_is_live | LIST(BOOL) | |
selection_virtual | LIST(BOOL) | |
selection_home_team | LIST(STRING) | |
selection_away_team | LIST(STRING) | |
selection_competitors | LIST(STRING) | Concatenação (ex: "Flamengo,Palmeiras") |
selection_event_name | LIST(STRING) | |
sport_match_id | LIST(STRING) |
Exemplo Python — converter array de objetos → columnar
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
from datetime import datetime, timezone
raw_bets = [{
"eid": "sport-eid-001",
"organization_id": "org_123",
"brand_id": "brand_456",
"user_ext_id": "user_789",
"event": "open",
"timestamp": datetime(2025, 10, 15, 20, 0, 0, tzinfo=timezone.utc),
"bet_id": "bilhete_001",
"ticket_status": "open",
"bet_amount": 50.00,
"bet_odds": 2.75,
"currency": "BRL",
"bet_timing": "LIVE",
"bet_platform": "MOBILE",
"selections": [
{
"selection_id": "sel-001",
"selection_status": "open",
"selection_sport_type": "SOCCER",
"selection_league": "Campeonato Brasileiro",
"selection_market": "1X2",
"selection_choice": "1",
"selection_odds": 1.80,
"selection_is_live": True,
"selection_virtual": False,
"selection_home_team": "Flamengo",
"selection_away_team": "Palmeiras",
"selection_competitors": "Flamengo,Palmeiras",
"selection_event_name": "FLA x PAL",
"sport_match_id": "match-001",
},
],
}]
def to_columnar(bets):
"""Converte selections de array-de-objetos para colunar."""
result = []
for bet in bets:
sels = bet.pop("selections", [])
flat = {
"selection_id": [s.get("selection_id", "") for s in sels],
"selection_status": [s.get("selection_status", "") for s in sels],
"selection_choice": [s.get("selection_choice", "") for s in sels],
"selection_sport_type": [s.get("selection_sport_type", "") for s in sels],
"selection_league": [s.get("selection_league", "") for s in sels],
"selection_market": [s.get("selection_market", "") for s in sels],
"selection_odds": [float(s.get("selection_odds", 0)) for s in sels],
"selection_is_live": [bool(s.get("selection_is_live", False)) for s in sels],
"selection_virtual": [bool(s.get("selection_virtual", False)) for s in sels],
"selection_home_team": [s.get("selection_home_team", "") for s in sels],
"selection_away_team": [s.get("selection_away_team", "") for s in sels],
"selection_competitors": [s.get("selection_competitors", "") for s in sels],
"selection_event_name": [s.get("selection_event_name", "") for s in sels],
"sport_match_id": [s.get("sport_match_id", "") for s in sels],
}
result.append({**bet, **flat})
return result
rows = to_columnar(raw_bets)
schema = pa.schema([
pa.field("eid", pa.string()),
pa.field("organization_id", pa.string()),
pa.field("brand_id", pa.string()),
pa.field("user_ext_id", pa.string()),
pa.field("event", pa.string()),
pa.field("timestamp", pa.timestamp("ms", tz="UTC")),
pa.field("bet_id", pa.string(), nullable=True),
pa.field("ticket_status", pa.string(), nullable=True),
pa.field("bet_amount", pa.decimal128(18, 2), nullable=True),
pa.field("bet_odds", pa.float64(), nullable=True),
pa.field("currency", pa.string()),
pa.field("bet_timing", pa.string(), nullable=True),
pa.field("bet_platform", pa.string(), nullable=True),
pa.field("selection_id", pa.list_(pa.string())),
pa.field("selection_status", pa.list_(pa.string())),
pa.field("selection_choice", pa.list_(pa.string())),
pa.field("selection_sport_type", pa.list_(pa.string())),
pa.field("selection_league", pa.list_(pa.string())),
pa.field("selection_market", pa.list_(pa.string())),
pa.field("selection_odds", pa.list_(pa.float64())),
pa.field("selection_is_live", pa.list_(pa.bool_())),
pa.field("selection_virtual", pa.list_(pa.bool_())),
pa.field("selection_home_team", pa.list_(pa.string())),
pa.field("selection_away_team", pa.list_(pa.string())),
pa.field("selection_competitors", pa.list_(pa.string())),
pa.field("selection_event_name", pa.list_(pa.string())),
pa.field("sport_match_id", pa.list_(pa.string())),
])
df = pd.DataFrame(rows)
table = pa.Table.from_pandas(df, schema=schema, preserve_index=False)
pq.write_table(
table,
"s3://seu-bucket/sportsbook_events/organization_id=org_123/brand_id=brand_456/year=2025/month=10/day=15/events_part_0001.parquet",
)