Real-Time Sports Scores w/ TeamTracker and TeamTracker-Card (Beta)

Oh, that is really odd. The Blue Jays and Yankees seem to have the old order. The others have a new order.

Exactly, the issue you are having is that for teams that have Clinched (or are Eliminated) from the Playoffs, ESPN adds this:

      - name: clincher
        displayName: Clincher
        shortDisplayName: CLINCH
        description: Clinched Playoff Berth
        abbreviation: CLINCH
        type: clincher
        value: 3
        displayValue: z

So you could add a new column after the first hidden column like this:

        - name: C
          data: entries
          modify: >-
            if(typeof x.stats.find(y=>y.abbreviation == 'CLINCH') !==
            'undefined' ){x.stats.find(y=>y.abbreviation ==
            'CLINCH').displayValue}else{'-'}

NOTE: you must use the if here because if the CLICH field does not exists yet (as it doesn’t for teams who haven’t or are eliminated), it will be an error.

I follow NHL mostly and NFL, so I had not done all the updates on MLB or NBA including some form of Playoff. For your use, I will post here code for NHL which you can use to study.

Decluttering Template:

  nhl_settings:
    card:
      type: custom:flex-table-card
      title: '[[title]]'
      css:
        table+: 'padding: 0px; width: 1600px;'
        tbody tr td:first-child: 'width: 2%;'
        tbody tr td:nth-child(2): 'width: 20%;'
        tbody tr td:nth-child(n+3): 'width: 5%;'
        tbody tr:hover: 'background-color: green!important; color:white!important;'
        tbody tr td:nth-child(7): 'background-color: green; color: white;'
      card_mod:
        style:
          .: |
            ha-card {
              overflow: auto;
              }
          $: |
            .card-header {
               padding-top: 6px!important;
               padding-bottom: 4px!important;
               font-size: 14px!important;
               line-height: 14px!important;
               font-weight: bold!important;
             }
      entities:
        include: '[[entity]]'
        exclude: '[[excluded_entities]]'
      sort_by: entries-
      columns:
        - hidden: true
          data: '[[attribute]]'
          modify: '[[sort]]'
        - name: >-
            <div title="Clinch Indicator:&#10; * : Presidents Trophy&#10; x :
            Playoff Berth&#10; - : In The Hunt&#10; e : Eliminated">C</div>
          data: '[[attribute]]'
          modify: >-
            if(typeof x.stats.find(y=>y.abbreviation == 'CLINCH') !==
            'undefined' ){x.stats.find(y=>y.abbreviation ==
            'CLINCH').displayValue}else{'-'}
        - name: Team
          data: '[[attribute]]'
          modify: >-
            '<div><a href="' + x.team.links[0].href + '" target="_blank"><img
            src="' + x.team.logos[0].href + '" style="height:
            20px;vertical-align:middle;"></a>&nbsp;' + x.team.displayName +
            '</div>'
        - name: <div title="Games Played">GP</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'GP').displayValue
        - name: <div title="Wins">W</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'W').displayValue
        - name: <div title="Losses">L</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'L').displayValue
        - name: <div title="Overtime/Shootout Losses">OTL</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'OTL').displayValue
        - name: <div title="Points">PTS</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'PTS').displayValue
        - name: <div title="Regulation Wins">RW</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'RW').displayValue
        - name: <div title="Regulation and Overtime Wins">ROW</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'ROW').displayValue
        - name: <div title="Shootout Wins">SOW</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'SOW').displayValue
        - name: <div title="Shootout Losses">SOL</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'SOL').displayValue
        - name: <div title="Home Record">HOME</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'HOME').displayValue
        - name: <div title="Away Record">AWAY</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'AWAY').displayValue
        - name: <div title="Goals For">GF</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'GF').displayValue
        - name: <div title="Goals Against">GA</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'GA').displayValue
        - name: <div title="Game Average Goal Differential">DIFF</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'DIFF').displayValue
        - name: <div title="Last 10 Games">L10</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'L10').summary
        - name: <div title="Current Streak">STRK</div>
          data: '[[attribute]]'
          modify: x.stats.find(y=>y.abbreviation == 'STRK').displayValue

Using that template:

                                  - type: custom:decluttering-card
                                    template: nhl_settings
                                    variables:
                                      - title: Eastern Atlantic
                                      - entity: sensor.nhl_east_atlantic
                                      - attribute: entries
                                      - excluded_entities:
                                          - sensor.nhl_starting_goalies
                                          - sensor.nhl_wildcard
                                          - sensor.nhl_wildcard_standings
                                      - sort: >-
                                          x.stats.find(y=>y.shortDisplayName ==
                                          'PTS').value

You can see here a few things.

  1. the variables include the attribute you wish to use (in most cases this is entries).
  2. You can also pass in a set of excluded entities because in divisional and overall, the code would try to get sensor.nhl_*_* and if you had a sensor like sensor.nhl_starting_goalies it would grab that sensor and this would break the card.
  3. It also allows you to pass in the sort so you can decide how you wish to sort and use the same template.
  4. It redoes how the name of the field is displayed (in a
    ) so you can add a title which results in a tooltip if you mouse over it.

@jeffcrum … if you track it through it is probably sorted as specified. The key is the x.stats[16].value is a is a different column for those with “x” or “e” than it is for those without. Who knows but it is likely two different columns, one could be “WON” and and the other “LOSSES”

Yeah. I am working and didn’t have time to do that. Just noting an observation that I saw.

Here is a simple decluttering template without all the bells and whistles in the NHL one.

  mlb_settings:
    card:
      type: custom:flex-table-card
      title: '[[title]]'
      css:
        table+: 'padding: 0px; width: 1600px;'
        tbody tr td:first-child: 'width: 2%;'
        tbody tr td:nth-child(2): 'width: 20%;'
        tbody tr td:nth-child(n+3): 'width: 5%;'
        tbody tr:hover: 'background-color: green!important; color:white!important;'
        tbody tr td:nth-child(6): 'background-color: green; color: white;'
      card_mod:
        style:
          .: |
            ha-card {
              overflow: auto;
              }
          $: |
            .card-header {
               padding-top: 6px!important;
               padding-bottom: 4px!important;
               font-size: 14px!important;
               line-height: 14px!important;
               font-weight: bold!important;
             }
      entities:
        include: '[[entity]]'
      sort_by: entries-
      columns:
        - hidden: true
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'PCT').value
        - name: C
          data: entries
          modify: >-
            if(typeof x.stats.find(y=>y.abbreviation == 'CLINCH') !==
            'undefined' ){x.stats.find(y=>y.abbreviation ==
            'CLINCH').displayValue}else{'-'}
        - name: Team
          data: entries
          modify: >-
            '<div><img src="' + x.team.logos[0].href + '" style="height:
            20px;vertical-align:middle;">&nbsp;' + x.team.displayName + '</div>'
        - name: GP
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'GP').displayValue
        - name: W
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'W').displayValue
        - name: L
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'L').displayValue
        - name: PCT
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'PCT').value.toFixed(2)
        - name: GB
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'GB').displayValue
        - name: HOME
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'Home').displayValue
        - name: AWAY
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'AWAY').displayValue
        - name: RS
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'RS').displayValue
        - name: RA
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'RA').displayValue
        - name: DIFF
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'DIFF').displayValue
        - name: L10
          data: entries
          modify: x.stats.find(y=>y.shortDisplayName == 'L10').displayValue
        - name: STRK
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'STRK').displayValue

For those interested in seeing what is going on, you can paste this into Developer Tools Template.

{%- for team in states.sensor.mlb_american_west.attributes.entries %}

{{ team.team.displayName }}
{%- set ns = namespace(count=0) -%}
{%- for stat in team.stats %}
{{ ns.count }} : {{ stat.abbreviation }} : {{ stat.displayName }} : {{ stat.displayValue }}
 x.stats.find(y=>y.abbreviation == '{{ stat.abbreviation }}').displayValue
  {%- set ns.count = ns.count + 1 -%}
{%- endfor -%}
{%- endfor -%}

You can see that both the Angels and Athletics have a CLINCH field defined. But, the other three do not. So, everything after number 4 is shifted one.

This shows the number, abbreviation, display name, and display value for each stat. It also builds the line using the abbreviation that can go in the modify statements.

This clearly shows why it makes sense to use the abbreviation instead of numbers.

LOVE this integration - i have a new problem, too many active game cards concurrently.

Is there a way to use the size of the PRE or POST cards for an “active” game so it’s smaller with less information and shows just teams, inning, score, time, etc?

WHat way are you laying out the card in the view? You need to tell us.
If you are using my integration it is using masonry layout.

Apologies for my lack of specificity. I’m using the team tracker card and looking for a more compact version of the team tracker card when the game is in progress. A compact version that is the same size as pre or post would be great!

I would think everything should scale. Like putting it into a custom:layout-card with 4 columns or maybe even 5

Thanks for the thought, unfortunately the current “in game” card has too much data to scale into a “compact” form, see example below:

My preference is to have a “minimal in game” card based on the current “in game” card that cuts off everything after the inning. this would give me the minimum info on the event. same idea for basketball and football.

I tried to add a Pull Request but got denied as i’m not a contributor.

That is not displaying anything that you could not make in a separate card/set of cards. All the data is in the teamtracker sensor.

Like I use declutter and card_mod for color changes when in the red zone. Also have light scenes and TTS announcements on game start and TDs.

Totally understand I could do it myself. Putting the idea out there in case others could find value in the idea.

I do like it. Given that I built the other part without a “installed” sensor (Sports Standings and Scores) I could build out something.

Maybe something like this?

image

Not sure Hits, Errors are available as they are not in the regular card, score certainly, team certainly, logo certainly, inning yes.

Here is everything that should be available (no game in process yet):

attribution: Data provided by ESPN
sport: baseball
league: MLB
league_logo: https://a.espncdn.com/i/teamlogos/leagues/500/mlb.png
team_abbr: DET
opponent_abbr: KC
event_name: KC @ DET
date: 2023-09-28T19:30Z
kickoff_in: in 2 hours
venue: Comerica Park
location: Detroit, Michigan
tv_network: null
odds: DET -110
overunder: 7.5
team_name: Tigers
team_id: 6
team_record: 74-83
team_rank: null
team_homeaway: home
team_logo: https://a.espncdn.com/i/teamlogos/mlb/500/scoreboard/det.png
team_colors: #002d5c, #ff6600
team_score: 0
team_win_probability: null
team_winner: null
team_timeouts: null
opponent_name: Royals
opponent_id: 7
opponent_record: 54-103
opponent_rank: null
opponent_homeaway: away
opponent_logo: https://a.espncdn.com/i/teamlogos/mlb/500/scoreboard/kc.png
opponent_colors: #004687, #7ab2dd
opponent_score: 0
opponent_win_probability: null
opponent_winner: null
opponent_timeouts: null
quarter: 1
clock: 9/28 - 3:30 PM EDT
possession: null
last_play: null
down_distance_text: null
outs: null
balls: null
strikes: null
on_first: null
on_second: null
on_third: null
team_shots_on_target: null
team_total_shots: null
opponent_shots_on_target: null
opponent_total_shots: null
team_sets_won: null
opponent_sets_won: null
last_update: 2023-09-28 09:49:13-07:00
api_message: Cached data
icon: mdi:baseball
friendly_name: Detroit Tigers

Or what about just some simple card_mod on the existing card?

type: custom:teamtracker-card
entity: sensor.detroit_tigers
card_mod:
  style:
    .logo 
      {max-width:30px!important;}
    .card
      {width:60%}
    .name
      {font-size:1.2em;height:1em;}
    .gameday
      {font-size:1em;height:1em;}
    .gamedate
      {font-size:1em;height:1em;}
    .gametime
      {font-size:1em;height:1em;}
    .pre-row1, .pre-row2, .pre-row3 
      {font-size:1em;height:1em;}

With and without image (needs edit on card widths but you get the idea)

Or this … hide components you do not want …

type: custom:teamtracker-card
entity: sensor.detroit_tigers
card_mod:
  style: >-
    .logo {max-width:30px!important;} .card {width:60%} .name
    {font-size:1.2em;height:1em;} .gameday {font-size:1em;height:1em;} .gamedate
    {font-size:1em;height:1em;} .gametime {font-size:1em;height:1em;} .pre-row1,
    .pre-row2, .pre-row3 {font-size:1em;height:1em;} .score {font-size:1.5em;}
    .play-clock {font-size: 1.2em;} .in-row1, .in-row2, .line1, .line2,
    .last-play, .bar-wrapper {display:none;}

Pulling everything together in a panel/grid:

views:
  - title: Panel Playground
    icon: mdi:toy-brick-marker
    type: panel
    badges: []
    cards:
      - square: true
        type: grid
        cards:
          - type: custom:teamtracker-card
            entity: sensor.detroit_tigers
            card_mod:
              style: >-
                .logo {max-width:30px!important;} .name
                {font-size:1.2em;height:1em;} .gameday
                {font-size:1em;height:1em;} .gamedate
                {font-size:1em;height:1em;} .gametime
                {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
                {font-size:1em;height:1em;} .score {font-size:1.5em;}
                .play-clock {font-size: 1.2em;} .in-row1, .in-row2, .line1,
                .line2, .last-play, .bar-wrapper {display:none;}
          - type: custom:teamtracker-card
            entity: sensor.detroit_tigers
            card_mod:
              style: >-
                .logo {max-width:30px!important;} .name
                {font-size:1.2em;height:1em;} .gameday
                {font-size:1em;height:1em;} .gamedate
                {font-size:1em;height:1em;} .gametime
                {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
                {font-size:1em;height:1em;} .score {font-size:1.5em;}
                .play-clock {font-size: 1.2em;} .in-row1, .in-row2, .line1,
                .line2, .last-play, .bar-wrapper {display:none;}
          - type: custom:teamtracker-card
            entity: sensor.detroit_tigers
            card_mod:
              style: >-
                .logo {max-width:30px!important;} .name
                {font-size:1.2em;height:1em;} .gameday
                {font-size:1em;height:1em;} .gamedate
                {font-size:1em;height:1em;} .gametime
                {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
                {font-size:1em;height:1em;} .score {font-size:1.5em;}
                .play-clock {font-size: 1.2em;} .in-row1, .in-row2, .line1,
                .line2, .last-play, .bar-wrapper {display:none;}
          - type: custom:teamtracker-card
            entity: sensor.detroit_tigers
            card_mod:
              style: >-
                .logo {max-width:30px!important;} .name
                {font-size:1.2em;height:1em;} .gameday
                {font-size:1em;height:1em;} .gamedate
                {font-size:1em;height:1em;} .gametime
                {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
                {font-size:1em;height:1em;} .score {font-size:1.5em;}
                .play-clock {font-size: 1.2em;} .in-row1, .in-row2, .line1,
                .line2, .last-play, .bar-wrapper {display:none;}
          - type: custom:teamtracker-card
            entity: sensor.detroit_tigers
            card_mod:
              style: >-
                .logo {max-width:30px!important;} .name
                {font-size:1.2em;height:1em;} .gameday
                {font-size:1em;height:1em;} .gamedate
                {font-size:1em;height:1em;} .gametime
                {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
                {font-size:1em;height:1em;} .score {font-size:1.5em;}
                .play-clock {font-size: 1.2em;} .in-row1, .in-row2, .line1,
                .line2, .last-play, .bar-wrapper {display:none;}
        columns: 5
title: Panel Playground

Mixing it up a bit with some in-game …

It could easily be shrunk more and also needs set height on cards for grid view … or better custom:layout-card so that cards snap to fit from phone/pad/computer view.

views:
  - title: Panel Playground
    icon: mdi:toy-brick-marker
    type: custom:masonry-layout
    layout:
      max_cols: 5
    badges: []
    cards:
      - type: custom:teamtracker-card
        entity: sensor.detroit_tigers
        card_mod:
          style: >-
            .logo {max-width:30px!important;} .name
            {font-size:1.2em;height:1em;} .gameday {font-size:1em;height:1em;}
            .gamedate {font-size:1em;height:1em;} .gametime
            {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
            {font-size:1em;height:1em;} .score {font-size:1.5em;} .play-clock
            {font-size: 1.2em;} .in-row1, .in-row2, .line1, .line2, .last-play,
            .bar-wrapper {display:none;}
      - type: custom:teamtracker-card
        entity: sensor.minnesota_twins
        card_mod:
          style: >-
            .logo {max-width:30px!important;} .name
            {font-size:1.2em;height:1em;} .gameday {font-size:1em;height:1em;}
            .gamedate {font-size:1em;height:1em;} .gametime
            {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
            {font-size:1em;height:1em;} .score {font-size:1.5em;} .play-clock
            {font-size: 1.2em;} .in-row1, .in-row2, .line1, .line2, .last-play,
            .bar-wrapper {display:none;}
      - type: custom:teamtracker-card
        entity: sensor.detroit_tigers
        card_mod:
          style: >-
            .logo {max-width:30px!important;} .name
            {font-size:1.2em;height:1em;} .gameday {font-size:1em;height:1em;}
            .gamedate {font-size:1em;height:1em;} .gametime
            {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
            {font-size:1em;height:1em;} .score {font-size:1.5em;} .play-clock
            {font-size: 1.2em;} .in-row1, .in-row2, .line1, .line2, .last-play,
            .bar-wrapper {display:none;}
      - type: custom:teamtracker-card
        entity: sensor.chicago_white_sox
        card_mod:
          style: >-
            .logo {max-width:30px!important;} .name
            {font-size:1.2em;height:1em;} .gameday {font-size:1em;height:1em;}
            .gamedate {font-size:1em;height:1em;} .gametime
            {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
            {font-size:1em;height:1em;} .score {font-size:1.5em;} .play-clock
            {font-size: 1.2em;} .in-row1, .in-row2, .line1, .line2, .last-play,
            .bar-wrapper {display:none;}
      - type: custom:teamtracker-card
        entity: sensor.detroit_tigers
        card_mod:
          style: >-
            .logo {max-width:30px!important;} .name
            {font-size:1.2em;height:1em;} .gameday {font-size:1em;height:1em;}
            .gamedate {font-size:1em;height:1em;} .gametime
            {font-size:1em;height:1em;} .pre-row1, .pre-row2, .pre-row3
            {font-size:1em;height:1em;} .score {font-size:1.5em;} .play-clock
            {font-size: 1.2em;} .in-row1, .in-row2, .line1, .line2, .last-play,
            .bar-wrapper {display:none;}
title: Panel Playground

Gives you this on computer:

This on phone:

This on iPad:

That is as far as I will go but you have all the blocks here to make what you want just using card-mod.
I will admit that this helped me a bit as I always wanted to scale down the card for my wallpanel displays

3 Likes

I tweaked the card a while ago specifically to increase it’s compatibility with card-mod. You should be able to mod whatever component or card-level setting you want.

If you can’t, let me know and I’ll make the changes to allow it. I decided it’s a lot easier to increase compatibility w/ card-mod than to add a ton of options trying to anticipate what someone might want.

3 Likes

Hello,

I am just getting started on Home Assistant and I am having problems setting up this card. I downloaded the card from HACs but I don’t have a sensor.team_tracker in my entities. I am only seeing update.team_tracker_card_update.

When I go to set up this card it says this entity is missing. Is there something that I did wrong?

Have you installed the Card and the Integration from HACS (and then setup the Integration to track a team)? It’s the Integration that provides the sensor, the card then displays it…

Best have a good read through this to get you up and running properly…

kann mir einer helfen die deutsche 2 bundesliga rein zu bekommen ? danke schonmal ist ein echt top gemacht

- platform: teamtracker
  league_id: XXX
  team_id: KIE
  sport_path: soccer
  league_path: ger.2
  name: holstein-kiel