diff options
-rwxr-xr-x | dumps/second-a-lap.py | 18 | ||||
-rwxr-xr-x | elo.py | 26 | ||||
-rw-r--r-- | f1elo/db.py | 9 | ||||
-rw-r--r-- | f1elo/elo.py | 12 | ||||
-rw-r--r-- | f1elo/interface.py | 27 | ||||
-rw-r--r-- | f1elo/model.py | 89 |
6 files changed, 139 insertions, 42 deletions
diff --git a/dumps/second-a-lap.py b/dumps/second-a-lap.py index 867d7d3..a6665c9 100755 --- a/dumps/second-a-lap.py +++ b/dumps/second-a-lap.py @@ -11,14 +11,14 @@ import urllib2 import urlparse from sys import argv -from lxml import etree, html - from bs4 import BeautifulSoup +from lxml import etree, html def fetch(url): print url - contents = json.loads(urllib2.urlopen('http://second-a-lap.blogspot.com/feeds/posts/default?'+urllib.urlencode({ 'alt': 'json', 'v': 2, 'dynamicviews': 1, 'path': url })).read()) + contents = json.loads(urllib2.urlopen('http://second-a-lap.blogspot.com/feeds/posts/default?' + + urllib.urlencode({'alt': 'json', 'v': 2, 'dynamicviews': 1, 'path': url})).read()) title = contents['feed']['entry'][0]['title']['$t'] print title text = contents['feed']['entry'][0]['content']['$t'] @@ -31,13 +31,19 @@ def fetch(url): path = open(os.path.join('second-a-lap', name + '.txt'), 'w') table = etree.tostring(table) print >>path, table - csv_file = csv.writer(open(os.path.join('second-a-lap', 'csv', name + '.csv'), 'w')) + csv_file = csv.writer( + open(os.path.join('second-a-lap', 'csv', name + '.csv'), 'w')) soup = BeautifulSoup(table) for row in soup.find_all('tr'): - row = map(lambda t: re.sub('\s+', ' ', " ".join(t.stripped_strings)).encode('utf-8'), row.find_all(re.compile('t[dh]'))) + row = map( + lambda t: re.sub('\s+', + ' ', + " ".join(t.stripped_strings)).encode('utf-8'), + row.find_all(re.compile('t[dh]'))) csv_file.writerow(row) i += 1 + def compile(files): headers = set() values = [] @@ -57,7 +63,7 @@ def compile(files): data['Race'] = race_id i += 1 values.append(data) - writer.writerow([race_id,path,'','','']) + writer.writerow([race_id, path, '', '', '']) race_id += 1 except IOError: pass @@ -1,9 +1,12 @@ #!/usr/bin/env python import argparse -import dateutil.parser import sys -parser = argparse.ArgumentParser(description='Ranks Formula One drivers using Elo rating', +import dateutil.parser +from f1elo.interface import Interface + +parser = argparse.ArgumentParser( + description='Ranks Formula One drivers using Elo rating', formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('command', metavar='COMMAND', nargs='?', help="Action to execute against the database:\n" @@ -18,9 +21,19 @@ parser.add_argument('--date', 'Print ratings for DATE,\n' 'reset ratings all the way down to DATE\n' 'or rank the races all the way up to DATE.') -parser.add_argument('--limit', help='Ranking list (display) cut-off point.', type=int) -parser.add_argument('-v', help='Display verbose info on rating progress to STDERR.', action='store_true') -parser.add_argument('--force', '-f', help='Force database initialization (for "init" command).', action='store_true') +parser.add_argument( + '--limit', + help='Ranking list (display) cut-off point.', + type=int) +parser.add_argument( + '-v', + help='Display verbose info on rating progress to STDERR.', + action='store_true') +parser.add_argument( + '--force', + '-f', + help='Force database initialization (for "init" command).', + action='store_true') arguments = parser.parse_args() @@ -30,13 +43,12 @@ date = arguments.date if date: date = dateutil.parser.parse(date).date() -from f1elo.interface import Interface interface = Interface(date) if command == 'reset': interface.reset(_debug=arguments.v) elif command == 'rate': - interface.rate( _debug=arguments.v) + interface.rate(_debug=arguments.v) elif command == 'init': interface.init_db(force=arguments.force) sys.exit(0) diff --git a/f1elo/db.py b/f1elo/db.py index 554a061..58e7a8a 100644 --- a/f1elo/db.py +++ b/f1elo/db.py @@ -5,13 +5,16 @@ import __main__ from sqlalchemy import create_engine, event from sqlalchemy.orm import sessionmaker -config = json.load(open(path.join(path.dirname(__main__.__file__), 'config', 'db.json'))) +config = json.load( + open(path.join(path.dirname(__main__.__file__), 'config', 'db.json'))) if config['engine'] == 'mysql': - engine = create_engine("mysql://{0[user]}:{0[pass]}@{0[host]}/{0[db]}?charset=utf8".format(config)) + engine = create_engine( + "mysql://{0[user]}:{0[pass]}@{0[host]}/{0[db]}?charset=utf8".format(config)) elif config['engine'] == 'sqlite': engine = create_engine("sqlite:///{0[file]}".format(config)) + def fk_pragma(conn, record): - conn.execute('PRAGMA FOREIGN_KEYS=ON'); + conn.execute('PRAGMA FOREIGN_KEYS=ON') event.listen(engine, 'connect', fk_pragma) Session = sessionmaker(bind=engine) diff --git a/f1elo/elo.py b/f1elo/elo.py index 6a007d8..51c9e23 100644 --- a/f1elo/elo.py +++ b/f1elo/elo.py @@ -7,9 +7,11 @@ from f1elo.model import * class Elo: + def __init__(self, session): self.session = session - self.config = json.load(open(path.join(path.dirname(__main__.__file__), 'config', 'elo.json'))) + self.config = json.load( + open(path.join(path.dirname(__main__.__file__), 'config', 'elo.json'))) def get_ranking(self, driver, rank_date=None): rank = driver.get_ranking(rank_date) @@ -31,7 +33,12 @@ class Elo: if e.result_group: entries_to_compare.append(e) for c in combinations(entries_to_compare, 2): - score = self.get_score(rankings[c[0]] - rankings[c[1]], self.get_outcome(c), self.get_importance(race, [rankings[c[0]], rankings[c[1]]])) + score = self.get_score( + rankings[c[0]] - rankings[c[1]], + self.get_outcome(c), + self.get_importance(race, + [rankings[c[0]], + rankings[c[1]]])) new_rankings[c[0]] += score new_rankings[c[1]] -= score return new_rankings @@ -45,7 +52,6 @@ class Elo: return base_importance * 0.75 return base_importance / 2 - def get_outcome(self, entries): if entries[0].result_group < entries[1].result_group: return 1 diff --git a/f1elo/interface.py b/f1elo/interface.py index 6cb8fc6..de472b5 100644 --- a/f1elo/interface.py +++ b/f1elo/interface.py @@ -1,16 +1,17 @@ from __future__ import print_function import datetime -import dateutil.relativedelta import sys -from sqlalchemy import MetaData - +import dateutil.relativedelta from f1elo.db import Session from f1elo.elo import Elo from f1elo.model import * +from sqlalchemy import MetaData + class Interface: + def __init__(self, date=None): self.session = Session() self.date = date @@ -69,8 +70,8 @@ class Interface: for entry, rank in ranks.iteritems(): correction = rank / len(entry.drivers) for driver in entry.drivers: - if not driver_ranks.has_key(driver): - driver_ranks[driver] = 0; + if driver not in driver_ranks: + driver_ranks[driver] = 0 driver_ranks[driver] += correction for driver, rank in driver_ranks.iteritems(): ranking = Ranking() @@ -81,7 +82,12 @@ class Interface: if _debug: for entry in race.entries: - print(entry, elo.get_entry_ranking(entry, race.date), elo.get_entry_ranking(entry), file=sys.stderr) + print( + entry, + elo.get_entry_ranking(entry, + race.date), + elo.get_entry_ranking(entry), + file=sys.stderr) print('', file=sys.stderr) race.ranked = True @@ -104,11 +110,16 @@ class Interface: date += datetime.timedelta(1) one_year = dateutil.relativedelta.relativedelta(years=1) - rankings = self.session.query(Ranking).filter(Ranking.rank_date > (date - one_year)).filter(Ranking.rank_date <= date).all() + rankings = self.session.query( + Ranking).filter( + Ranking.rank_date > ( + date - one_year)).filter( + Ranking.rank_date <= date).all( + ) drivers = {} for ranking in rankings: - if not drivers.has_key(ranking.driver): + if ranking.driver not in drivers: drivers[ranking.driver] = ranking.driver.get_ranking(date) self.date = date diff --git a/f1elo/model.py b/f1elo/model.py index 2fc4c0c..7757564 100644 --- a/f1elo/model.py +++ b/f1elo/model.py @@ -5,6 +5,7 @@ from sqlalchemy.types import Boolean, Date, Float, Integer, String Base = declarative_base() + class Driver(Base): __tablename__ = 'drivers' @@ -12,7 +13,12 @@ class Driver(Base): driver = Column(String(1024)) country = Column(String(255)) - rankings = relationship('Ranking', order_by='Ranking.rank_date', back_populates='driver', cascade="all", passive_deletes=True) + rankings = relationship( + 'Ranking', + order_by='Ranking.rank_date', + back_populates='driver', + cascade="all", + passive_deletes=True) def __repr__(self): return (u"<%s (#%d)>" % (self.driver, self.id)).encode('utf8') @@ -27,7 +33,7 @@ class Driver(Base): @staticmethod def fetch(name, country, session): - driver = session.query(Driver).filter(Driver.driver==name).first() + driver = session.query(Driver).filter(Driver.driver == name).first() if not driver: driver = Driver() driver.driver = name @@ -36,10 +42,23 @@ class Driver(Base): return driver driver_entry = Table('driver_entries', Base.metadata, - Column('_driver', Integer, ForeignKey('drivers.id', onupdate="CASCADE", ondelete="CASCADE")), - Column('_entry', Integer, ForeignKey('entries.id', onupdate="CASCADE", ondelete="CASCADE")), + Column( + '_driver', + Integer, + ForeignKey( + 'drivers.id', + onupdate="CASCADE", + ondelete="CASCADE")), + Column( + '_entry', + Integer, + ForeignKey( + 'entries.id', + onupdate="CASCADE", + ondelete="CASCADE")), Column('id', Integer, primary_key=True)) + class Entry(Base): __tablename__ = 'entries' @@ -48,14 +67,27 @@ class Entry(Base): car_no = Column(String(255)) result_group = Column(Integer) - _race = Column(Integer, ForeignKey('races.id', onupdate="CASCADE", ondelete="CASCADE")) - race = relationship('Race', back_populates='entries', order_by=result_group) - - drivers = relationship('Driver', secondary=driver_entry, cascade="all", passive_deletes=True) + _race = Column( + Integer, + ForeignKey( + 'races.id', + onupdate="CASCADE", + ondelete="CASCADE")) + race = relationship( + 'Race', + back_populates='entries', + order_by=result_group) + + drivers = relationship( + 'Driver', + secondary=driver_entry, + cascade="all", + passive_deletes=True) def __repr__(self): return ('#%s (%s) %s[%d]' % (self.car_no, ', '.join([driver.__repr__().decode('utf8') for driver in self.drivers]), self.result, self.result_group)).encode('utf8') + class Race(Base): __tablename__ = 'races' @@ -64,14 +96,28 @@ class Race(Base): date = Column(Date) ranked = Column(Boolean, default=False) - _type = Column(Integer, ForeignKey('race_types.id', onupdate="CASCADE", ondelete="CASCADE")) - type = relationship('RaceType', back_populates='races', order_by='Race.date') - - entries = relationship('Entry', back_populates='race', order_by='Entry.result_group', cascade="all", passive_deletes=True) + _type = Column( + Integer, + ForeignKey( + 'race_types.id', + onupdate="CASCADE", + ondelete="CASCADE")) + type = relationship( + 'RaceType', + back_populates='races', + order_by='Race.date') + + entries = relationship( + 'Entry', + back_populates='race', + order_by='Entry.result_group', + cascade="all", + passive_deletes=True) def __repr__(self): return ('%s (%s)' % (self.race, self.date)).encode('utf8') + class RaceType(Base): __tablename__ = 'race_types' @@ -79,11 +125,16 @@ class RaceType(Base): code = Column(String(255)) description = Column(String(1024)) - races = relationship('Race', back_populates='type', cascade="all", passive_deletes=True) + races = relationship( + 'Race', + back_populates='type', + cascade="all", + passive_deletes=True) def __repr__(self): return ('%s (%s)' % (self.description, self.code)).encode('utf8') + class Ranking(Base): __tablename__ = 'rankings' @@ -91,8 +142,16 @@ class Ranking(Base): rank_date = Column(Date) ranking = Column(Float) - _driver = Column(Integer, ForeignKey('drivers.id', onupdate="CASCADE", ondelete="CASCADE")) - driver = relationship('Driver', back_populates='rankings', order_by=rank_date) + _driver = Column( + Integer, + ForeignKey( + 'drivers.id', + onupdate="CASCADE", + ondelete="CASCADE")) + driver = relationship( + 'Driver', + back_populates='rankings', + order_by=rank_date) def __repr__(self): return ("%s: %0.2f (%s)" % (self.driver.__repr__().decode('utf8'), self.ranking, self. rank_date)).encode('utf8') |