Skip to content

Commit

Permalink
Merge pull request #94 from fedecarboni7/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
fedecarboni7 authored Oct 15, 2024
2 parents 1d5a31a + 4089402 commit 4703cfc
Show file tree
Hide file tree
Showing 12 changed files with 912 additions and 126 deletions.
50 changes: 33 additions & 17 deletions app/routes/main_routes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Dict, List
from typing import List

from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from sqlite3 import OperationalError
from requests import Session

Expand All @@ -10,6 +10,7 @@
from app.db.database_utils import execute_with_retries, query_players
from app.db.models import Player, User
from app.db.schemas import PlayerCreate
from app.utils.ai_formations import create_formations
from app.utils.auth import get_current_user
from app.utils.team_optimizer import find_best_combination

Expand All @@ -20,7 +21,6 @@
async def landing_page(request: Request):
return templates.TemplateResponse(request=request, name="landing-page.html")

calculated_results: Dict[str, dict] = {}

@router.get("/", response_class=HTMLResponse, include_in_schema=False)
async def get_form(
Expand All @@ -40,14 +40,6 @@ async def get_form(

context = {"players": players}

if current_user_id in calculated_results:
context.update(calculated_results[current_user_id])
context.update({
"len_teams": len(context["teams"]),
"skills": {"velocidad": "Velocidad", "resistencia": "Resistencia", "control": "Control", "pases": "Pases", "fuerza_cuerpo": "Fuerza cuerpo", "habilidad_arquero": "Hab. Arquero", "defensa": "Defensa", "tiro": "Tiro", "vision": "Visión"}
})
del calculated_results[current_user_id]

return templates.TemplateResponse(request=request, name="index.html", context=context)


Expand Down Expand Up @@ -184,10 +176,34 @@ async def submit_form(
for _, player in player_data_dict.items()
}

calculated_results[current_user_id] = {
"teams": teams,
"min_difference_total": str(min_difference_total),
"playerDataDict": player_data_dict
}
# Renderizar solo el bloque de HTML que contiene los equipos
rendered_html = templates.get_template("results.html").render(
teams=teams,
skills={"velocidad": "Velocidad", "resistencia": "Resistencia", "control": "Control", "pases": "Pases", "fuerza_cuerpo": "Fuerza cuerpo", "habilidad_arquero": "Hab. Arquero", "defensa": "Defensa", "tiro": "Tiro", "vision": "Visión"},
min_difference_total=min_difference_total,
len_teams=len(teams)
)

return JSONResponse(content={"html": rendered_html, "player_data_dict": player_data_dict, "teams": teams})

@router.post("/formations", include_in_schema=False, response_class=JSONResponse)
async def show_formations(
request: Request,
current_user: User = Depends(get_current_user)
):
if not current_user:
request.session.clear()
return RedirectResponse("/landing-page", status_code=302)

# Recibir los datos del frontend
data = await request.json()

# Extraer los valores del diccionario JSON recibido
player_data_dict = data.get('player_data_dict')
teams = data.get('teams')

# Generar las formaciones
formations = await create_formations(player_data_dict, teams)

return RedirectResponse(url="/", status_code=303)
# Retornar las formaciones como respuesta JSON
return JSONResponse(content=formations)
100 changes: 100 additions & 0 deletions app/utils/ai_formations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import asyncio
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain.prompts import PromptTemplate
from langchain_core.output_parsers import JsonOutputParser

llm = ChatGoogleGenerativeAI(model="gemini-1.5-flash")

# Define el template del prompt
prompt_template = PromptTemplate(
input_variables=["num_players", "team_data", "allowed_formations"],
template="""
Eres un entrenador experto especializado en equipos de fútbol de {num_players} jugadores.
Basándote en la habilidades de los jugadores listadas a continuación y en las formaciones permitidas con sus posiciones correspondientes,
asigna una formación táctica óptima y posiciones a los jugadores.
Formaciones permitidas:
{allowed_formations}
Habilidades de los jugadores:
{team_data}
Asigna una formación que maximice las fortalezas del equipo, en el siguiente formato JSON:
{{
"formation": "X-X-X",
"players": [
{{"position": "XX", "number": N, "name": "Nombre del Jugador"}},
...
]
}}
Donde:
- "formation" es la formación asignada.
- "players" es una lista de jugadores con su número de camiseta y posición asignada.
- "position" es la abreviatura de la posición asignada al jugador.
- "number" es un número de camiseta único del 1 al {num_players}
- "name" es el nombre del jugador
Asegúrate de que la formación y las posiciones asignadas sean consistentes con las habilidades de los jugadores.
"""
)

# Crea la cadena LLM
chain = prompt_template | llm | JsonOutputParser()

allowed_formations = {
5: {
'2-1-1': ['GK', 'CB', 'CB', 'CM', 'ST'],
'3-0-1': ['GK', 'CB', 'CB', 'CB', 'ST'],
'2-0-2': ['GK', 'CB', 'CB', 'ST', 'ST'],
'1-2-1': ['GK', 'CB', 'CM', 'CM', 'ST'],
'1-1-2': ['GK', 'CB', 'CM', 'ST', 'ST'],
'1-0-3': ['GK', 'CB', 'ST', 'ST', 'ST']
},
11:
{
'4-4-2': ['GK', 'LB', 'CB', 'CB', 'RB', 'LM', 'CM', 'CM', 'RM', 'ST', 'ST'],
'4-3-3': ['GK', 'LB', 'CB', 'CB', 'RB', 'CM', 'CM', 'CM', 'LW', 'ST', 'RW'],
'3-4-3': ['GK', 'CB', 'CB', 'CB', 'LM', 'CM', 'CM', 'RM', 'LW', 'ST', 'RW'],
'4-2-3-1': ['GK', 'LB', 'CB', 'CB', 'RB', 'CDM', 'CDM', 'LW', 'CAM', 'RW', 'ST'],
'5-4-1': ['GK', 'LB', 'CB', 'CB', 'CB', 'RB', 'LM', 'CM', 'CM', 'RM', 'ST'],
'4-5-1': ['GK', 'LB', 'CB', 'CB', 'RB', 'LM', 'CM', 'CM', 'CM', 'RM', 'ST'],
'3-5-2': ['GK', 'CB', 'CB', 'CB', 'LM', 'CM', 'CM', 'CM', 'RM', 'ST', 'ST'],
'5-3-2': ['GK', 'LB', 'CB', 'CB', 'CB', 'RB', 'LM', 'CM', 'RM', 'ST', 'ST'],
'4-1-4-1': ['GK', 'LB', 'CB', 'CB', 'RB', 'CDM', 'LM', 'CM', 'CM', 'RM', 'ST'],
'3-4-2-1': ['GK', 'CB', 'CB', 'CB', 'LM', 'CM', 'CM', 'RM', 'AM', 'AM', 'ST']
}
}

async def create_formations(players, teams, allowed_formations=allowed_formations):
"""
Calcula la formación óptima y asigna posiciones a los jugadores.
Args:
players (dict): Un diccionario con los datos de los jugadores.
teams (list): Una lista de listas con los nombres de los jugadores en cada equipo.
allowed_formations (Dict[int, List[str]]): Un diccionario con las formaciones permitidas por número de jugadores.
Returns:
formations (Dict): Un diccionario con las formaciones sugeridas para cada equipo.
"""

formations = {'team1': {}, 'team2': {}}

# Función auxiliar para invocar chain.ainvoke asíncronamente
async def get_formation_for_team(team, team_number):
players_by_team = {k: players[k] for k in team[0]}
num_players = len(players_by_team)
return await chain.ainvoke({"team_data": players_by_team,
"num_players": num_players,
"allowed_formations": allowed_formations[num_players]})

# Ejecutar todas las invocaciones de forma simultánea
tasks = [get_formation_for_team(team, i+1) for i, team in enumerate(teams)]
results = await asyncio.gather(*tasks)

# Asignar los resultados a sus respectivos equipos
for i, formation in enumerate(results):
formations[f'team{i+1}'] = formation

return formations
7 changes: 5 additions & 2 deletions app/utils/team_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ def find_best_combination(scores):
min_difference = float("inf")
min_difference_total = float("inf")
mejores_equipos = list()
number_of_combinations = len(all_combinations) // 2
if len(all_combinations) % 2 == 1:
number_of_combinations += 1

for combination in all_combinations:
team1_indices = combination
for i in range(number_of_combinations):
team1_indices = all_combinations[i]
team2_indices = [i for i in range(len(scores)) if i not in team1_indices]

team1_score = calculate_team_score(team1_indices, scores)
Expand Down
8 changes: 6 additions & 2 deletions proyect_structure.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Listado de rutas de carpetas
| | | __init__.py
| |
| +---utils
| | | ai_formations.py
| | | auth.py
| | | security.py
| | | team_optimizer.py
Expand All @@ -45,8 +46,9 @@ Listado de rutas de carpetas
+---static
| +---css
| | daisyui.min.css
| | formations.css
| | style.css
| | styles-v1.0.3.css
| | styles-v1.0.4.css
| |
| +---favicon-v1.0
| | android-chrome-192x192.png
Expand Down Expand Up @@ -74,14 +76,16 @@ Listado de rutas de carpetas
| | iphone-hero.png
| |
| +---js
| formations.js
| main.min.js
| script-v1.0.6.js
| script-v1.0.7.js
|
+---templates
| 500.html
| index.html
| landing-page.html
| login.html
| results.html
| signup.html
|
+---tests
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ pyjwt[crypto]
httpx
uvicorn
python-multipart
langchain
langchain-google-genai
Loading

0 comments on commit 4703cfc

Please sign in to comment.