2024 Regional CMP Qualification based on 2026 System

I wrote a possibly broken and buggy script to simulate the 2026 universal points system. My script gave 320 slots for regional qualification, both through regionals and the regional pool, assuming 600 champs slots, districts proportional to number of teams, and 8 pre-qualified teams.

Script Output (details which team qualified how

Canadian Pacific Regional
frc1622 - 76
frc359 - 73
frc4270 - 72
Brazil Regional
frc7565 - 89
frc1156 - 73
frc7567 - 64
Hueneme Port Regional
frc498 - 81
frc4481 - 71
frc3647 - 63
Silicon Valley Regional
frc1678 - 112
frc581 - 68
frc604 - 58
South Florida Regional
frc108 - 77
frc179 - 73
frc180 - 72
Lake Superior Regional
frc1732 - 73
frc2052 - 71
frc8298 - 52
Northern Lights Regional
frc6147 - 68
frc4230 - 65
frc6574 - 63
Regional Monterrey presented by PrepaTec
frc4400 - 82
frc7421 - 71
frc4403 - 70
Greater Pittsburgh Regional
frc2614 - 82
frc4611 - 69
frc325 - 66
Festival de Robotique Regional
frc3990 - 112
frc7605 - 73
frc3544 - 63
Smoky Mountains Regional
frc7428 - 78
frc801 - 73
frc4020 - 72
Utah Regional
frc1410 - 64
frc1339 - 63
frc7426 - 61
Arkansas Regional
frc16 - 73
frc3937 - 72
frc2341 - 63
San Francisco Regional
frc114 - 73
frc5940 - 72
frc1700 - 60
Ventura County Regional
frc4414 - 73
frc8 - 72
frc589 - 66
Central Missouri Regional
frc1706 - 73
frc4522 - 72
frc4593 - 62
Great Northern Regional
frc3134 - 73
frc876 - 71
frc3276 - 68
Hudson Valley Regional
frc353 - 73
frc1796 - 67
frc7748 - 58
Green Country Regional
frc1730 - 94
frc4499 - 80
frc6424 - 73
Ä°stanbul Regional
frc8613 - 73
frc6941 - 71
frc6014 - 63
Bosphorus Regional
frc4613 - 73
frc9483 - 69
frc8570 - 61
Week 2 Regional Pool
frc6902 - 119.60000000000001
frc3478 - 114.80000000000001
frc1781 - 113.2
frc9602 - 111.60000000000001
frc2491 - 111.60000000000001
frc4635 - 111.60000000000001
frc6517 - 110.0
frc8033 - 110.0
frc6459 - 110.0
frc2486 - 108.4
frc8806 - 107
frc3996 - 106.80000000000001
frc1987 - 106.80000000000001
frc9460 - 105.2
frc2102 - 105.2
frc5528 - 105.2
frc6036 - 103.60000000000001
frc1714 - 103.60000000000001
frc2129 - 103.60000000000001
frc1014 - 103.60000000000001
frc3245 - 103.60000000000001
frc2854 - 103.60000000000001
frc9576 - 103.60000000000001
frc8153 - 103.60000000000001
frc4421 - 102.0
frc5557 - 102.0
frc5817 - 102.0
frc2530 - 102.0
frc4329 - 102.0
frc2357 - 100.4
frc4646 - 100.4
frc1797 - 100.4
frc20 - 100.4
frc2383 - 98.80000000000001
frc5439 - 98.80000000000001
frc4944 - 98.80000000000001
frc3256 - 97.2
frc4265 - 97.2
frc3928 - 97.2
frc7632 - 97
frc3128 - 95.60000000000001
frc144 - 95.60000000000001
frc3293 - 95.60000000000001
frc2531 - 94.0
Southern Cross Regional
frc5985 - 89
frc8011 - 70
frc5584 - 59
Arizona Valley Regional
frc4146 - 65
frc9501 - 51
frc6413 - 50
Sacramento Regional
frc2073 - 75
frc254 - 71
frc5419 - 59
Los Angeles Regional
frc687 - 80
frc9408 - 71
frc368 - 68
Tallahassee Regional
frc5472 - 96
frc386 - 44
frc21 - 40
Central Illinois Regional
frc2338 - 84
frc2481 - 73
frc1756 - 71
Heartland Regional
frc3061 - 71
frc1986 - 56
frc1710 - 42
Magnolia Regional
frc8044 - 73
frc2987 - 67
frc2992 - 65
Regional Hermosillo presented by PrepaTec
frc5705 - 78
frc6647 - 68
frc7102 - 67
Finger Lakes Regional
frc694 - 73
frc1591 - 63
frc1468 - 57
Wisconsin Regional
frc930 - 106
frc4786 - 78
frc8744 - 56
Arizona East Regional
frc7419 - 80
frc670 - 73
frc294 - 49
Central Valley Regional
frc840 - 81
frc1323 - 73
frc1671 - 67
San Diego Regional presented by Qualcomm
frc3341 - 70
frc2485 - 61
frc4738 - 49
Colorado Regional
frc3006 - 66
frc3729 - 52
frc1108 - 46
Orlando Regional
frc59 - 73
frc2080 - 50
frc8841 - 50
Iowa Regional
frc4607 - 89
frc4143 - 66
frc2526 - 54
Idaho Regional
frc5461 - 82
frc2122 - 62
frc2813 - 59
St. Louis Regional
frc3284 - 65
frc1288 - 44
frc2783 - 39
Regional Laguna presented by Peñoles
frc3354 - 64
frc9120 - 59
frc6170 - 49
FIRST Long Island Regional
frc9016 - 68
frc6806 - 66
frc6423 - 54
New York Tech Valley Regional
frc3173 - 59
frc3419 - 57
frc5993 - 55
Buckeye Regional
frc4145 - 74
frc3015 - 73
frc1787 - 72
Haliç Regional
frc6429 - 88
frc8214 - 73
frc9609 - 71
Marmara Regional
frc8054 - 100
frc7576 - 57
frc6989 - 47
Week 4 Regional Pool
frc2826 - 114.80000000000001
frc9692 - 107
frc8159 - 105
frc6017 - 102
frc379 - 100
frc7525 - 99
frc4336 - 98
frc34 - 97.2
frc9535 - 95.60000000000001
frc2823 - 94.0
frc5507 - 94.0
frc4253 - 93
frc3473 - 93
frc340 - 93
frc6995 - 93
frc111 - 92.4
frc6381 - 91
frc2584 - 91
frc6907 - 90.80000000000001
frc6694 - 90.80000000000001
frc9175 - 89.2
frc6743 - 89.2
frc5851 - 89.2
frc5914 - 87.60000000000001
frc5172 - 87.60000000000001
frc5458 - 87.60000000000001
frc9624 - 87
frc6985 - 87
frc9199 - 86.0
frc7536 - 86.0
frc7042 - 86.0
frc1912 - 86.0
frc2194 - 86.0
frc3501 - 85
frc287 - 85
frc8882 - 84.4
frc6045 - 84.4
frc3522 - 84.4
frc9406 - 84.4
frc3277 - 84.4
frc4788 - 84.4
frc4135 - 84.4
frc7094 - 84.4
frc9694 - 84.4
frc5232 - 83
Monterey Bay Regional
frc649 - 65
frc971 - 57
frc6962 - 53
Orange County Regional
frc3476 - 91
frc7157 - 71
frc5199 - 56
Midwest Regional
frc5934 - 48
frc5822 - 47
frc4655 - 45
Las Vegas Regional
frc987 - 72
frc2403 - 59
frc8717 - 49
Rocket City Regional
frc2638 - 60
frc6652 - 58
frc5002 - 40
Aerospace Valley Regional
frc2429 - 76
frc2659 - 70
frc1148 - 63
East Bay Regional
frc2637 - 56
frc6619 - 44
frc841 - 41
Hawaii Regional
frc2438 - 56
frc2443 - 55
frc2465 - 46
Bayou Regional
frc456 - 61
frc2183 - 54
frc4065 - 49
Minnesota 10,000 Lakes Regional
frc2472 - 70
frc3926 - 58
frc3630 - 52
Minnesota Granite City Regional
frc1816 - 80
frc7028 - 72
frc2883 - 68
Greater Kansas City Regional
frc4766 - 69
frc5809 - 62
frc1764 - 56
New York City Regional
frc2869 - 92
frc4122 - 73
frc3950 - 67
Miami Valley Regional
frc4028 - 71
frc8393 - 65
frc5667 - 58
Oklahoma Regional
frc9401 - 65
frc1561 - 61
frc935 - 54
Seven Rivers Regional
frc7021 - 68
frc525 - 63
frc8802 - 55
Week 6 Regional Pool
frc2373 - 89.2
frc2718 - 86
frc5700 - 85
frc1155 - 85
frc4539 - 84
frc5811 - 84
frc2791 - 83
frc8780 - 82.8
frc8056 - 82.8
frc100 - 82
frc4952 - 81.2
frc6838 - 81
frc9432 - 81
frc1538 - 81
frc8581 - 80
frc7522 - 80
frc9604 - 79.60000000000001
frc9429 - 79.60000000000001
frc6436 - 79.60000000000001
frc8814 - 79.60000000000001
frc4091 - 79.60000000000001
frc3480 - 79.60000000000001
frc6873 - 79.60000000000001
frc5736 - 79
frc6702 - 79
frc973 - 78
frc4630 - 78.0
frc6560 - 78
frc9084 - 78
frc1884 - 78.0
frc2143 - 78.0
frc5690 - 77
frc4107 - 77
frc7257 - 77
frc9134 - 77
frc424 - 77
frc1880 - 77
frc9200 - 76.4
frc329 - 76.4
frc5434 - 76.4
frc9452 - 75
frc3360 - 75
frc3749 - 75
frc5013 - 75
frc2905 - 75

There’s a decent chance I messed up with this, so please take it with a grain of salt. Also keep in mind that this doesn’t take tiebreakers into account.

25 Likes

For those who want to see the code:

Code
import requests
import math

TOTAL_TEAMS = 3474

HEADERS = {"X-TBA-Auth-Key": "****************"}

ROOT_URL = "https://www.thebluealliance.com/api/v3"

CAPACITY = 600

IMPACT_AWARD_KEY = 0
EI_KEY = 9

REGIONALS = 62

qualified_teams: set[str] = set()

events: list = requests.get(ROOT_URL + "/events/2024", headers=HEADERS).json()

regional_pool: dict[str, tuple[int] | tuple[int, int]] = {}

for team_count in {127, 127, 98, 514, 63, 190, 185, 107, 77, 138, 61}:
    CAPACITY -= math.ceil(team_count / TOTAL_TEAMS * 600)

CAPACITY -= 8

REGIONAL_POOL_SIZE = CAPACITY - REGIONALS * 3

district_teams: set[str] = set()

for district in {"fma", "pnw", "pch", "fim", "fin", "fit", "ne", "chs", "fnc", "ont", "isr"}:
    for team in requests.get(ROOT_URL + f"/district/2024{district}/teams", headers=HEADERS).json():
        district_teams.add(team["key"])

def regional_pool_points(events: tuple[int] | tuple[int, int]) -> int:
    if len(events) == 2:
        return events[0] + events[1]
    return events[0] * 1.6 + 14

for week in range(1, 7):
    for event in filter(lambda event: event["week"] == week - 1 and event["district"] is None, events):
        print(event["name"])
        this_event_teams: dict[str, int] = {}
        event_key = event["key"]
        district_points = requests.get(ROOT_URL + f"/event/{event_key}/district_points", headers=HEADERS).json()
        awards = requests.get(ROOT_URL + f"/event/{event_key}/awards", headers=HEADERS).json()
        for team_key, points in district_points["points"].items():
            if team_key in district_teams:
                continue
            if team_key in qualified_teams:
                continue
            points = points["total"]
            if list(filter(lambda award: award["award_type"] == IMPACT_AWARD_KEY and award["recipient_list"][0]["team_key"] == team_key, awards)):
                points += 45 - 10
            if list(filter(lambda award: award["award_type"] == EI_KEY and award["recipient_list"][0]["team_key"] == team_key, awards)):
                points += 28 - 8
            this_event_teams[team_key] = points
            if team_key not in regional_pool:
                regional_pool[team_key] = (points,)
            elif len(regional_pool[team_key]) == 1:
                regional_pool[team_key] = (regional_pool[team_key][0], points)
        for team_key in list(sorted(this_event_teams.keys(), key=lambda key: this_event_teams[key], reverse=True))[:3]:
            qualified_teams.add(team_key)
            if team_key in regional_pool:
                del regional_pool[team_key]
            print(team_key, "-", this_event_teams[team_key])
    if week % 2 == 0:
        print("Week", week, "Regional Pool")
        TO_QUALIFY = 0
        if week == 2:
            TO_QUALIFY = REGIONAL_POOL_SIZE // 3
        elif week == 4:
            TO_QUALIFY = REGIONAL_POOL_SIZE // 2
        else:
            TO_QUALIFY = REGIONAL_POOL_SIZE
        REGIONAL_POOL_SIZE -= TO_QUALIFY
        for team_key in list(sorted(regional_pool.keys(), key=lambda key: regional_pool_points(regional_pool[key]), reverse=True))[:TO_QUALIFY]:
            qualified_teams.add(team_key)
            print(team_key, "-", regional_pool_points(regional_pool[team_key]))
            del regional_pool[team_key]

So, I was running through this to get a sense of how this would change what teams local to me qualified and something seems off about this. I’m not sure where the error appears but I can give the teams and values I used to check this.
4146 (AZVA 8 alliance selection points, 45 award points (FIA), 0 elim points, 12 qual points = 53, AZGL 4 alliance selection points, 0 award points, 20 elim points, 10 qual points = 34) 99 Points
972 (CASJ 12 alliance selection points, 5 award points, 13 elim points, 14 qual points = 44, CAFR 12 alliance selection points, 0 award points, 0 elim points, 14 qual points = 26) 70 Points.
Your script returned 972 qualifying and 4146 not qualifying. I’m not 100% sure where the error is but something seems off, or I am misunderstanding how the points are supposed to add up.

I think something is wrong here in relation to how it is handling the awards.

Looking at South Florida the top 3 point earners should be:
108, 179, and 180 with 77, 73, and 72, points respectively.

@RoboticDaymon @MARS_James Thanks for catching that. What happened was the script was adding Impact and EI points to all teams because a filter object is true even if it has no items. It should be fixed now.

I’ve updated so that it should display point totals qualified with as well to make it easier to find errors.

So what’s the distribution for number of points a team will have and qualify?

Edit:
I have done some very questionable google sheets math and this is what I came out with

image

Edit 2:
This may(?) be inaccurate since it uses pigrammer’s code rather than bovlb’s

Edit once more:
After more questionable google sheetsing the histogram for bovlb’s code appears to be almost identical. Curiously though the teams are different as my team appears in pigrammer’s code but not bovlb’s

image

Also using bov’s list I was able to produce this chart which seems interesting

image

2 Likes

I think it is safe to assume that femtopoints, even ten of them, can be neglected.

17 Likes

Thanks for this. I tweaked your script slightly to remove pre-qualified teams and emit more data about scores and weeks.

Data

Week 1 Events

Brazil Regional

frc7565 89
frc1156 73
frc7567 64

Canadian Pacific Regional

frc1622 76
frc359 73
frc4270 72

Festival de Robotique Regional

frc3990 112
frc7605 73
frc3544 63

Greater Pittsburgh Regional

frc2614 82
frc4611 69
frc325 66

Hueneme Port Regional

frc498 81
frc4481 71
frc3647 63

Lake Superior Regional

frc1732 73
frc2052 71
frc8298 52

Northern Lights Regional

frc6147 68
frc4230 65
frc6574 63

Regional Monterrey presented by PrepaTec

frc4400 82
frc7421 71
frc4403 70

Silicon Valley Regional

frc1678 112
frc581 68
frc604 58

Smoky Mountains Regional

frc7428 78
frc801 73
frc4020 72

South Florida Regional

frc108 77
frc179 73
frc180 72

Utah Regional

frc1410 64
frc1339 63
frc7426 61

Week 2 Events

Arkansas Regional

frc16 73
frc3937 72
frc2341 63

Bosphorus Regional

frc9483 69
frc8570 61
frc6459 60

Central Missouri Regional

frc1706 73
frc4522 72
frc4593 62

Great Northern Regional

frc3134 73
frc876 71
frc3276 68

Green Country Regional

frc1730 94
frc4499 80
frc6424 73

Hudson Valley Regional

frc353 73
frc1796 67
frc7748 58

San Francisco Regional

frc114 73
frc5940 72
frc1700 60

Ventura County Regional

frc4414 73
frc8 72
frc589 66

Ä°stanbul Regional

frc8613 73
frc6941 71
frc6014 63

Week 2 Regional Pool

frc6902 119.60000000000001
frc3478 114.80000000000001
frc1781 113.2
frc9602 111.60000000000001
frc2491 111.60000000000001
frc4635 111.60000000000001
frc6517 110.0
frc8033 110.0
frc2486 108.4
frc8806 107
frc3996 106.80000000000001
frc1987 106.80000000000001
frc9460 105.2
frc5528 105.2
frc2102 105.2
frc6036 103.60000000000001
frc1714 103.60000000000001
frc2129 103.60000000000001
frc1014 103.60000000000001
frc3245 103.60000000000001
frc8153 103.60000000000001
frc9576 103.60000000000001
frc2854 103.60000000000001
frc4421 102.0
frc5557 102.0
frc2530 102.0
frc4329 102.0
frc5817 102.0
frc2357 100.4
frc4646 100.4
frc1797 100.4
frc20 100.4
frc5439 98.80000000000001
frc2383 98.80000000000001
frc4944 98.80000000000001
frc3256 97.2
frc4265 97.2
frc3928 97.2
frc7632 97
frc144 95.60000000000001
frc3128 95.60000000000001
frc3293 95.60000000000001
frc2531 94.0
frc2823 94.0
Qualifying teams by week of last event (week, count) [(1, 27), (2, 17)]

Week 3 Events

Arizona Valley Regional

frc4146 65
frc9501 51
frc6413 50

Central Illinois Regional

frc2338 84
frc2481 73
frc1756 71

Finger Lakes Regional

frc694 73
frc1591 63
frc1468 57

Heartland Regional

frc3061 71
frc1986 56
frc1710 42

Los Angeles Regional

frc687 80
frc9408 71
frc368 68

Magnolia Regional

frc8044 73
frc2987 67
frc2992 65

Regional Hermosillo presented by PrepaTec

frc5705 78
frc6647 68
frc7102 67

Sacramento Regional

frc2073 75
frc254 71
frc5419 59

Southern Cross Regional

frc5985 89
frc8011 70
frc5584 59

Tallahassee Regional

frc5472 96
frc386 44
frc21 40

Wisconsin Regional

frc930 106
frc4786 78
frc8744 56

Week 4 Events

Arizona East Regional

frc7419 80
frc670 73
frc294 49

Buckeye Regional

frc4145 74
frc3015 73
frc1787 72

Central Valley Regional

frc840 81
frc1323 73
frc1671 67

Colorado Regional

frc3006 66
frc3729 52
frc1108 46

FIRST Long Island Regional

frc9016 68
frc6806 66
frc6423 54

Haliç Regional

frc6429 88
frc8214 73
frc9609 71

Idaho Regional

frc5461 82
frc2122 62
frc2813 59

Iowa Regional

frc4607 89
frc4143 66
frc2526 54

Marmara Regional

frc8054 100
frc7576 57
frc6989 47

New York Tech Valley Regional

frc3173 59
frc3419 57
frc5993 55

Orlando Regional

frc59 73
frc2080 50
frc8841 50

Regional Laguna presented by Peñoles

frc3354 64
frc9120 59
frc6170 49

San Diego Regional presented by Qualcomm

frc3341 70
frc2485 61
frc4738 49

St. Louis Regional

frc3284 65
frc1288 44
frc2783 39

Week 4 Regional Pool

frc2826 114.80000000000001
frc9692 107
frc8159 105
frc6017 102
frc379 100
frc7525 99
frc4336 98
frc34 97.2
frc9535 95.60000000000001
frc5507 94.0
frc4253 93
frc340 93
frc3473 93
frc6995 93
frc111 92.4
frc6381 91
frc2584 91
frc6907 90.80000000000001
frc6694 90.80000000000001
frc9175 89.2
frc6743 89.2
frc5851 89.2
frc5914 87.60000000000001
frc5172 87.60000000000001
frc5458 87.60000000000001
frc9624 87
frc6985 87
frc9199 86.0
frc7536 86.0
frc7042 86.0
frc1912 86.0
frc2194 86.0
frc3501 85
frc287 85
frc8882 84.4
frc9406 84.4
frc6045 84.4
frc3522 84.4
frc3277 84.4
frc7094 84.4
frc4135 84.4
frc4788 84.4
frc9694 84.4
frc5232 83
frc2791 83
Qualifying teams by week of last event (week, count) [(1, 9), (2, 4), (3, 19), (4, 13)]

Week 5 Events

Las Vegas Regional

frc987 72
frc2403 59
frc8717 49

Midwest Regional

frc5934 48
frc5822 47
frc4655 45

Monterey Bay Regional

frc649 65
frc971 57
frc6962 53

Orange County Regional

frc3476 91
frc7157 71
frc5199 56

Week 6 Events

Aerospace Valley Regional

frc2429 76
frc2659 70
frc1148 63

Bayou Regional

frc456 61
frc2183 54
frc4065 49

East Bay Regional

frc2637 56
frc6619 44
frc841 41

Greater Kansas City Regional

frc4766 69
frc5809 62
frc1764 56

Hawaii Regional

frc2438 56
frc2443 55
frc2465 46

Miami Valley Regional

frc4028 71
frc8393 65
frc5667 58

Minnesota 10,000 Lakes Regional

frc2472 70
frc3926 58
frc3630 52

Minnesota Granite City Regional

frc7028 72
frc2883 68
frc8188 44

New York City Regional

frc2869 92
frc4122 73
frc3950 67

Oklahoma Regional

frc9401 65
frc1561 61
frc935 54

Rocket City Regional

frc2638 60
frc6652 58
frc5002 40

Seven Rivers Regional

frc7021 68
frc525 63
frc8802 55

Week 6 Regional Pool

frc2373 89.2
frc2718 86
frc1155 85
frc5700 85
frc4539 84
frc5811 84
frc8056 82.8
frc8780 82.8
frc100 82
frc4952 81.2
frc6838 81
frc9432 81
frc1538 81
frc7522 80
frc8581 80
frc9604 79.60000000000001
frc9429 79.60000000000001
frc6436 79.60000000000001
frc8814 79.60000000000001
frc4091 79.60000000000001
frc3480 79.60000000000001
frc6873 79.60000000000001
frc5736 79
frc6702 79
frc973 78
frc4630 78.0
frc6560 78
frc1884 78.0
frc9084 78
frc2143 78.0
frc5690 77
frc4107 77
frc7257 77
frc424 77
frc9134 77
frc1880 77
frc9200 76.4
frc329 76.4
frc5434 76.4
frc3360 75
frc9452 75
frc5013 75
frc3749 75
frc2905 75
frc9597 75
Qualifying teams by week of last event (week, count) [(1, 4), (2, 5), (3, 1), (4, 15), (5, 4), (6, 16)]

Thresholds

Week 1

Event thresholds DescribeResult(nobs=12, minmax=(52, 72), mean=64.66666666666667, variance=38.06060606060606, skewness=-0.4265743757923236, kurtosis=-0.4388514747048555)

Week 2

Event thresholds DescribeResult(nobs=9, minmax=(58, 73), mean=63.666666666666664, variance=21.749999999999993, skewness=0.8121234213971719, kurtosis=-0.21422909235037313)

Week 3

Event thresholds DescribeResult(nobs=11, minmax=(40, 71), mean=57.63636363636363, variance=104.85454545454549, skewness=-0.4916934646155355, kurtosis=-0.8589617613822544)

Week 4

Event thresholds DescribeResult(nobs=14, minmax=(39, 72), mean=54.357142857142854, variance=95.0164835164835, skewness=0.609778338111961, kurtosis=-0.5603005490937321)

Week 5

Event thresholds DescribeResult(nobs=4, minmax=(45, 56), mean=50.75, variance=22.916666666666664, skewness=-0.13814701606108443, kurtosis=-1.4023669421487603)

Week 6

Event thresholds DescribeResult(nobs=12, minmax=(40, 67), mean=52.083333333333336, variance=71.3560606060606, skewness=0.1592227111036165, kurtosis=-0.9082021314810489)
Pool thresholds [94.0, 83, 75]

Code
import requests
import math
import scipy.stats
from collections import Counter

TOTAL_TEAMS = 3474

HEADERS = {"X-TBA-Auth-Key": ""}

ROOT_URL = "https://www.thebluealliance.com/api/v3"

CAPACITY = 600

IMPACT_AWARD_KEY = 0
EI_KEY = 9

REGIONALS = 62

qualified_teams: set[str] = set()

events: list = requests.get(ROOT_URL + "/events/2024", headers=HEADERS).json()

regional_pool: dict[str, tuple[int] | tuple[int, int]] = {}

for team_count in {127, 127, 98, 514, 63, 190, 185, 107, 77, 138, 61}:
    CAPACITY -= math.ceil(team_count / TOTAL_TEAMS * 600)

prequalifying_teams = [ f"frc{n}" for n in 
    [ # https://www.firstinspires.org/resource-library/frc/past-winners-of-the-first-impact-award
    321, 1629, 503, 4613, 1816, 1902, 1311, 2834, 
    ] ]

CAPACITY -= len(prequalifying_teams)

REGIONAL_POOL_SIZE = CAPACITY - REGIONALS * 3

district_teams: set[str] = set()

for district in {"fma", "pnw", "pch", "fim", "fin", "fit", "ne", "chs", "fnc", "ont", "isr"}:
    for team in requests.get(ROOT_URL + f"/district/2024{district}/teams", headers=HEADERS).json():
        district_teams.add(team["key"])

def regional_pool_points(events: tuple[int] | tuple[int, int]) -> float:
    if len(events) == 2:
        return events[0] + events[1]
    return events[0] * 1.6 + 14

event_thresholds_by_week = []
pool_thresholds = []
team_last_week: dict[str, int] = {}

for week in range(1, 7):
    event_thresholds = []
    print("## Week", week, "Events")
    for event in sorted(filter(lambda event: event["week"] == week - 1 and event["district"] is None, events), key=lambda event: event["name"]):
        print("### ", event["name"])
        this_event_teams: dict[str, int] = {}
        event_key = event["key"]
        district_points = requests.get(ROOT_URL + f"/event/{event_key}/district_points", headers=HEADERS).json()
        awards = requests.get(ROOT_URL + f"/event/{event_key}/awards", headers=HEADERS).json()
        for team_key, points in district_points["points"].items():
            if team_key in district_teams:
                continue
            if team_key in qualified_teams:
                continue
            if team_key in prequalifying_teams:
                continue
            team_last_week[team_key] = week
            points = points["total"]
            if any(award["award_type"] == IMPACT_AWARD_KEY and award["recipient_list"][0]["team_key"] == team_key for award in awards):
                points += 45 - 10
            if any(award["award_type"] == EI_KEY and award["recipient_list"][0]["team_key"] == team_key for award in awards):
                points += 28 - 8
            this_event_teams[team_key] = points
            if team_key not in regional_pool:
                regional_pool[team_key] = (points,)
            elif len(regional_pool[team_key]) == 1:
                regional_pool[team_key] = (regional_pool[team_key][0], points)
        event_threshold = None
        for team_key in list(sorted(this_event_teams.keys(), key=lambda key: this_event_teams[key], reverse=True))[:3]:
            qualified_teams.add(team_key)
            if team_key in regional_pool:
                del regional_pool[team_key]
            print(team_key, this_event_teams[team_key])
            event_threshold = this_event_teams[team_key]
        event_thresholds.append(event_threshold)
    if week % 2 == 0:
        print("### Week", week, "Regional Pool")
        TO_QUALIFY = 0
        if week == 2:
            TO_QUALIFY = REGIONAL_POOL_SIZE // 3
        elif week == 4:
            TO_QUALIFY = REGIONAL_POOL_SIZE // 2
        else:
            TO_QUALIFY = REGIONAL_POOL_SIZE
        REGIONAL_POOL_SIZE -= TO_QUALIFY
        pool_threshold = 0
        c = Counter()
        for team_key in list(sorted(regional_pool.keys(), key=lambda key: regional_pool_points(regional_pool[key]), reverse=True))[:TO_QUALIFY]:
            qualified_teams.add(team_key)
            points = regional_pool_points(regional_pool[team_key])
            print(team_key, points)
            c[team_last_week[team_key]] += 1
            pool_threshold = points
            del regional_pool[team_key]
        pool_thresholds.append(pool_threshold)
        print("Qualifying teams by week of last event (week, count)", sorted(c.most_common()))
    event_thresholds_by_week.append(event_thresholds)

print("## Thresholds")
for i, event_thresholds in enumerate(event_thresholds_by_week):
    print("### Week", i + 1)
    print("Event thresholds", scipy.stats.describe(event_thresholds))
print("Pool thresholds", pool_thresholds)

​​ ​​
3 Likes

Ignoring individual events for now because there is a wide range there it looks like the lowest point total pulled was 75 (with tie breakers)

So 76 points (Slightly above the middle of the pack ranking wise, first picked by the 4th seed alliance, 4th place at both events) will get you there alongside 1 award at one of your events,

That is pretty decent odds for teams who used to have 0 chance.

9 Likes

Ok these results make me feel better that there’s not any advantage to registering for early events. If anything, it’s actually in some cases the inverse. Thanks for sharing!

7 Likes

Here’s a violin plot showing the distribution of points in the regional events for each week and the fortnightly pool pulls. Remember that pool scores are “doubled”.

4 Likes

This bar chart shows, for teams competing in a specific week, which pulls they qualify in.

2 Likes

Cool now I get to spend the rest of my life saying what if the system was implemented a year earlier if we dont make worlds in 2025 :upside_down_face::melting_face:

2 Likes

Fantastic insight by the code-slinging pioneers in this topic!

I wanted to see qualification rates by week of first event. I extended the code from @bovlb to add this functionality.

Code
import requests
import math
import scipy.stats
from collections import Counter

TOTAL_TEAMS = 3474

HEADERS = {"X-TBA-Auth-Key": ""}

ROOT_URL = "https://www.thebluealliance.com/api/v3"

CAPACITY = 600

IMPACT_AWARD_KEY = 0
EI_KEY = 9

REGIONALS = 62

qualified_teams: set[str] = set()

events: list = requests.get(ROOT_URL + "/events/2024", headers=HEADERS).json()

regional_pool: dict[str, tuple[int] | tuple[int, int]] = {}

for team_count in {127, 127, 98, 514, 63, 190, 185, 107, 77, 138, 61}:
    CAPACITY -= math.ceil(team_count / TOTAL_TEAMS * 600)

prequalifying_teams = [ f"frc{n}" for n in 
    [ # https://www.firstinspires.org/resource-library/frc/past-winners-of-the-first-impact-award
    321, 1629, 503, 4613, 1816, 1902, 1311, 2834, 
    ] ]

CAPACITY -= len(prequalifying_teams)

REGIONAL_POOL_SIZE = CAPACITY - REGIONALS * 3

district_teams: set[str] = set()

for district in {"fma", "pnw", "pch", "fim", "fin", "fit", "ne", "chs", "fnc", "ont", "isr"}:
    for team in requests.get(ROOT_URL + f"/district/2024{district}/teams", headers=HEADERS).json():
        district_teams.add(team["key"])

def regional_pool_points(events: tuple[int] | tuple[int, int]) -> float:
    if len(events) == 2:
        return events[0] + events[1]
    return events[0] * 1.6 + 14

event_thresholds_by_week = []
pool_thresholds = []
team_last_week: dict[str, int] = {}
team_first_week: dict[str, int] = {}
plays = Counter()

for week in range(1, 7):
    event_thresholds = []
    print("## Week", week, "Events")
    for event in sorted(filter(lambda event: event["week"] == week - 1 and event["district"] is None, events), key=lambda event: event["name"]):
        print("### ", event["name"])
        this_event_teams: dict[str, int] = {}
        event_key = event["key"]
        district_points = requests.get(ROOT_URL + f"/event/{event_key}/district_points", headers=HEADERS).json()
        awards = requests.get(ROOT_URL + f"/event/{event_key}/awards", headers=HEADERS).json()
        for team_key, points in district_points["points"].items():
            plays.update([team_key])
            if team_key in district_teams:
                continue
            if team_key in qualified_teams:
                continue
            if team_key in prequalifying_teams:
                continue
            team_last_week[team_key] = week
            if team_key not in team_first_week.keys():
              team_first_week[team_key] = week
            points = points["total"]
            if any(award["award_type"] == IMPACT_AWARD_KEY and award["recipient_list"][0]["team_key"] == team_key for award in awards):
                points += 45 - 10
            if any(award["award_type"] == EI_KEY and award["recipient_list"][0]["team_key"] == team_key for award in awards):
                points += 28 - 8
            this_event_teams[team_key] = points
            if team_key not in regional_pool:
                regional_pool[team_key] = (points,)
            elif len(regional_pool[team_key]) == 1:
                regional_pool[team_key] = (regional_pool[team_key][0], points)
        event_threshold = None
        for team_key in list(sorted(this_event_teams.keys(), key=lambda key: this_event_teams[key], reverse=True))[:3]:
            qualified_teams.add(team_key)
            if team_key in regional_pool:
                del regional_pool[team_key]
            print(team_key, this_event_teams[team_key])
            event_threshold = this_event_teams[team_key]
        event_thresholds.append(event_threshold)
    if week % 2 == 0:
        print("### Week", week, "Regional Pool")
        TO_QUALIFY = 0
        if week == 2:
            TO_QUALIFY = REGIONAL_POOL_SIZE // 3
        elif week == 4:
            TO_QUALIFY = REGIONAL_POOL_SIZE // 2
        else:
            TO_QUALIFY = REGIONAL_POOL_SIZE
        REGIONAL_POOL_SIZE -= TO_QUALIFY
        pool_threshold = 0
        c = Counter()
        for team_key in list(sorted(regional_pool.keys(), key=lambda key: regional_pool_points(regional_pool[key]), reverse=True))[:TO_QUALIFY]:
            qualified_teams.add(team_key)
            points = regional_pool_points(regional_pool[team_key])
            print(team_key, points)
            c[team_last_week[team_key]] += 1
            pool_threshold = points
            del regional_pool[team_key]
        pool_thresholds.append(pool_threshold)
        print("Qualifying teams by week of last event (week, count)", sorted(c.most_common()))
    event_thresholds_by_week.append(event_thresholds)

print("## Thresholds")
for i, event_thresholds in enumerate(event_thresholds_by_week):
    print("### Week", i + 1)
    print("Event thresholds", scipy.stats.describe(event_thresholds))
print("Pool thresholds", pool_thresholds)

team_by_first_week = Counter(team_first_week.values())
qual_first_week = {k:v for (k,v) in team_first_week.items() if k in qualified_teams}
qual_by_first_week = Counter(qual_first_week.values())
qual_frac_by_first_week = {k: qual_by_first_week[k]/team_by_first_week[k] for k in qual_by_first_week}
plays_multi = {k:v for (k,v) in plays.items() if v > 1}
team_first_week_multi = {k:v for (k,v) in team_first_week.items() if k in plays_multi}
team_by_first_week_multi = Counter(team_first_week_multi.values())
qual_first_week_multi = {k:v for (k,v) in qual_first_week.items() if k in plays_multi}
qual_by_first_week_multi = Counter(qual_first_week_multi.values())
qual_frac_by_first_week_multi = {k: qual_by_first_week_multi[k]/team_by_first_week_multi[k] for k in qual_by_first_week_multi}

print("Percent qualified by week of first event")
for week, frac in qual_frac_by_first_week.items():
  print("Week:", week, " Percent Qualified:", round(frac*100, 1))

print("Percent qualified by week of first event - multiple event participation")
for week, frac in qual_frac_by_first_week_multi.items():
  print("Week:", week, " Percent Qualified:", round(frac*100, 1))

The new part of the output is:

Percent qualified by week of first event
Week: 1  Percent Qualified: 23.6
Week: 2  Percent Qualified: 26.2
Week: 3  Percent Qualified: 16.6
Week: 4  Percent Qualified: 9.7
Week: 5  Percent Qualified: 1.9
Week: 6  Percent Qualified: 3.1

Percent qualified by week of first event - multiple event participation
Week: 1  Percent Qualified: 32.2
Week: 2  Percent Qualified: 31.1
Week: 3  Percent Qualified: 22.8
Week: 4  Percent Qualified: 27.2

With all regional teams considered, there is a substantial dropoff in qualification rates as the week of the first event increases. I expected that a large part of that relationship was being driven by single-event teams that did not play until the later weeks. The second table shows the rates only for teams that played two or more regionals. The rates get closer, which is great to see, but the early-play teams still qualify at higher rates. There are almost certainly other confounding factors lurking in these results. For example, there is a possibility that stronger teams participate at higher rates in early events because they believe they will be ready to play.

I did not put graphing in the code attached to this post, but graphs of the counts of qualifying and non-qualifying teams are below.

image

image

If I’ve done something in error in this analysis, I’d be happy to fix it and edit this post.

Anyone want to sort/break up the list by state (or even proposed districts) for lazy people like me :grinning:

4 Likes

What the champs EPA graph would look like in the current format vs. the 2026 format?

3 Likes

Does this code include giving the team age bonuses for 2023 and 2024 rookies? I might just not be seeing it.

This code uses the TBA “district points” implementation and then tweaks it for the two differences (points for EI and FIA). As some point TBA will presumably implement a “regional points” method for us.

3 Likes

Participating in greater numbers of events makes teams stronger.

Separately…

Thank you for running this analysis folks, it helped me realize that I definitely do not understand how 3rd+ events work yet. Surprised to see we would have qualified at EBR this year on a 4th place finish.