matt-bernhardt/trapp

View on GitHub
trapp/import_goal.py

Summary

Maintainability
C
1 day
Test Coverage
# -*- coding: utf-8 -*-
from trapp.importer import Importer
from trapp.game import Game
from trapp.gameevent import GameEvent
from trapp.player import Player


class ImporterGoals(Importer):

    def correctValues(self):
        # This takes in all rows of the imported spreadsheet, and performs
        # any needed repairs / adjustments

        # We can re-use the same Game object inside the loop
        g = Game()
        g.connectDB()

        self.log.message('Correcting values...')
        for record in self.records:

            # Log the record when we begin
            self.log.message('\nParsing game record:')
            # self.log.message('  ' + str(record) + '\n')

            # NewEvents holds the rebuilt event records - we create it this
            # early because the next step skips this record when no goals
            # are scored - and it still needs to be present.
            record['NewEvents'] = []

            # Games with no goals get skipped
            if (record['Goals'] == ''):
                continue

            # 1. TeamID lookup
            teamID = self.lookupTeamID(record['Team'])

            # 2. OpponentID lookup
            opponentID = self.lookupTeamID(record['Opponent'])

            # 3. Home or Away
            if (record['H/A'] == 'H'):
                homeID = teamID
                awayID = opponentID
            elif (record['H/A'] == 'A'):
                homeID = opponentID
                awayID = teamID

            # 4. The game date needs to be converted
            record['Date'] = self.source.recoverDate(record['Date'])

            # Use home/away ID and game date to look up the game ID
            needle = {
                'MatchTime': record['Date'],
                'HTeamID': homeID,
                'ATeamID': awayID,
            }
            self.log.message('  Looking up needle: ' + str(needle))
            game = g.lookupID(needle, self.log)

            self.log.message('  Found games: ' + str(game) + '\n')

            if (len(game) != 1):
                self.log.message('Found wrong number of games: ' +
                                 str(len(game)))
                self.skipped += 1
                # If we didn't find one gameID, then we abort processing this
                # game
                return False

            # Need to convert gameID from a list of 1 number to an integer
            game = game[0]

            # 5. The goalscorers string needs to be expanded
            record['Events'] = self.splitGoals(record['Goals'])
            # record['Events'] is now a list of strings. We now need to parse
            # each individual string into a dictionary.
            for item in record['Events']:
                item = self.parseOneGoal(item, game, teamID, opponentID)
                for subitem in item:
                    record['NewEvents'].append(self.lookupPlayerID(subitem))

            # Log the corrected record for later inspection
            self.log.message('  Outcome:\n  ' + str(record))

        return True

    def disambiguatePlayers(self, record, result):
        # Ask user to provide player ID
        print(('\nPlayerID lookup of _' + str(record['playername']) + '_' +
              ' failed with ' + str(len(result)) + ' records\n'))
        print((str(record)))
        newID = int(input('Player ID - provide null or 0 to skip: '))

        return newID

    def importRecord(self, record):
        self.log.message('\nImporting record:\n  ' + str(record))

        for item in record['NewEvents']:
            self.log.message(str(item))

            # Skip over items
            if item is False:
                self.log.message('Skipping FALSE item')
                continue

            e = GameEvent()
            e.connectDB()

            eventID = e.lookupID(item, self.log)

            if (len(eventID) > 1):
                # We have more than one record of this player/team/game/minute.
                # This is a problem.
                self.log.message('Error: ' +
                                 str(len(eventID)) +
                                 ' events found during lookup')
                self.errored += 1
            elif (len(eventID) == 1):
                # We already have a record of this event.
                # We add that eventID to ensure an update.
                item['ID'] = eventID[0]
                e.saveDict(item, self.log)
                self.updated += 1
            else:
                e.saveDict(item, self.log)
                self.imported += 1

        return True

    def lookupPlayerID(self, event):
        self.log.message('Looking up PlayerID for event:\n' + str(event))

        # Swap team and opponent IDs for own goals
        event = self.swapTeamIDs(event)

        p = Player()
        p.connectDB()

        PlayerID = p.lookupIDbyGoal(event, self.log)

        # Swap team and opponent IDs back for own goals
        event = self.swapTeamIDs(event)

        if (len(PlayerID) != 1):
            # First step is to ask the user to disambiguate
            newID = self.disambiguatePlayers(event, PlayerID)
            PlayerID = []
            PlayerID.append(newID)

        if (PlayerID[0] == 0):
            # If PlayerID is still zero, mark it as missing
            self.processMissingRecord(event['playername'], len(PlayerID))
            return False

        event['PlayerID'] = PlayerID[0]
        return event

    def parseAssists(self, recordList, minute, assists, gameID, teamID):
        # This adds records to a list according to the assists in a string
        # describing a goal.

        # Split into a list, test its length
        test = assists.split(',')
        if (len(test) > 2):
            self.skipped += 1
            self.log.message('Found too many assists: ' +
                             str(test) + ' has ' + str(len(test)))
            return recordList

        # Parse each element in the list
        eventID = 2
        for item in test:
            item = item.strip()
            recordList.append({
                'GameID': gameID,
                'TeamID': teamID,
                'MinuteID': minute,
                'Event': eventID,
                'playername': item,
                'Notes': ''
            })
            eventID += 1

        return recordList

    def parseEventTime(self, inputString):
        candidate = inputString[inputString.rfind(' '):].strip()
        try:
            time = self.parseMinute(candidate)
        except ValueError:
            time = 0

        return time

    def parseOneGoal(self, inputString, gameID, teamID, opponentID):
        # This takes in a string describing a single goal.
        # It returns a list of dictionaries, one for the goal and then up to
        # two for the assists.

        # Need to check format. Expects:
        # Lastname (Assist, Assist) Minute
        # If no assist, then:
        # Lastname (unassisted) Minute
        # If a penalty, then:
        # Lastname (penalty) Minute

        records = []

        # If there's no parenthesis, then increment skipped and head back
        if not (inputString.find('(')):
            self.skipped += 1
            return records

        begin = inputString.find('(')
        end = inputString.rfind(')')

        # Isolate player name and substitute
        playerName = inputString[:begin - 1].strip()

        # Isolate substitute name
        assistName = inputString[begin + 1:end].strip()

        event = 1
        notes = ''
        if assistName == 'penalty':
            notes = 'penalty kick'

        if assistName == 'own goal':
            notes = 'own goal'
            event = 6

        # Isolate minute
        minute = self.parseEventTime(inputString)

        records.append({
            'GameID': gameID,
            'TeamID': teamID,
            'OpponentID': opponentID,
            'MinuteID': minute,
            'Event': event,
            'playername': playerName,
            'Notes': notes
        })

        if (
            assistName != 'penalty' and
            assistName != 'unassisted' and
            assistName != 'own goal'
        ):
            records = self.parseAssists(
                records, minute, assistName, gameID, teamID
            )

        return records

    def splitGoals(self, inputString):
        # This takes ina string listing all goals scored in a game.
        # It returns a list, with each goal separated.
        # Downstream steps will perform additional parsing.
        self.log.message('  Parsing goal string: ' + str(inputString))
        events = []
        for goal in inputString.split(';'):
            events.append(goal.strip())
        self.log.message('  ' + str(events) + '\n')
        return events

    def swapTeamIDs(self, record):
        # This takes a dictionary of an event record and swaps the team and
        # opponent IDs. This is called as part of looking up a PlayerID for
        # an own goal

        if (record['Event'] == 6):
            temp = record['TeamID']
            record['TeamID'] = record['OpponentID']
            record['OpponentID'] = temp

        return record