cwendt94 / espn-api

ESPN Fantasy API! (Football, Basketball)
MIT License
544 stars 183 forks source link

Historical player injuries #401

Closed DesiPilla closed 7 months ago

DesiPilla commented 1 year ago

Sport

Football

Summary

Okay so for a while now, an issue we've had is that ESPN only return's a player's current health status, even when querying a previous week or year. I think I have a way to get around this and assume a player's health status from previous weeks.

Example REST call: https://fantasy.espn.com/apis/v3/games/ffl/seasons/2022/segments/0/leagues/1086064?view=mMatchupScore&view=mScoreboard&scoringPeriodId=6 (I know this isn't that far back, but it still works).

LAC WR Keenan Allen was OUT due to injury. His player json looks like:

{
    "injuryStatus": "NORMAL",
    "lineupSlotId": 20,
    "playerId": 15818,
    "playerPoolEntry": {
    "appliedStatTotal": 0.0,
    "id": 15818,
    "onTeamId": 1,
    "player": {
        "active": true,
        "defaultPositionId": 3,
        "eligibleSlots": [3, 4, 5, 23, 7, 20, 21],
        "firstName": "Keenan",
        "fullName": "Keenan Allen",
        "id": 15818,
        "injured": false,
        "injuryStatus": "QUESTIONABLE",
        "jersey": "13",
        "lastName": "Allen",
        "proTeamId": 24,
        "stats": [
        {
            "appliedStats": {},
            "appliedTotal": 0.0,
            "appliedTotalCeiling": 0.0,
            "externalId": "20226",
            "id": "1120226",
            "lastUpdateInfo": {},
            "proTeamId": 0,
            "scoringPeriodId": 6,
            "seasonId": 2022,
            "statSourceId": 1,
            "statSplitTypeId": 1,
            "stats": {},
            "variance": {
            "23": 0.572518801,
            "24": 5.888548208,
            "25": 0.016224972,
            "26": 0.187113084,
            "35": 0.07,
            "36": 0.06,
            "42": 36.81194703,
            "43": 0.565194165,
            "44": 0.279276063,
            "45": 0.156779565,
            "46": 0.18,
            "53": 3.011551833,
            "58": 3.509159092,
            "63": 0.001,
            "68": 0.339298477,
            "72": 0.190029238
            }
        },
        {
            "appliedStats": {},
            "appliedTotal": 0.0,
            "externalId": "401437790",
            "id": "01401437790",
            "lastUpdateInfo": {},
            "proTeamId": 24,
            "scoringPeriodId": 6,
            "seasonId": 2022,
            "statSourceId": 0,
            "statSplitTypeId": 1,
            "stats": {},
            "variance": {}
        }
        ],
        "universeId": 1
    },
    "status": "ONTEAM"
    },
    "status": "NORMAL"
},

As we see, "injured": false and "injuryStatus": "QUESTIONABLE" are not reflective of his actual out status in Week 6. However, resp["player"]["stats"][1]["stats"] = {} is an empty dictionary. I believe that this indicates that the player was OUT that week. Note: We must look at the stat dictionary with "statSourceId": 1. I think this refers to single-season stats, vs season-long stats.

For example, here's the json for NYJ WR Elijah Moore from that week.

{
    "injuryStatus": "NORMAL",
    "lineupSlotId": 20,
    "playerId": 4372414,
    "playerPoolEntry": {
    "appliedStatTotal": 0.0,
    "id": 4372414,
    "onTeamId": 4,
    "player": {
        "active": true,
        "defaultPositionId": 3,
        "eligibleSlots": [3, 4, 5, 23, 7, 20, 21],
        "firstName": "Elijah",
        "fullName": "Elijah Moore",
        "id": 4372414,
        "injured": false,
        "injuryStatus": "ACTIVE",
        "jersey": "8",
        "lastName": "Moore",
        "proTeamId": 20,
        "stats": [
        {
            "appliedStats": {},
            "appliedTotal": 0.0,
            "externalId": "401437780",
            "id": "01401437780",
            "lastUpdateInfo": {},
            "proTeamId": 20,
            "scoringPeriodId": 6,
            "seasonId": 2022,
            "statSourceId": 0,
            "statSplitTypeId": 1,
            "stats": { "155": 1.0, "210": 1.0 },
            "variance": {}
        },
        {
            "appliedStats": {
            "53": 1.8088437515,
            "72": -0.068263156,
            "24": 0.3093177588,
            "25": 0.097625838,
            "26": 0.001402,
            "42": 5.059137268000001,
            "43": 1.4621709059999999,
            "44": 0.021829334,
            "63": 0.001644
            },
            "appliedTotal": 8.6937077003,
            "appliedTotalCeiling": 19.296405354,
            "externalId": "20226",
            "id": "1120226",
            "lastUpdateInfo": {},
            "proTeamId": 0,
            "scoringPeriodId": 6,
            "seasonId": 2022,
            "statSourceId": 1,
            "statSplitTypeId": 1,
            "stats": {
            "23": 0.500451604,
            "24": 3.093177588,
            "25": 0.016270973,
            "26": 7.01e-4,
            "35": 9.22e-4,
            "36": 6.45e-4,
            "37": 3.97e-4,
            "38": 1.35e-5,
            "39": 6.180772654,
            "40": 3.093177588,
            "42": 50.59137268,
            "43": 0.243695151,
            "44": 0.010914667,
            "45": 0.011145444,
            "46": 0.007283547,
            "47": 10.0,
            "48": 5.0,
            "49": 2.0,
            "50": 2.0,
            "51": 1.0,
            "53": 3.617687503,
            "56": 0.099440855,
            "57": 0.00308,
            "58": 6.293878265,
            "60": 13.98445074,
            "61": 50.59137268,
            "62": 0.011615355,
            "63": 2.74e-4,
            "66": 0.00757,
            "67": 0.054730147,
            "68": 0.062301224,
            "70": 0.00348,
            "71": 0.030648882,
            "72": 0.034131578,
            "73": 0.034131578,
            "210": 1.0
            },
            "variance": {
            "23": 0.512347538,
            "24": 6.407027392,
            "25": 0.25,
            "26": 0.187113084,
            "35": 0.07,
            "36": 0.06,
            "42": 35.93831521,
            "43": 0.602079729,
            "44": 0.279276063,
            "45": 0.22,
            "46": 0.18,
            "53": 2.301267767,
            "58": 2.633122354,
            "63": 0.001,
            "68": 0.037512689,
            "72": 0.416863579
            }
        }
        ],
        "universeId": 1
    },
    "status": "ONTEAM"
    },
    "status": "NORMAL"
}

Moore played, but did not record a single stat. Yet, despite not registering any stats, his json shows resp["player"]["stats"][0]["stats"] = "stats": { "155": 1.0, "210": 1.0 }. For reference, statId 155 refers to "Team win" and 210 refers to "Games played". Because Moore was active for the game, these two stats appear in his json.

So, this would indicate that if a player's resp["player"]["stats"][0]["stats"] is an empty dictionary, then we can safely assume that the player was OUT that week. However, there are two other things to consider:

Here's the json for TEN RB Derrick Henry

{
    "injuryStatus": "NORMAL",
    "lineupSlotId": 20,
    "playerId": 3043078,
    "playerPoolEntry": {
    "appliedStatTotal": 0.0,
    "id": 3043078,
    "onTeamId": 8,
    "player": {
        "active": true,
        "defaultPositionId": 2,
        "eligibleSlots": [2, 3, 23, 7, 20, 21],
        "firstName": "Derrick",
        "fullName": "Derrick Henry",
        "id": 3043078,
        "injured": false,
        "injuryStatus": "ACTIVE",
        "jersey": "22",
        "lastName": "Henry",
        "proTeamId": 10,
        "stats": [
        {
            "appliedStats": {},
            "appliedTotal": 0.0,
            "appliedTotalCeiling": 0.0,
            "externalId": "20226",
            "id": "1120226",
            "lastUpdateInfo": {},
            "proTeamId": 0,
            "scoringPeriodId": 6,
            "seasonId": 2022,
            "statSourceId": 1,
            "statSplitTypeId": 1,
            "stats": {},
            "variance": {
            "23": 7.989211797,
            "24": 58.70602937,
            "25": 0.960427714,
            "26": 0.187113084,
            "35": 0.349889526,
            "36": 0.349889526,
            "42": 16.03528837,
            "43": 0.18778446,
            "44": 0.279276063,
            "45": 0.167801519,
            "46": 0.167801519,
            "53": 1.198000097,
            "58": 1.384393089,
            "63": 0.001,
            "68": 0.397612623,
            "72": 0.260540364
            }
        }
        ],
        "universeId": 1
    },
    "status": "ONTEAM"
    },
    "status": "NORMAL"
},

The Titans were on a bye during Week 6, so Henry did not play. His json also has "injuryStatus": "ACTIVE". However, recall that resp["player"]["stats"] is a list with 2 elements in it: "statSourceId": 0 is a stats dictionary containing single-week stats for the current week, and "statSourceId": 1 is a stats dictionary containing season-long stats. In Henry's json, len(resp["player"]["stats"]) = 1. He only has one stats dictionary, the one with "statSourceId": 1. This is because he did not play this week, and was not supposed to play. This is also the case for other players on bye, which to me makes it a reliable indicator that the player was on a bye and not injured.

Here's the json for ARI WR DeAndre Hopkins from Week 6

{
    "injuryStatus": "NORMAL",
    "lineupSlotId": 20,
    "playerId": 15795,
    "playerPoolEntry": {
    "appliedStatTotal": 0.0,
    "id": 15795,
    "onTeamId": 10,
    "player": {
        "active": true,
        "defaultPositionId": 3,
        "eligibleSlots": [3, 4, 5, 23, 7, 20, 21],
        "firstName": "DeAndre",
        "fullName": "DeAndre Hopkins",
        "id": 15795,
        "injured": false,
        "injuryStatus": "ACTIVE",
        "jersey": "10",
        "lastName": "Hopkins",
        "proTeamId": 22,
        "stats": [
        {
            "appliedStats": {},
            "appliedTotal": 0.0,
            "externalId": "401437787",
            "id": "01401437787",
            "lastUpdateInfo": {},
            "proTeamId": 22,
            "scoringPeriodId": 6,
            "seasonId": 2022,
            "statSourceId": 0,
            "statSplitTypeId": 1,
            "stats": {},
            "variance": {}
        },
        {
            "appliedStats": {},
            "appliedTotal": 0.0,
            "appliedTotalCeiling": 0.0,
            "externalId": "20226",
            "id": "1120226",
            "lastUpdateInfo": {},
            "proTeamId": 0,
            "scoringPeriodId": 6,
            "seasonId": 2022,
            "statSourceId": 1,
            "statSplitTypeId": 1,
            "stats": {},
            "variance": {
            "23": 0.226210458,
            "24": 1.755608773,
            "25": 0.016224972,
            "26": 0.187113084,
            "35": 0.07,
            "36": 0.06,
            "42": 41.18427934,
            "43": 0.716598572,
            "44": 0.279276063,
            "45": 0.244508154,
            "46": 0.16604118,
            "53": 2.526829014,
            "58": 3.328825781,
            "63": 0.001,
            "68": 0.310767721,
            "72": 0.27312011
            }
        }
        ],
        "universeId": 2
    },
    "status": "ONTEAM"
    },
    "status": "NORMAL"
}

First, let's note that Hopkins' json shows"injured": false and "injuryStatus": "ACTIVE" when it should show "injured":false and "injuryStatus":"SUSPENSION". This is because, today in Week 10, he is no longer suspended. In Week 6, though, Hopkins was still serving his 6-game suspension. BUT, we can see that resp["player"]["stats"][0]["stats"] = {}, which we previously decided safely meant that a player was OUT. This is the edge case. If the player is still suspended today, it becomes an easy fix (just check for "injuryStatus":"SUSPENSION"). But in a case like this, I don't know how to pull this data simply from the API.

Perhaps, a player being suspended is comparable to being OUT. It's not due to injury, but it's distinct from a bye. Players on suspension function more like those that are injured, since teams will stash them waiting until they come back from suspension. And when they do, you don't know how long it will take before they make meaningful contributions to the team (like injured players). Players returning from a bye don't really exhibit this behavior.

In conclusion

When looking at a player's json, if resp["player"]["stats"][0]["stats"] = {} (make sure it's the one with "statSourceId": 1, AND len(resp["player"]["stats"]) == 2, the player WAS OUT (or suspended).

Does this sound right?

cwendt94 commented 1 year ago

Wow thanks for doing all of this research and finding a way to figure out if the player played the game or not! I will look into this and try to add this logic for "injury" and active field for the box_player class!