Skip to content

Commit

Permalink
fix: Update logic for division generation to operate based on qualifi…
Browse files Browse the repository at this point in the history
…ed indicator (#8)
  • Loading branch information
ebachle authored Apr 15, 2024
1 parent ecf5ac8 commit 3c0adfc
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ python generate_divisions.py --divisions-file test_data/divisions.txt --out-file
## Validate Generated Divisions

```bash
python validate_divisions.py --out-file test_data/out.txt --season 2024 --district FIM --api-key "username:guid"
python validate_divisions.py --out-file test_data/out.txt --season 2024 --district FIM --api-key "username:guid" --num-teams 160
```

## File Formats
Expand Down
23 changes: 21 additions & 2 deletions generate_divisions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import validate_divisions
import util


def main(
api_key: str = typer.Option(..., help="Formatted as 'username:token'"),
season: int = typer.Option(...),
Expand Down Expand Up @@ -70,10 +71,27 @@ def print_debug(message: str):
quartile_size = num_teams // 4
division_quartile_size = quartile_size // len(divisions)

rankings = util.get_rankings(season, district, num_teams, api_key)
rankings = util.get_rankings(season, district, api_key)

qualified_teams = [team for team in rankings if team.qualified]

if len(qualified_teams) != num_teams:
typer.echo(f"Expected {num_teams} teams to attend the championship, got {len(qualified_teams)}")
raise typer.Exit(code=1)

# get the keys of the accomodations dictionary
accommodated_teams = list(accommodations.keys())
# if accommodated teams are not in qualified teams, raise an error
for team in accommodated_teams:
if team not in [qualified_team.team_number for qualified_team in qualified_teams]:
typer.echo(f"Team {team} is not qualified to attend the championship")
raise typer.Exit(code=1)

# Let's ensure this is sorted, despite what the API returns
qualified_teams = sorted(qualified_teams, key=lambda team: team.district_points, reverse=True)

# Divide into quartiles and shuffle them
quartiles = [rankings[i * quartile_size:(i + 1) * quartile_size] for i in range(4)]
quartiles = [qualified_teams[i * quartile_size:(i + 1) * quartile_size] for i in range(4)]

while True:
breaks_rule = False
Expand Down Expand Up @@ -109,6 +127,7 @@ def print_debug(message: str):
file.write(f"{team}\n")
file.write(f"\n")


if __name__ == "__main__":
assert sys.version_info >= (3, 7)
typer.run(main)
1 change: 1 addition & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
class DivisionTeam:
team_number: int
district_points: int
qualified: bool

@dataclass
class Division:
Expand Down
13 changes: 7 additions & 6 deletions util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import models

def get_rankings(season: int, district: str, num_teams: int, api_key: str) -> list[models.DivisionTeam]:

def get_rankings(season: int, district: str, api_key: str) -> list[models.DivisionTeam]:
rankings = []
current_page = 1
total_pages = 99
Expand All @@ -13,14 +14,14 @@ def get_rankings(season: int, district: str, num_teams: int, api_key: str) -> li
"Accept": "application/json"
}

while len(rankings) < 160 and current_page < total_pages:
while current_page <= total_pages:
resp = requests.get(f"https://frc-api.firstinspires.org/v3.0/{season}/rankings/district?districtCode={district}&page={current_page}", headers=api_headers)
if not resp.ok:
raise Exception("Got a bad response from the FRC API: " + str(resp.status_code) + " - " + resp.text)
resp = resp.json()
if current_page == 1:
total_pages = resp["pageTotal"]
rankings += [models.DivisionTeam(t["teamNumber"], t["totalPoints"]) for t in resp["districtRanks"]]
total_pages = resp["pageTotal"]
rankings += [models.DivisionTeam(t["teamNumber"], t["totalPoints"], t["qualifiedDistrictCmp"]) for t in resp["districtRanks"]]
current_page += 1
return rankings[:num_teams]

return rankings
18 changes: 14 additions & 4 deletions validate_divisions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
CHECK_MARK = "✅"
X_MARK = "❌"


def __within_tolerance(val_list: list[float], tolerance: float):
return max(val_list) - min(val_list) <= tolerance


def validate_divisions(division_output: list[models.Division]) -> bool:
'''Checks that divisions conform with the tolerances set in the FRC game manual'''
"""Checks that divisions conform with the tolerances set in the FRC game manual"""
if not __within_tolerance([d.strength() for d in division_output], 2):
return False
if not __within_tolerance([d.snr() for d in division_output], 2.5):
Expand All @@ -23,11 +25,13 @@ def validate_divisions(division_output: list[models.Division]) -> bool:
return False
return True


def main(
out_file: Path = typer.Option(..., exists=True, file_okay=True, dir_okay=False, resolve_path=True, readable=True),
api_key: str = typer.Option(..., help="Formatted as 'username:token'"),
season: int = typer.Option(...),
district: str = typer.Option(..., help="FRC District Code"),
num_teams: int = typer.Option(..., help="The total number of teams to attend the championship")
):
typer.echo("NOTE: this tool assumes that all constraints from accommodations have been fulfilled")

Expand All @@ -41,11 +45,17 @@ def main(
name = lines[0]
team_mapping[name] = [int(line) for line in lines[1:]]

rankings = util.get_rankings(season, district, sum([len(v) for v in team_mapping.values()]), api_key)
rankings = util.get_rankings(season, district, api_key)
team_rankings_map = {team.team_number: team for team in rankings}

team_count = sum(len(v) for v in team_mapping.values())

if team_count != num_teams:
typer.echo(typer.style(f"{X_MARK} Total number of teams does not match: {team_count} != {num_teams}", fg="red"))
raise typer.Exit(code=1)

divisions = [models.Division(k, [team_rankings_map[t] for t in v]) for (k, v) in team_mapping.items()]

# Check that the math works out
is_valid = validate_divisions(divisions)
if is_valid:
Expand All @@ -64,4 +74,4 @@ def main(

if __name__ == "__main__":
assert sys.version_info >= (3, 7)
typer.run(main)
typer.run(main)

0 comments on commit 3c0adfc

Please sign in to comment.