summaryrefslogtreecommitdiff
path: root/src/bidding_data.py
diff options
context:
space:
mode:
authoremkael <emkael@tlen.pl>2015-09-01 20:53:02 +0200
committeremkael <emkael@tlen.pl>2015-09-01 20:53:02 +0200
commit1c3fb72ac83e96e765a65186bb5e371dd1e5ebcb (patch)
tree7305de5b016878129f6f70424205fe5c54002f44 /src/bidding_data.py
parent663b0ce431635ad98908fd53689205d61f21b56f (diff)
* build tools and environment
Diffstat (limited to 'src/bidding_data.py')
-rw-r--r--src/bidding_data.py303
1 files changed, 303 insertions, 0 deletions
diff --git a/src/bidding_data.py b/src/bidding_data.py
new file mode 100644
index 0000000..ae789f1
--- /dev/null
+++ b/src/bidding_data.py
@@ -0,0 +1,303 @@
+import sys
+import glob
+import re
+import pypyodbc
+
+from os import path
+from bs4 import BeautifulSoup as bs4
+
+
+class JFRBidding:
+
+ # alignment of the bidding table
+ __directions = ['W', 'N', 'E', 'S']
+
+ # converts BWS lineup data to
+ # {round}.{sector}_{table}.{pair numbers} structure
+ def __parse_lineup_data(self, sitting_data):
+ round_lineups = {}
+ for sitting in sitting_data[1:]:
+ round_no = sitting[2]
+ table_no = str(sitting[0]) + '_' + str(sitting[1])
+ if round_no not in round_lineups:
+ round_lineups[round_no] = {}
+ round_lineups[round_no][table_no] = sorted([
+ sitting[3], sitting[4]])
+ return round_lineups
+
+ # converts BWS bidding to the structure:
+ # {board}_{round}_{sector}_{table}.{sector}_{table}.{round} -> {bidding}[],
+ # including erased calls
+ def __parse_bidding_data(self, bidding_data):
+ bids = {}
+ for bid in bidding_data[1:]:
+ round_no = bid[3]
+ table_no = str(bid[1]) + '_' + str(bid[2])
+ board_no = str(bid[4]) + '_' + str(round_no) + '_' + table_no
+ bid_counter = bid[5]
+ bid_erased = bid[10]
+ if board_no not in bids:
+ bids[board_no] = {}
+ if table_no not in bids[board_no]:
+ bids[board_no][table_no] = {}
+ if round_no not in bids[board_no][table_no]:
+ bids[board_no][table_no][round_no] = {}
+ if bid_erased == 1:
+ if bid_counter in bids[board_no][table_no][round_no]:
+ if bids[board_no][table_no][round_no][bid_counter][
+ 'direction'] == bid[6]:
+ bids[board_no][table_no][round_no].pop(
+ bid_counter, None)
+ if len(bids[board_no][table_no][round_no]) == 0:
+ bids[board_no][table_no].pop(round_no, None)
+ else:
+ bids[board_no][table_no][round_no][bid_counter] = {
+ 'direction': bid[6], 'bid': bid[7]}
+ return bids
+
+ # converts bidding data into HTML table
+ def __format_bidding(self, bidding):
+ bid_match = re.compile('(\d)([SHDCN])')
+ html_output = bs4('<table>', 'lxml')
+ header_row = html_output.new_tag('tr')
+ html_output.table.append(header_row)
+ for direction in self.__directions:
+ header_cell = html_output.new_tag('th')
+ header_cell.string = direction
+ header_row.append(header_cell)
+ for bid_round in bidding:
+ bidding_row = html_output.new_tag('tr')
+ html_output.table.append(bidding_row)
+ for bid in bid_round:
+ bid_cell = html_output.new_tag('td')
+ call_match = re.match(bid_match, bid)
+ if call_match:
+ bid_cell.append(call_match.group(1))
+ bid_icon = html_output.new_tag(
+ 'img', src='images/' + call_match.group(2) + '.gif')
+ bid_cell.append(bid_icon)
+ else:
+ bid_cell.append(bid)
+ bidding_row.append(bid_cell)
+ return html_output.table.prettify()
+
+ # returns file path for bidding HTML output
+ # {prefix}_bidding_{jfr_board_number}_{pair_numbers}.txt
+ def __get_bidding_file_output_path(self,
+ board_no,
+ round_no=None,
+ table_no=None,
+ pair_numbers=None):
+ return u'{0}_bidding_{1:03}_{2}.txt'.format(
+ self.__tournament_prefix, board_no,
+ '_'.join(map(str,
+ self.__round_lineups[round_no][table_no]
+ if pair_numbers is None # read numbers from lineup
+ else pair_numbers))) # or use provided numbers
+
+ def __map_board_numbers(self):
+ self.__tournament_files = [
+ f for f
+ in glob.glob(self.__tournament_prefix + '*.html')
+ if re.search(self.__tournament_files_match, f)]
+ for round_data in self.__lineup_data:
+ # 13th column has JFR number for the first board
+ if len(round_data) > 12:
+ jfr_number = round_data[12]
+ round_no = round_data[2]
+ sector_no = round_data[0]
+ table_no = round_data[1]
+ if jfr_number and round_no:
+ # 5th and 6th - actual board number
+ for board_number in range(int(round_data[5]),
+ int(round_data[6])+1):
+ board_string = '_'.join([
+ str(board_number),
+ str(round_no),
+ str(sector_no),
+ str(table_no)])
+ self.__board_number_mapping[
+ board_string
+ ] = jfr_number + board_number - round_data[5]
+ # only include these board numbers from mapping
+ # which actually exist in JFR output
+ custom_files = []
+ for b_number, jfr_number in self.__board_number_mapping.iteritems():
+ board_files = [
+ f for f
+ in self.__tournament_files
+ if f.endswith('{0:03}.html'.format(jfr_number))]
+ if len(board_files):
+ custom_files = custom_files + board_files
+ else:
+ self.__board_number_mapping[b_number] = None
+ self.__tournament_files = custom_files
+
+ # sitting read from BWS
+ __round_lineups = {}
+ # bidding read from BWS
+ __bids = {}
+
+ # full path + JFR prefix
+ __tournament_prefix = ''
+ # RegEx matching board HTML files
+ __tournament_files_match = None
+ # matched files, including board number mapping boundaries
+ __tournament_files = []
+
+ # BWS number -> JFR number mapping
+ __board_number_mapping = {}
+
+ def __init__(self, bws_file, file_prefix):
+ with pypyodbc.win_connect_mdb(bws_file) as connection:
+ cursor = connection.cursor()
+ self.__lineup_data = cursor.execute(
+ 'SELECT * FROM RoundData').fetchall()
+ bid_data = cursor.execute('SELECT * FROM BiddingData').fetchall()
+ self.__round_lineups = self.__parse_lineup_data(self.__lineup_data)
+ self.__bids = self.__parse_bidding_data(bid_data)
+ self.__tournament_prefix = path.splitext(
+ path.realpath(file_prefix))[0]
+ self.__tournament_files_match = re.compile(
+ re.escape(self.__tournament_prefix) + '([0-9]{3})\.html')
+ self.__map_board_numbers()
+
+ def write_bidding_tables(self):
+ for board_no, board_data in self.__bids.items():
+ if board_no in self.__board_number_mapping:
+ for table_no, table_data in board_data.items():
+ for round_no, round_data in table_data.items():
+ if round_no in self.__round_lineups:
+ if table_no in self.__round_lineups[round_no]:
+ bidding = sorted(round_data)
+ dealer = round_data[bidding[0]]['direction']
+ bidding_table = [[], [], [], []]
+ # compile bidding player-by-player
+ for bid_index in bidding:
+ bid = round_data[bid_index]
+ bidding_table[
+ self.__directions.index(
+ bid['direction'])
+ ].append(bid['bid'])
+ last_bidder = bid['direction']
+ # fill skipped calls for players before dealer
+ # in the first round of bidding
+ for pos in range(
+ 0, self.__directions.index(dealer)):
+ bidding_table[pos].insert(0, '')
+ # fill skipped calls for players after pass out
+ # (so that bidding table is a proper matrix)
+ for pos in range(
+ self.__directions.index(last_bidder),
+ len(self.__directions)):
+ bidding_table[pos].append('')
+ # transpose the bidding table
+ # aligning it row-by-row (bid round-by-round)
+ bidding_table = map(list, zip(*bidding_table))
+ bidding_fpath = \
+ self.__get_bidding_file_output_path(
+ self.__board_number_mapping[board_no],
+ round_no, table_no)
+ with file(bidding_fpath, 'w') as bidding_file:
+ bidding_file.write(
+ self.__format_bidding(bidding_table))
+
+ def write_bidding_scripts(self):
+ for tournament_file in self.__tournament_files:
+ with file(tournament_file, 'r+') as board_html:
+ board_content = bs4(board_html, 'lxml', from_encoding='utf-8')
+ header_scripts = board_content.select('head script')
+ # check for jQuery, append if necessary
+ jquery_scripts = [script for script in header_scripts
+ if script['src'] == 'javas/jquery.js']
+ if not len(jquery_scripts):
+ jquery = board_content.new_tag(
+ 'script', src='javas/jquery.js',
+ type='text/javascript')
+ jquery_scripts.append(jquery)
+ board_content.head.append(jquery)
+ # check for bidding.js
+ bidding_scripts = [
+ script for script in header_scripts
+ if script['src'] == 'javas/bidding.js']
+ # and make sure bidding.js is appended after jQuery
+ for script in bidding_scripts:
+ script.extract()
+ bidding_script = board_content.new_tag(
+ 'script', src='javas/bidding.js',
+ type='text/javascript')
+ jquery_scripts[0].insert_after(bidding_script)
+ board_html.seek(0)
+ board_html.write(board_content.prettify(
+ 'utf-8', formatter='html'))
+ board_html.truncate()
+
+ def write_bidding_links(self):
+ for tournament_file in self.__tournament_files:
+ file_number = re.match(
+ self.__tournament_files_match,
+ tournament_file).group(1)
+ board_text_path = path.splitext(tournament_file)[0] + '.txt'
+ with file(board_text_path, 'r+') as board_text:
+ board_text_content = bs4(
+ board_text, 'lxml', from_encoding='iso-8859-2')
+ for row in board_text_content.select('tr'):
+ cells = row.select('td')
+ # traveller table rows for specific score entries
+ # should have 11 cells
+ if len(cells) == 11:
+ try:
+ pair_numbers = sorted([
+ int(cells[1].contents[0]),
+ int(cells[2].contents[0])])
+ except ValueError:
+ continue
+ bidding_link = board_text_content.new_tag(
+ 'a', href='#', **{'class': 'biddingLink'})
+ bidding_link.string = ' '
+ bidding_link['data-bidding-link'] = path.basename(
+ self.__get_bidding_file_output_path(
+ int(file_number, 10),
+ pair_numbers=pair_numbers))
+ # only append link if we've got bidding data
+ if path.isfile(path.join(
+ path.dirname(self.__tournament_prefix),
+ bidding_link['data-bidding-link'])):
+ # fourth cell is the contract
+ for link in cells[3].select('a.biddingLink'):
+ link.extract()
+ cells[3].append(bidding_link)
+ board_text.seek(0)
+ board_text.write(board_text_content.table.prettify(
+ 'iso-8859-2', formatter='html'))
+ board_text.truncate()
+
+if __name__ == '__main__':
+ import argparse
+
+ argument_parser = argparse.ArgumentParser(
+ description='Display bidding data from BWS files on JFR Pary pages')
+
+ def file_path(filepath):
+ filepath = unicode(filepath, sys.getfilesystemencoding())
+ if path.isfile(filepath):
+ return filepath
+ else:
+ argument_parser.error('File %s does not exist' % filepath)
+
+ argument_parser.add_argument('bws_file', metavar='BWS_FILE',
+ help='path to BWS file',
+ type=file_path)
+ argument_parser.add_argument('path', metavar='PATH',
+ help='tournament path (to PREFIX.html)',
+ type=file_path)
+
+ arguments = argument_parser.parse_args()
+
+ bidding_parser = JFRBidding(
+ bws_file=arguments.bws_file,
+ file_prefix=arguments.path,
+ )
+ bidding_parser.write_bidding_tables()
+ bidding_parser.write_bidding_scripts()
+ bidding_parser.write_bidding_links()