{
"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
idInteiro para indexação das instâncias
uuidCampo UUID para exposição externa da instância
labelRepresentação textual da instância
createdCriação da instância
updatedÚltima atualização da instância
Tournament.matchesCreationCriação das partidas do torneio
Matches.resultRegistrationRegistro do resultado de partida
competitorsInteiro positivo representando quantidade de competidores durante o início do torneio
startingRoundInteiro não-negativo representando a rodada de início do torneio
roundInteiro não-negativo representando quantidade de rodadas até a partida final
positionInteiro não-negativo representando a posição da partida na rodada do torneio
competitorAReferência para um competidor, aqui chamado de Competidor A
competitorBReferência para um competidor, aqui chamado de Competidor B
winnerReferência para o vencedor
loserReferência para o perdedor
nextMatchReferê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 /competitorStatus code: 201 Created
|
|
| |
POST /tournamentStatus code: 201 Created
|
|
| |
POST /tournament/<t_uuid>/competitorStatus code: 201 Created
|
|
| |
POST /tournament/<t_uuid>/competitorFalhas
| Competidor inexistente |
| Torneio inexistente |
| Competidor já registrado no torneio |
| Torneio já iniciado não permitindo novos registros |
POST /tournament/<t_uuid>/startStatus code: 201 Created
Payload: N/A
POST /tournament/<t_uuid>/startResponse
{
"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>/startFalhas
| 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>/matchStatus code: 200 OK
Payload: N/A
GET /tournament/<t_uuid>/matchResponse
{
"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>/matchFalhas
| Torneio inexistente |
| Torneio ainda não criou suas partidas |
GET /tournament/<t_uuid>/resultStatus code: 200 OK
Payload: N/A
GET /tournament/<t_uuid>/resultResponse
{
"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>/resultFalhas
| 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
CompetitorCHECK Constraint
NOT(TRIM(label) LIKE '')
TournamentCHECK 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)
TournamentCompetitorUNIQUE Constraint
tournament_id, competitor_id
MatchUNIQUE Constraint
tournament_id, round, position
CHECK Constraint
round >= 0
position >= 0
(round == 0 AND position < 2) OR (round > 0 AND position < pow(2, round))
MatchCHECK 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_tournamentCalcula 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_resultValida 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%
| |