{
"label": "South Korea"
}
Alexandre Harano @ayharano <email@ayharano.dev>
Pequeno sistema e API HTTP
Sistema de torneio por chaves eliminatórias
Pareamento inicial
Cada rodada
Rodada final
Competidores divididos em duplas de forma aleatória
Caso não pareie todos os competidores, alguns passam automaticamente para a segunda rodada
Competem entre si
Vencedores para a próxima rodada e competem contra outros vencedores
Vencedores da penúltima rodada: partida final com vencedor do torneio e segundo lugar
Perdedores da penúltima rodada: partida de terceira lugar com terceiro e o quarto lugar
Método de Documentação
Linguagem Ubíqua
Regras Estabelecidas
Mapeamento de Dados
API e Endpoints
Um sujeito (indivíduo, time, etc.) que compete em zero ou mais torneios
Usado ubiquamente como representação de torneio de eliminação única
Confronto de dois competidores, com um resultado de um vencedor e um perdedor. É possível casos em que possua somente um competidor e por padrão tal competidor é o vencedor
Um cliente que interage com o sistema de gerenciamento usando ciclos requisição-resposta de API REST
Foram definidas 14 regras sendo as mais importantes:
Um torneio deve ter ao menos um competidor para a criação de suas partidas
O sistema é responsável pela geração aleatória inicial das partidas e das rodadas seguindo as regras
Uma partida deve ser criada com um competidor ou duas partidas anteriores
Tão importante quanto as regras são as ressalvas
Um competidor não pode desistir do torneio
Não registraremos a pontuação da partida, somente o vencedor e o perdedor
O sistema não permite empate entre os competidores
id
Inteiro para indexação das instâncias
uuid
Campo UUID para exposição externa da instância
label
Representação textual da instância
created
Criação da instância
updated
Última atualização da instância
Tournament.matchesCreation
Criação das partidas do torneio
Matches.resultRegistration
Registro do resultado de partida
competitors
Inteiro positivo representando quantidade de competidores durante o início do torneio
startingRound
Inteiro não-negativo representando a rodada de início do torneio
round
Inteiro não-negativo representando quantidade de rodadas até a partida final
position
Inteiro não-negativo representando a posição da partida na rodada do torneio
competitorA
Referência para um competidor, aqui chamado de Competidor A
competitorB
Referência para um competidor, aqui chamado de Competidor B
winner
Referência para o vencedor
loser
Referência para o perdedor
nextMatch
Referência a próxima partida do competidor no torneio
Dividido em
Escrita
Leitura
| |
Cadastro de novos torneios |
|
Cadastro dos competidores |
|
Cadastro de resultado de partida |
|
| |
Listagem de partidas |
|
Exibição do TOP 4 |
|
|
| |
Cadastro de novos competidores |
| |
Cadastro de novos torneios |
|
|
Cadastro dos competidores |
|
|
|
| |
Início do torneio |
| |
Cadastro de resultado de partida |
|
|
|
| |
Listagem de partidas |
|
|
Exibição do TOP 4 |
|
|
POST /competitor
Status code: 201 Created
|
|
|
|
POST /tournament
Status code: 201 Created
|
|
|
|
POST /tournament/<t_uuid>/competitor
Status code: 201 Created
|
|
|
|
POST /tournament/<t_uuid>/competitor
Falhas
| Competidor inexistente |
| Torneio inexistente |
| Competidor já registrado no torneio |
| Torneio já iniciado não permitindo novos registros |
POST /tournament/<t_uuid>/start
Status code: 201 Created
Payload: N/A
POST /tournament/<t_uuid>/start
Response
{
"tournament": {
"uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
"label": "2002 FIFA World Cup",
"startingRound": 1,
"numberCompetitors": 4
},
"competitors": [
{
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
},
{
"uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
"label": "South Korea"
},
{
"uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
"label": "Brazil"
},
{
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Turkey"
}
],
"matches": [
{
"uuid": "1e172084-ec76-4f56-bd8e-7b3c170e1221",
"round": 1,
"position": 0,
"competitorA": {
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
},
"competitorB": {
"uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
"label": "South Korea"
},
"winner": null,
"loser": null
},
{
"uuid": "3866cad6-ba40-44fb-96c6-09f1131c5649",
"round": 1,
"position": 1,
"competitorA": {
"uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
"label": "Brazil"
},
"competitorB": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Turkey"
},
"winner": null,
"loser": null
},
{
"uuid": "1f1fc156-4382-427c-aefb-5ae10009b7ce",
"round": 0,
"position": 0,
"competitorA": null,
"competitorB": null,
"winner": null,
"loser": null
},
{
"uuid": "a9367a16-3f64-408b-9596-4029f7f60e62",
"round": 0,
"position": 1,
"competitorA": null,
"competitorB": null,
"winner": null,
"loser": null
}
]
}
POST /tournament/<t_uuid>/start
Falhas
| Torneio inexistente |
| Torneio não possui um competidor registrado |
| Torneio já criou suas partidas |
POST /match/<m_uuid>
Status code: 200 OK
Payload:
{
"winner_uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e"
}
POST /match/<m_uuid>
Response
{
"uuid": "a9367a16-3f64-408b-9596-4029f7f60e62",
"tournament": {
"uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
"label": "2002 FIFA World Cup",
"startingRound": 1,
"numberCompetitors": 4
},
"round": 0,
"position": 0,
"competitorA": {
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
},
"competitorB": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Brazil"
},
"winner": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Brazil"
},
"loser": {
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
}
}
POST /match/<m_uuid>
Falhas
| Partida inexistente |
| Partida já registrou seu resultado |
| Partida não está pronta para registrar seu resultado devido a partidas anteriores com competidor faltante |
GET /tournament/<t_uuid>/match
Status code: 200 OK
Payload: N/A
GET /tournament/<t_uuid>/match
Response
{
"tournament": {
"uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
"label": "2002 FIFA World Cup",
"startingRound": 1,
"numberCompetitors": 4
},
"past": [
{
"uuid": "1e172084-ec76-4f56-bd8e-7b3c170e1221",
"round": 1,
"position": 0,
"competitorA": {
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
},
"competitorB": {
"uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
"label": "South Korea"
},
"winner": {
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
},
"loser": {
"uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
"label": "South Korea"
}
},
{
"uuid": "3866cad6-ba40-44fb-96c6-09f1131c5649",
"round": 1,
"position": 1,
"competitorA": {
"uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
"label": "Brazil"
},
"competitorB": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Turkey"
},
"winner": {
"uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
"label": "Brazil"
},
"loser": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Turkey"
}
},
{
"uuid": "a9367a16-3f64-408b-9596-4029f7f60e62",
"round": 0,
"position": 1,
"competitorA": {
"uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
"label": "South Korea"
},
"competitorB": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Turkey"
},
"winner": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Turkey"
},
"loser": {
"uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
"label": "South Korea"
}
}
],
"upcoming": [
{
"uuid": "1f1fc156-4382-427c-aefb-5ae10009b7ce",
"round": 0,
"position": 0,
"competitorA": {
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
},
"competitorB": {
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Brazil"
},
"winner": null,
"loser": null
}
]
}
GET /tournament/<t_uuid>/match
Falhas
| Torneio inexistente |
| Torneio ainda não criou suas partidas |
GET /tournament/<t_uuid>/result
Status code: 200 OK
Payload: N/A
GET /tournament/<t_uuid>/result
Response
{
"tournament": {
"uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
"label": "2002 FIFA World Cup",
"startingRound": 1,
"numberCompetitors": 4
},
"top4": [
{
"uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
"label": "Brazil"
},
{
"uuid": "de686e37-804b-4815-a507-d5879a240af6",
"label": "Germany"
},
{
"uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
"label": "Turkey"
},
{
"uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
"label": "South Korea"
}
]
}
GET /tournament/<t_uuid>/result
Falhas
| Torneio inexistente |
| Torneio ainda não criou suas partidas |
| Torneio ainda não está pronto para exibir seus Top 4 competidores |
CPython 3.12
uvicorn
FastAPI (pydantic + Starlette) + pydantic-settings
SQLAlchemy + SQLAlchemy-Utils + Alembic
pytest + pytest-cov + factory_boy
Modelos SQLAlchemy
Modelos pydantic
Rotas FastAPI
Serviços
Competitor
CHECK Constraint
NOT(TRIM(label) LIKE '')
Tournament
CHECK Constraint
NOT(TRIM(label) LIKE '')
(matchesCreation IS NULL AND numberCompetitors IS NULL AND startingRound IS NULL) OR (matchesCreation IS NOT NULL AND numberCompetitors IS NOT NULL AND startingRound IS NOT NULL AND numberCompetitors >= 1 AND startingRound >= 0)
TournamentCompetitor
UNIQUE Constraint
tournament_id, competitor_id
Match
UNIQUE Constraint
tournament_id, round, position
CHECK Constraint
round >= 0
position >= 0
(round == 0 AND position < 2) OR (round > 0 AND position < pow(2, round))
Match
CHECK Constraint
(competitorA_id IS NULL AND competitorB_id IS NULL) OR (competitorA_id <> competitorB_id)
(resultRegistration IS NULL AND winner_id is NULL) OR (resultRegistration IS NOT NULL AND winner_id IS NOT NULL)
(resultRegistration IS NULL AND loser_id is NULL) OR (resultRegistration IS NOT NULL AND loser_id is NULL) OR (resultRegistration IS NOT NULL AND loser_id IS NOT NULL)
CompetitorPayloadSchema
(label
)
TournamentPayloadSchema
(label
)
TournamentCompetitorPayloadSchema
(competitor_uuid
)
WinnerPayloadSchema
(winner_uuid
)
CompetitorSchema
(label
e uuid
)
TournamentSchema
(label
e uuid
)
TournamentCompetitorSchema
(tournament
e competitor
)
TournamentStartSchema
(tournament
, competitor
e matches
)
TournamentMatchesSchema
(tournament
, past
e upcoming
)
TournamentResultSchema
(tournament
e top4
)
MatchSchema
(uuid
, tournament
, round
, position
, competitorA
, competitorB
, winner
e loser
)
start_tournament
register_match_result
Exceto para o caso de um único competidor,
seja \$C\$ o número de competitores registrados,
seja \$R\$ o número da rodada inicial, e
seja \$M\$ o número de partidas de entrada.
Temos que
\$R = \lfloor\log_{2}(C-1)\rfloor\$
\$M = 2^{R}\$
start_tournament
Calcula parâmetros do torneio (competidores, rodada de entrada, quantidade de partidas de entrada)
Prepara dados de partida como lista de dicionários
Prepara sequência aleatória de competidores
Aloca competidores nas partidas de entrada
Calcula as partidas automaticamente ganhas e ajusta as seguintes
Insere partidas em lote no banco de dados
Ajusta as referências de próximas partidas
Parâmetros do torneio
\$C = 5\$
\$R = \lfloor\log_{2}(C-1)\rfloor = 2\$
\$M = 2^{R} = 4\$
Montagem de dados de partida com lista de dicionários:
r2p0 | r2p1 | r2p2 | r2p3 |
r2p0 | r2p1 | r2p2 | r2p3 | r1p0 | r1p1 |
r2p0 | r2p1 | r2p2 | r2p3 | r1p0 | r1p1 | r0p0 |
r2p0 | r2p1 | r2p2 | r2p3 | r1p0 | r1p1 | r0p0 | r0p1 |
Antes de sequência aleatória:
|
|
|
|
|
Após definição de sequência aleatória:
|
|
|
|
|
Aloca competidores nas partidas de entrada
Competidores
|
|
|
|
|
Rodada 2 (quartas de final)
r2p0 cA cB | r2p1 cA cB | r2p2 cA cB | r2p3 cA cB |
Aloca competidores nas partidas de entrada
Competidores
|
|
|
|
|
Rodada 2 (quartas de final)
r2p0 cA B cB | r2p1 cA cB | r2p2 cA cB | r2p3 cA cB |
Aloca competidores nas partidas de entrada
Competidores
|
|
|
|
|
Rodada 2 (quartas de final)
r2p0 cA B cB | r2p1 cA C cB | r2p2 cA cB | r2p3 cA cB |
Aloca competidores nas partidas de entrada
Competidores
|
|
|
|
|
Rodada 2 (quartas de final)
r2p0 cA B cB | r2p1 cA C cB | r2p2 cA A cB | r2p3 cA cB |
Aloca competidores nas partidas de entrada
Competidores
|
|
|
|
|
Rodada 2 (quartas de final)
r2p0 cA B cB | r2p1 cA C cB | r2p2 cA A cB | r2p3 cA E cB |
Aloca competidores nas partidas de entrada
Competidores
|
|
|
|
|
Rodada 2 (quartas de final)
r2p0 cA B cB D | r2p1 cA C cB | r2p2 cA A cB | r2p3 cA E cB |
Aloca competidores nas partidas de entrada
Competidores
|
|
|
|
|
Rodada 2 (quartas de final)
r2p0 cA B cB D | r2p1 cA C cB | r2p2 cA A cB | r2p3 cA E cB |
Ajusta partidas seguintes às automaticamente ganhas
Rodada 1 (semifinal)
r1p0 cA cB C | r1p1 cA A cB E |
register_match_result
Valida a partida para registrar o resultado
Ajusta as próximas partidas
Atualiza os dados de próxima partida do vencedor
Atualiza os dados de próxima partida do perdedor
Rodada 1 (semifinal)
r1p0 cA B cB C | r1p1 cA A cB |
Rodada 0 (final e terceiro lugar)
r0p0 cA cB A | r0p1 cA cB |
Competitor C ganhou r1p0
Rodada 0 (final e terceiro lugar)
r0p0 cA C cB A | r0p1 cA B cB winner B |
Name Stmts Miss Cover
------------------------------------------------------------------
src/matamata/__init__.py 1 0 100%
src/matamata/database.py 7 2 71%
src/matamata/main.py 7 0 100%
src/matamata/models/__init__.py 5 0 100%
src/matamata/models/base.py 14 0 100%
src/matamata/models/competitor.py 13 0 100%
src/matamata/models/constants.py 10 0 100%
src/matamata/models/exceptions.py 4 0 100%
src/matamata/models/match.py 25 0 100%
src/matamata/models/tournament.py 29 0 100%
src/matamata/models/tournament_competitor.py 14 0 100%
src/matamata/routers/__init__.py 0 0 100%
src/matamata/routers/competitor.py 13 0 100%
src/matamata/routers/match.py 70 0 100%
src/matamata/routers/tournament.py 83 0 100%
src/matamata/schemas.py 62 2 97%
src/matamata/services.py 81 2 98%
src/matamata/settings.py 5 0 100%
------------------------------------------------------------------
TOTAL 443 6 99%
Refatoração para melhorar legibilidade
Endpoints para melhorar usabilidade
| |
Listagem de competidores |
|
Detalhe de competidor com torneios passados, em andamento e futuros |
|
Detalhe de partida |
|
| |
Listagem de torneios |
|
Listagem de competidores em um torneio |
|
Listagem de todas as partidas de um competidor em um torneio dividido em passadas e futuras |
|
Mudança de SQLite para PostgreSQL para melhorar escalabilidade
Substituição de venv local para o uso de Docker Compose com compartilhamento de arquivos locais
Name Stmts Miss Cover
--------------------------------------------------------------------
src/matamata/__init__.py 1 0 100%
src/matamata/database.py 7 2 71%
src/matamata/main.py 7 0 100%
src/matamata/models/__init__.py 5 0 100%
src/matamata/models/base.py 14 0 100%
src/matamata/models/competitor.py 16 0 100%
src/matamata/models/constants.py 10 0 100%
src/matamata/models/exceptions.py 4 0 100%
src/matamata/models/match.py 25 0 100%
src/matamata/models/tournament.py 31 0 100%
src/matamata/models/tournament_competitor.py 15 0 100%
src/matamata/routers/__init__.py 0 0 100%
src/matamata/routers/competitor.py 34 0 100%
src/matamata/routers/match.py 32 0 100%
src/matamata/routers/tournament.py 124 0 100%
src/matamata/schemas.py 94 2 98%
src/matamata/services/__init__.py 2 0 100%
src/matamata/services/exceptions.py 10 0 100%
src/matamata/services/register_match_result.py 86 0 100%
src/matamata/services/start_tournament.py 93 2 98%
src/matamata/settings.py 5 0 100%
--------------------------------------------------------------------
TOTAL 615 6 99%
| |