]> de.git.xonotic.org Git - xonotic/xonstat.git/commitdiff
Major reorganization. Views made into a module with all of the sub-portions categorized.
authorAnt Zucaro <azucaro@gmail.com>
Wed, 25 May 2011 14:43:02 +0000 (10:43 -0400)
committerAnt Zucaro <azucaro@gmail.com>
Wed, 25 May 2011 14:43:02 +0000 (10:43 -0400)
xonstat/views/__init__.py [new file with mode: 0755]
xonstat/views/game.py [new file with mode: 0755]
xonstat/views/map.py [new file with mode: 0755]
xonstat/views/player.py [new file with mode: 0755]
xonstat/views/server.py [new file with mode: 0755]
xonstat/views/submission.py [new file with mode: 0755]

diff --git a/xonstat/views/__init__.py b/xonstat/views/__init__.py
new file mode 100755 (executable)
index 0000000..50b68ba
--- /dev/null
@@ -0,0 +1,6 @@
+from xonstat.views.submission import stats_submit\r
+from xonstat.views.player import player_index, player_info, player_game_index\r
+from xonstat.views.player import player_weapon_stats\r
+from xonstat.views.game import game_index, game_info\r
+from xonstat.views.map import map_info\r
+from xonstat.views.server import server_info, server_game_index\r
diff --git a/xonstat/views/game.py b/xonstat/views/game.py
new file mode 100755 (executable)
index 0000000..b5be6ac
--- /dev/null
@@ -0,0 +1,85 @@
+import datetime\r
+import logging\r
+import re\r
+import time\r
+from pyramid.response import Response\r
+from sqlalchemy import desc\r
+from webhelpers.paginate import Page, PageURL\r
+from xonstat.models import *\r
+from xonstat.util import page_url\r
+\r
+log = logging.getLogger(__name__)\r
+\r
+\r
+def game_index(request):\r
+    """\r
+    Provides a list of current games, with the associated game stats.\r
+    These games are ordered by game_id, with the most current ones first.\r
+    Paginated.\r
+    """\r
+    if 'page' in request.matchdict:\r
+        current_page = request.matchdict['page']\r
+    else:\r
+        current_page = 1\r
+\r
+    games_q = DBSession.query(Game, Server, Map).\\r
+            filter(Game.server_id == Server.server_id).\\r
+            filter(Game.map_id == Map.map_id).\\r
+            order_by(Game.game_id.desc())\r
+\r
+    games = Page(games_q, current_page, url=page_url)\r
+\r
+    pgstats = {}\r
+    for (game, server, map) in games:\r
+        pgstats[game.game_id] = DBSession.query(PlayerGameStat).\\r
+                filter(PlayerGameStat.game_id == game.game_id).\\r
+                order_by(PlayerGameStat.rank).\\r
+                order_by(PlayerGameStat.score).all()\r
+\r
+    return {'games':games, \r
+            'pgstats':pgstats}\r
+\r
+\r
+def game_info(request):\r
+    """\r
+    List the game stats (scoreboard) for a particular game. Paginated.\r
+    """\r
+    game_id = request.matchdict['id']\r
+    try:\r
+        notfound = False\r
+\r
+        (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \\r
+        DBSession.query("start_dt", "game_type_cd", "server_id", \r
+                "server_name", "map_id", "map_name").\\r
+                from_statement("select g.start_dt, g.game_type_cd, "\r
+                        "g.server_id, s.name as server_name, g.map_id, "\r
+                        "m.name as map_name "\r
+                        "from games g, servers s, maps m "\r
+                        "where g.game_id = :game_id "\r
+                        "and g.server_id = s.server_id "\r
+                        "and g.map_id = m.map_id").\\r
+                        params(game_id=game_id).one()\r
+\r
+        player_game_stats = DBSession.query(PlayerGameStat).\\r
+                from_statement("select * from player_game_stats "\r
+                        "where game_id = :game_id "\r
+                        "order by score desc").\\r
+                            params(game_id=game_id).all()\r
+    except Exception as inst:\r
+        notfound = True\r
+        start_dt = None\r
+        game_type_cd = None\r
+        server_id = None\r
+        server_name = None\r
+        map_id = None\r
+        map_name = None\r
+        player_game_stats = None\r
+\r
+    return {'notfound':notfound,\r
+            'start_dt':start_dt,\r
+            'game_type_cd':game_type_cd,\r
+            'server_id':server_id,\r
+            'server_name':server_name,\r
+            'map_id':map_id,\r
+            'map_name':map_name,\r
+            'player_game_stats':player_game_stats}\r
diff --git a/xonstat/views/map.py b/xonstat/views/map.py
new file mode 100755 (executable)
index 0000000..358dc5f
--- /dev/null
@@ -0,0 +1,19 @@
+import logging\r
+from pyramid.response import Response\r
+from webhelpers.paginate import Page, PageURL\r
+from xonstat.models import *\r
+from xonstat.util import page_url\r
+\r
+log = logging.getLogger(__name__)\r
+\r
+\r
+def map_info(request):\r
+    """\r
+    List the information stored about a given map. \r
+    """\r
+    map_id = request.matchdict['id']\r
+    try:\r
+        gmap = DBSession.query(Map).filter_by(map_id=map_id).one()\r
+    except:\r
+        gmap = None\r
+    return {'gmap':gmap}\r
diff --git a/xonstat/views/player.py b/xonstat/views/player.py
new file mode 100755 (executable)
index 0000000..9337b83
--- /dev/null
@@ -0,0 +1,126 @@
+import datetime\r
+import logging\r
+import re\r
+import time\r
+from pyramid.response import Response\r
+from sqlalchemy import desc\r
+from webhelpers.paginate import Page, PageURL\r
+from xonstat.models import *\r
+from xonstat.util import page_url\r
+\r
+log = logging.getLogger(__name__)\r
+\r
+\r
+def player_index(request):\r
+    """\r
+    Provides a list of all the current players. \r
+    """\r
+    players = DBSession.query(Player)\r
+\r
+    log.debug("testing logging; entered PlayerHandler.index()")\r
+    return {'players':players}\r
+\r
+def player_info(request):\r
+    """\r
+    Provides detailed information on a specific player\r
+    """\r
+    player_id = request.matchdict['id']\r
+    try:\r
+        player = DBSession.query(Player).filter_by(player_id=player_id).one()\r
+\r
+        weapon_stats = DBSession.query("descr", "actual_total", \r
+                "max_total", "hit_total", "fired_total", "frags_total").\\r
+                from_statement(\r
+                    "select cw.descr, sum(actual) actual_total, "\r
+                    "sum(max) max_total, sum(hit) hit_total, "\r
+                    "sum(fired) fired_total, sum(frags) frags_total "\r
+                    "from xonstat.player_weapon_stats ws, xonstat.cd_weapon cw "\r
+                    "where ws.weapon_cd = cw.weapon_cd "\r
+                    "and player_id = :player_id "\r
+                    "group by descr "\r
+                    "order by descr"\r
+                ).params(player_id=player_id).all()\r
+\r
+        log.debug(weapon_stats)\r
+\r
+        recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\\r
+                filter(PlayerGameStat.player_id == player_id).\\r
+                filter(PlayerGameStat.game_id == Game.game_id).\\r
+                filter(Game.server_id == Server.server_id).\\r
+                filter(Game.map_id == Map.map_id).\\r
+                order_by(Game.game_id.desc())[0:10]\r
+\r
+    except Exception as e:\r
+        player = None\r
+        weapon_stats = None\r
+        recent_games = None\r
+    return {'player':player, \r
+            'recent_games':recent_games,\r
+            'weapon_stats':weapon_stats}\r
+\r
+\r
+def player_game_index(request):\r
+    """\r
+    Provides an index of the games in which a particular\r
+    player was involved. This is ordered by game_id, with\r
+    the most recent game_ids first. Paginated.\r
+    """\r
+    player_id = request.matchdict['player_id']\r
+\r
+    if 'page' in request.matchdict:\r
+        current_page = request.matchdict['page']\r
+    else:\r
+        current_page = 1\r
+\r
+    try:\r
+        player = DBSession.query(Player).filter_by(player_id=player_id).one()\r
+\r
+        games_q = DBSession.query(PlayerGameStat, Game, Server, Map).\\r
+                filter(PlayerGameStat.player_id == player_id).\\r
+                filter(PlayerGameStat.game_id == Game.game_id).\\r
+                filter(Game.server_id == Server.server_id).\\r
+                filter(Game.map_id == Map.map_id).\\r
+                order_by(Game.game_id.desc())\r
+\r
+        games = Page(games_q, current_page, url=page_url)\r
+\r
+        \r
+    except Exception as e:\r
+        player = None\r
+        games = None\r
+        raise e\r
+\r
+    return {'player':player,\r
+            'games':games}\r
+\r
+\r
+def player_weapon_stats(request):\r
+    """\r
+    List the accuracy statistics for the given player_id in a particular\r
+    game.\r
+    """\r
+    game_id = request.matchdict['game_id']\r
+    pgstat_id = request.matchdict['pgstat_id']\r
+    try:\r
+        pwstats = DBSession.query(PlayerWeaponStat, Weapon).\\r
+                filter(PlayerWeaponStat.weapon_cd==Weapon.weapon_cd).\\r
+                filter_by(game_id=game_id).\\r
+                filter_by(player_game_stat_id=pgstat_id).\\r
+                order_by(Weapon.descr).\\r
+                all()\r
+\r
+        pgstat = DBSession.query(PlayerGameStat).\\r
+                filter_by(player_game_stat_id=pgstat_id).one()\r
+\r
+        game = DBSession.query(Game).filter_by(game_id=game_id).one()\r
+\r
+        log.debug(pwstats)\r
+        log.debug(pgstat)\r
+        log.debug(game)\r
+\r
+    except Exception as e:\r
+        pwstats = None\r
+        pgstat = None\r
+        game = None\r
+        raise e\r
+    return {'pwstats':pwstats, 'pgstat':pgstat, 'game':game}\r
diff --git a/xonstat/views/server.py b/xonstat/views/server.py
new file mode 100755 (executable)
index 0000000..1df6f24
--- /dev/null
@@ -0,0 +1,56 @@
+import datetime\r
+import logging\r
+import time\r
+from pyramid.response import Response\r
+from sqlalchemy import desc\r
+from webhelpers.paginate import Page, PageURL\r
+from xonstat.models import *\r
+from xonstat.util import page_url\r
+\r
+log = logging.getLogger(__name__)\r
+\r
+\r
+def server_info(request):\r
+    """\r
+    List the stored information about a given server.\r
+    """\r
+    server_id = request.matchdict['id']\r
+    try:\r
+        server = DBSession.query(Server).filter_by(server_id=server_id).one()\r
+        recent_games = DBSession.query(Game, Server, Map).\\r
+                filter(Game.server_id == server_id).\\r
+                filter(Game.server_id == Server.server_id).\\r
+                filter(Game.map_id == Map.map_id).\\r
+                order_by(Game.game_id.desc())[0:10]\r
+\r
+    except Exception as e:\r
+        server = None\r
+        recent_games = None\r
+    return {'server':server,\r
+            'recent_games':recent_games}\r
+\r
+\r
+def server_game_index(request):\r
+    """\r
+    List the games played on a given server. Paginated.\r
+    """\r
+    server_id = request.matchdict['server_id']\r
+    current_page = request.matchdict['page']\r
+\r
+    try:\r
+        server = DBSession.query(Server).filter_by(server_id=server_id).one()\r
+\r
+        games_q = DBSession.query(Game, Server, Map).\\r
+                filter(Game.server_id == server_id).\\r
+                filter(Game.server_id == Server.server_id).\\r
+                filter(Game.map_id == Map.map_id).\\r
+                order_by(Game.game_id.desc())\r
+\r
+        games = Page(games_q, current_page, url=page_url)\r
+    except Exception as e:\r
+        server = None\r
+        games = None\r
+        raise e\r
+\r
+    return {'games':games,\r
+            'server':server}\r
diff --git a/xonstat/views/submission.py b/xonstat/views/submission.py
new file mode 100755 (executable)
index 0000000..97836de
--- /dev/null
@@ -0,0 +1,376 @@
+import datetime\r
+import logging\r
+import re\r
+import time\r
+from pyramid.response import Response\r
+from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound\r
+from xonstat.models import *\r
+\r
+log = logging.getLogger(__name__)\r
+\r
+\r
+def get_or_create_server(session=None, name=None):\r
+    """\r
+    Find a server by name or create one if not found. Parameters:\r
+\r
+    session - SQLAlchemy database session factory\r
+    name - server name of the server to be found or created\r
+    """\r
+    try:\r
+        # find one by that name, if it exists\r
+        server = session.query(Server).filter_by(name=name).one()\r
+        log.debug("Found server id {0} with name {1}.".format(\r
+            server.server_id, server.name))\r
+    except NoResultFound, e:\r
+        server = Server(name=name)\r
+        session.add(server)\r
+        session.flush()\r
+        log.debug("Created server id {0} with name {1}".format(\r
+            server.server_id, server.name))\r
+    except MultipleResultsFound, e:\r
+        # multiple found, so use the first one but warn\r
+        log.debug(e)\r
+        servers = session.query(Server).filter_by(name=name).order_by(\r
+                Server.server_id).all()\r
+        server = servers[0]\r
+        log.debug("Created server id {0} with name {1} but found \\r
+                multiple".format(\r
+            server.server_id, server.name))\r
+\r
+    return server\r
+\r
+def get_or_create_map(session=None, name=None):\r
+    """\r
+    Find a map by name or create one if not found. Parameters:\r
+\r
+    session - SQLAlchemy database session factory\r
+    name - map name of the map to be found or created\r
+    """\r
+    try:\r
+        # find one by the name, if it exists\r
+        gmap = session.query(Map).filter_by(name=name).one()\r
+        log.debug("Found map id {0} with name {1}.".format(gmap.map_id, \r
+            gmap.name))\r
+    except NoResultFound, e:\r
+        gmap = Map(name=name)\r
+        session.add(gmap)\r
+        session.flush()\r
+        log.debug("Created map id {0} with name {1}.".format(gmap.map_id,\r
+            gmap.name))\r
+    except MultipleResultsFound, e:\r
+        # multiple found, so use the first one but warn\r
+        log.debug(e)\r
+        gmaps = session.query(Map).filter_by(name=name).order_by(\r
+                Map.map_id).all()\r
+        gmap = gmaps[0]\r
+        log.debug("Found map id {0} with name {1} but found \\r
+                multiple.".format(gmap.map_id, gmap.name))\r
+\r
+    return gmap\r
+\r
+\r
+def create_game(session=None, start_dt=None, game_type_cd=None, \r
+        server_id=None, map_id=None, winner=None):\r
+    """\r
+    Creates a game. Parameters:\r
+\r
+    session - SQLAlchemy database session factory\r
+    start_dt - when the game started (datetime object)\r
+    game_type_cd - the game type of the game being played\r
+    server_id - server identifier of the server hosting the game\r
+    map_id - map on which the game was played\r
+    winner - the team id of the team that won\r
+    """\r
+\r
+    game = Game(start_dt=start_dt, game_type_cd=game_type_cd,\r
+                server_id=server_id, map_id=map_id, winner=winner)\r
+    session.add(game)\r
+    session.flush()\r
+    log.debug("Created game id {0} on server {1}, map {2} at time \\r
+            {3} and on map {4}".format(game.game_id, \r
+                server_id, map_id, start_dt, map_id))\r
+\r
+    return game\r
+\r
+\r
+def get_or_create_player(session=None, hashkey=None, nick=None):\r
+    """\r
+    Finds a player by hashkey or creates a new one (along with a\r
+    corresponding hashkey entry. Parameters:\r
+\r
+    session - SQLAlchemy database session factory\r
+    hashkey - hashkey of the player to be found or created\r
+    nick - nick of the player (in case of a first time create)\r
+    """\r
+    # if we have a bot\r
+    if re.search('^bot#\d+$', hashkey):\r
+        player = session.query(Player).filter_by(player_id=1).one()\r
+    # if we have an untracked player\r
+    elif re.search('^player#\d+$', hashkey):\r
+        player = session.query(Player).filter_by(player_id=2).one()\r
+    # else it is a tracked player\r
+    else:\r
+        # see if the player is already in the database\r
+        # if not, create one and the hashkey along with it\r
+        try:\r
+            hashkey = session.query(Hashkey).filter_by(\r
+                    hashkey=hashkey).one()\r
+            player = session.query(Player).filter_by(\r
+                    player_id=hashkey.player_id).one()\r
+            log.debug("Found existing player {0} with hashkey {1}.".format(\r
+                player.player_id, hashkey.hashkey))\r
+        except:\r
+            player = Player()\r
+\r
+            if nick:\r
+                player.nick = nick\r
+\r
+            session.add(player)\r
+            session.flush()\r
+            hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)\r
+            session.add(hashkey)\r
+            log.debug("Created player {0} with hashkey {1}.".format(\r
+                player.player_id, hashkey.hashkey))\r
+\r
+    return player\r
+\r
+def create_player_game_stat(session=None, player=None, \r
+        game=None, player_events=None):\r
+    """\r
+    Creates game statistics for a given player in a given game. Parameters:\r
+\r
+    session - SQLAlchemy session factory\r
+    player - Player record of the player who owns the stats\r
+    game - Game record for the game to which the stats pertain\r
+    player_events - dictionary for the actual stats that need to be transformed\r
+    """\r
+\r
+    # in here setup default values (e.g. if game type is CTF then\r
+    # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc\r
+    # TODO: use game's create date here instead of now()\r
+    pgstat = PlayerGameStat(create_dt=datetime.datetime.now())\r
+\r
+    # set player id from player record\r
+    pgstat.player_id = player.player_id\r
+\r
+    #set game id from game record\r
+    pgstat.game_id = game.game_id\r
+\r
+    # all games have a score\r
+    pgstat.score = 0\r
+\r
+    if game.game_type_cd == 'dm':\r
+        pgstat.kills = 0\r
+        pgstat.deaths = 0\r
+        pgstat.suicides = 0\r
+    elif game.game_type_cd == 'ctf':\r
+        pgstat.kills = 0\r
+        pgstat.captures = 0\r
+        pgstat.pickups = 0\r
+        pgstat.drops = 0\r
+        pgstat.returns = 0\r
+        pgstat.carrier_frags = 0\r
+\r
+    for (key,value) in player_events.items():\r
+        if key == 'n': pgstat.nick = value\r
+        if key == 't': pgstat.team = value\r
+        if key == 'rank': pgstat.rank = value\r
+        if key == 'alivetime': \r
+            pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))\r
+        if key == 'scoreboard-drops': pgstat.drops = value\r
+        if key == 'scoreboard-returns': pgstat.returns = value\r
+        if key == 'scoreboard-fckills': pgstat.carrier_frags = value\r
+        if key == 'scoreboard-pickups': pgstat.pickups = value\r
+        if key == 'scoreboard-caps': pgstat.captures = value\r
+        if key == 'scoreboard-score': pgstat.score = value\r
+        if key == 'scoreboard-deaths': pgstat.deaths = value\r
+        if key == 'scoreboard-kills': pgstat.kills = value\r
+        if key == 'scoreboard-suicides': pgstat.suicides = value\r
+\r
+    # check to see if we had a name, and if \r
+    # not use the name from the player id\r
+    if pgstat.nick == None:\r
+        pgstat.nick = player.nick\r
+\r
+    session.add(pgstat)\r
+    session.flush()\r
+\r
+    return pgstat\r
+\r
+\r
+def create_player_weapon_stats(session=None, player=None, \r
+        game=None, pgstat=None, player_events=None):\r
+    """\r
+    Creates accuracy records for each weapon used by a given player in a\r
+    given game. Parameters:\r
+\r
+    session - SQLAlchemy session factory object\r
+    player - Player record who owns the weapon stats\r
+    game - Game record in which the stats were created\r
+    pgstat - Corresponding PlayerGameStat record for these weapon stats\r
+    player_events - dictionary containing the raw weapon values that need to be\r
+        transformed\r
+    """\r
+    pwstats = []\r
+\r
+    for (key,value) in player_events.items():\r
+        matched = re.search("acc-(.*?)-cnt-fired", key)\r
+        if matched:\r
+            weapon_cd = matched.group(1)\r
+            pwstat = PlayerWeaponStat()\r
+            pwstat.player_id = player.player_id\r
+            pwstat.game_id = game.game_id\r
+            pwstat.player_game_stat_id = pgstat.player_game_stat_id\r
+            pwstat.weapon_cd = weapon_cd\r
+\r
+            if 'n' in player_events:\r
+                pwstat.nick = player_events['n']\r
+            else:\r
+                pwstat.nick = player_events['P']\r
+\r
+            if 'acc-' + weapon_cd + '-cnt-fired' in player_events:\r
+                pwstat.fired = int(round(float(\r
+                        player_events['acc-' + weapon_cd + '-cnt-fired'])))\r
+            if 'acc-' + weapon_cd + '-fired' in player_events:\r
+                pwstat.max = int(round(float(\r
+                        player_events['acc-' + weapon_cd + '-fired'])))\r
+            if 'acc-' + weapon_cd + '-cnt-hit' in player_events:\r
+                pwstat.hit = int(round(float(\r
+                        player_events['acc-' + weapon_cd + '-cnt-hit'])))\r
+            if 'acc-' + weapon_cd + '-hit' in player_events:\r
+                pwstat.actual = int(round(float(\r
+                        player_events['acc-' + weapon_cd + '-hit'])))\r
+            if 'acc-' + weapon_cd + '-frags' in player_events:\r
+                pwstat.frags = int(round(float(\r
+                        player_events['acc-' + weapon_cd + '-frags'])))\r
+\r
+            session.add(pwstat)\r
+            pwstats.append(pwstat)\r
+\r
+    return pwstats\r
+\r
+\r
+def parse_body(request):\r
+    """\r
+    Parses the POST request body for a stats submission\r
+    """\r
+    # storage vars for the request body\r
+    game_meta = {}\r
+    player_events = {}\r
+    current_team = None\r
+    players = []\r
+    \r
+    log.debug(request.body)\r
+\r
+    for line in request.body.split('\n'):\r
+        try:\r
+            (key, value) = line.strip().split(' ', 1)\r
+    \r
+            if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':\r
+                game_meta[key] = value\r
+\r
+            if key == 'P':\r
+                # if we were working on a player record already, append\r
+                # it and work on a new one (only set team info)\r
+                if len(player_events) != 0:\r
+                    players.append(player_events)\r
+                    player_events = {}\r
+    \r
+                player_events[key] = value\r
+\r
+            if key == 'e':\r
+                (subkey, subvalue) = value.split(' ', 1)\r
+                player_events[subkey] = subvalue\r
+            if key == 'n':\r
+                player_events[key] = value\r
+            if key == 't':\r
+                player_events[key] = value\r
+        except:\r
+            # no key/value pair - move on to the next line\r
+            pass\r
+    \r
+    # add the last player we were working on\r
+    if len(player_events) > 0:\r
+        players.append(player_events)\r
+\r
+    return (game_meta, players)\r
+\r
+\r
+def create_player_stats(session=None, player=None, game=None, \r
+        player_events=None):\r
+    """\r
+    Creates player game and weapon stats according to what type of player\r
+    """\r
+    if 'joins' in player_events and 'matches' in player_events\\r
+            and 'scoreboardvalid' in player_events:\r
+                pgstat = create_player_game_stat(session=session, \r
+                        player=player, game=game, player_events=player_events)\r
+                if not re.search('^bot#\d+$', player_events['P']):\r
+                        create_player_weapon_stats(session=session, \r
+                            player=player, game=game, pgstat=pgstat,\r
+                            player_events=player_events)\r
+    \r
+\r
+def stats_submit(request):\r
+    """\r
+    Entry handler for POST stats submissions.\r
+    """\r
+    try:\r
+        session = DBSession()\r
+\r
+        (game_meta, players) = parse_body(request)  \r
+    \r
+        # verify required metadata is present\r
+        if 'T' not in game_meta or\\r
+            'G' not in game_meta or\\r
+            'M' not in game_meta or\\r
+            'S' not in game_meta:\r
+            log.debug("Required game meta fields (T, G, M, or S) missing. "\\r
+                    "Can't continue.")\r
+            raise Exception("Required game meta fields (T, G, M, or S) missing.")\r
+    \r
+        has_real_players = False\r
+        for player_events in players:\r
+            if not player_events['P'].startswith('bot'):\r
+                if 'joins' in player_events and 'matches' in player_events\\r
+                    and 'scoreboardvalid' in player_events:\r
+                    has_real_players = True\r
+\r
+        if not has_real_players:\r
+            raise Exception("No real players found. Stats ignored.")\r
+\r
+        server = get_or_create_server(session=session, name=game_meta['S'])\r
+        gmap = get_or_create_map(session=session, name=game_meta['M'])\r
+\r
+        if 'W' in game_meta:\r
+            winner = game_meta['W']\r
+        else:\r
+            winner = None\r
+\r
+        game = create_game(session=session, \r
+                start_dt=datetime.datetime(\r
+                    *time.gmtime(float(game_meta['T']))[:6]), \r
+                server_id=server.server_id, game_type_cd=game_meta['G'], \r
+                map_id=gmap.map_id, winner=winner)\r
+    \r
+        # find or create a record for each player\r
+        # and add stats for each if they were present at the end\r
+        # of the game\r
+        for player_events in players:\r
+            if 'n' in player_events:\r
+                nick = player_events['n']\r
+            else:\r
+                nick = None\r
+\r
+            player = get_or_create_player(session=session, \r
+                    hashkey=player_events['P'], nick=nick)\r
+            log.debug('Creating stats for %s' % player_events['P'])\r
+            create_player_stats(session=session, player=player, game=game, \r
+                    player_events=player_events)\r
+    \r
+        session.commit()\r
+        log.debug('Success! Stats recorded.')\r
+        return Response('200 OK')\r
+    except Exception as e:\r
+        session.rollback()\r
+        raise e\r