#!/usr/bin/env python # -*- coding: utf8 -*- # Python program to create # password-protected bonus files # Create pdf using FPDF # Encrypt PDF using pyPDF2 # Get file name and password import PyPDF2 import random import string from fpdf import FPDF from pathlib import Path import math def createPassword(length): #characters = string.ascii_letters + string.digits + string.punctuation characters = string.ascii_letters + string.digits password = ''.join(random.choice(characters) for i in range(length)) return password def coinSide(length): characters = ("H","T") winningSides = ''.join(random.choice(characters) for i in range(length)) return winningSides def cardNumbers(numTasks): cards = {i: [None]*numTasks for i in range(1,11)} numList = list(range(1,11)) for i in range(numTasks): shuffled = numList for k in range(1000): ## Shuffle 1000 times shuffled = random.sample(shuffled,10) for key in cards: tmp = cards[key] tmp[i] = shuffled[key-1] cards[key] = tmp return cards def protectPDF(inputfile, password, outputfile): pdfFile = open(inputfile, 'rb') # Create reader and writer object pdfReader = PyPDF2.PdfFileReader(pdfFile) pdfWriter = PyPDF2.PdfFileWriter() # Add all pages to writer (accepted answer results into blank pages) for pageNum in range(pdfReader.numPages): pdfWriter.addPage(pdfReader.getPage(pageNum)) # Encrypt with your password pdfWriter.encrypt(password) # Write it to an output file. (you can delete unencrypted version now) resultPdf = open(outputfile, 'wb') pdfWriter.write(resultPdf) resultPdf.close() def verificationPDF(inputfile, bonusFileDict =[], winningSideDict =[], cardNumbersDict =[], password_bonusFile = "Loewe50", includeCards = False): class PDF(FPDF): def footer(self): # Go to 1.5 cm from bottom self.set_y(-15) # Select Arial italic 8 self.set_font('DejaVu', '', 8) # Print centered page number self.cell(0, 10, 'Page %s' % self.page_no(), 0, 0, 'C') def titles(self, text): self.set_xy(0.0,0.0) self.set_font('DejaVu', 'B', 16) self.cell(w=210.0, h=40.0, align='C', txt=text, border=0) th = self.font_size self.ln(th) def subTitles(self, text): y_pos = self.get_y() self.set_xy(left, y_pos) self.set_font('DejaVu', 'B', 14) th = 1.2 * (self.font_size) self.multi_cell(w=pdf_w - 2 * left , h=th, align='L', txt=text, border=0) self.ln(th) def write_multicell(self, startx, text): starty = self.get_y() if starty <= top: starty = top self.set_xy(startx, starty) self.set_font('DejaVu', '', 12) th = self.font_size self.multi_cell(w=pdf_w - 2 * left , h=th * 1.2, align='J', txt=text, border=0) self.ln(th) def write_multicell_with_styles(self, startx, text_list, color = None): starty = self.get_y() self.set_font('DejaVu', '', 14) if starty <= top: starty = top self.set_xy(startx, starty) th = self.font_size #loop through differenct sections in different styles for text_part in text_list: #check and set style try: current_style = text_part['style'] self.set_font('DejaVu', current_style, 12) except KeyError: self.set_font('DejaVu', '', 12) if color: line_break = False if '\n' in text_part['text']: line_break = True # Need to use cells for writing text to achieve coloring self.set_fill_color(240, 240, 240) #loop through words and write them down space_width = self.get_string_width(' ') for word in text_part['text'].split(): current_pos = self.get_x() word_width = self.get_string_width(word + " ") + space_width #check for newline if (current_pos + word_width) > (pdf_w - left): self.cell(w = 0, h= th * 1.3, fill=True) self.set_y(self.get_y() + th * 1.2) self.set_x(startx) if int(round(self.get_x(),0)) != startx: self.cell(word_width, th * 1.3, " " + word, fill=True) else: self.cell(word_width, th * 1.3, word, fill=True) # Add line if last element and line break symbol if line_break and word == list(text_part['text'].split())[-1]: self.cell(w = 0, h= th * 1.3, fill=True) self.ln(th * 1.2) if text_part == text_list[-1]: ## Fill Last Line self.cell(w = 0, h= th * 1.3, fill=True) else: self.write(th * 1.2 , text_part['text']) self.ln(th) def enumeration(self, justify, number, text): self.set_font('DejaVu', '', 12) startx = self.get_x() + justify starty = self.get_y() th = self.font_size self.set_xy(startx, starty) self.cell(w=justify, h=th*1.2, align='L', txt=number, border=0) self.set_xy(startx + justify, starty) self.multi_cell(w=0, h=th * 1.2, align='L', txt=text, border=0) self.ln(th) def subEnumeration(self, justify, text): self.set_font('DejaVu', '', 12) startx = self.get_x() + 2 * justify starty = self.get_y() th = self.font_size self.set_xy(startx, starty) self.cell(w=justify, h=th * 1.2, align='L', txt="-", border=0) self.set_xy(startx + justify/2, starty) self.multi_cell(w=0, h=th*1.2, align='L', txt=text, border=0) self.ln(th) def table1(self, data): numFiles = len(list(data.keys())) numRows = math.ceil(numFiles / 2) self.set_line_width(0.5) th = self.font_size col_width = (pdf_w - 2 * left) / 4 # Four columns self.set_font('DejaVu', 'B', 12) self.cell(w=col_width, h=th*1.2, align='L', txt="Table 1: Content of the bonus files", border=0) self.ln(th * 1.5) startx = self.get_x() starty = self.get_y() self.line(startx, starty, startx+pdf_w - 2*left ,starty) self.cell(w=col_width, h=th*2, align='C', txt="Bonus file", border=0) self.cell(w=col_width, h=th*2, align='C', txt="Task", border=0) self.cell(w=col_width, h=th*2, align='C', txt="Bonus file", border=0) self.cell(w=col_width, h=th*2, align='C', txt="Task", border=0) self.line(startx, starty + th*2, startx + pdf_w - 2*left, starty + th*2) self.ln(th*2) tablex = self.get_x() tabley = self.get_y() self.set_font('DejaVu', '', 10) num = 1 for key, value in data.items(): if (num % 2) == 0: self.set_fill_color(240, 240, 240) else: self.set_fill_color(255, 255, 255) bonusFileName = "BonusFile" + str(key) + ".pdf" taskName = "" task = value[0] if task == "walking": taskName = task.capitalize() + " Task " + str(value[2]) elif task == "decoloring": taskName = task.capitalize() + " Task " self.cell(w=col_width, h=th*1.4, align='C', txt=bonusFileName, fill=True) self.cell(w=col_width, h=th*1.4, align='C', txt=taskName, fill=True) self.ln(th*1.4) if num == numRows: self.set_xy(tablex + 2 * col_width, tabley) elif num > numRows: self.set_x(tablex + 2 * col_width) num += 1 if (numRows * 2 > numBonusFiles): # Need to add one more row without content if (num % 2) == 0: self.set_fill_color(240, 240, 240) else: self.set_fill_color(255, 255, 255) self.cell(w=col_width, h=th*1.4, align='C', txt="", fill=True) self.cell(w=col_width, h=th*1.4, align='C', txt="", fill=True) self.ln(th*1.4) starty = self.get_y() self.line(startx, starty, startx+pdf_w - 2*left ,starty) self.set_fill_color(255, 255, 255) def winningSideTable(self, title, colheaders, data): self.set_line_width(0.5) th = self.font_size col_width = (pdf_w - 2 * left) / (len(colheaders) + 1) self.set_font('DejaVu', 'B', 12) self.cell(w=col_width, h=th*1.2, align='L', txt=title, border=0) self.ln(th * 1.5) startx = self.get_x() starty = self.get_y() self.line(startx, starty, startx+pdf_w - 2*left ,starty) self.cell(w=col_width * 2, h=th*2, align='C', txt="", border=0) self.cell(w=col_width * (len(colheaders)-1), h=th*2, align='C', txt="Repetition", border=0) self.ln(th*1.5) self.cell(w=col_width * 2, h=th*2, align='C', txt=colheaders[0], border=0) for head in colheaders[1:]: self.cell(w=col_width, h=th*2, align='C', txt=head, border=0) self.line(startx, starty + th*3, startx + pdf_w - 2*left, starty + th*3) self.ln(th*2) self.set_font('DejaVu', '', 10) num = 1 for key, value in data.items(): taskName = "" if key == "decoloring": taskName="Decoloring Task" else: taskName="Walking Task " + str(key) if (num % 2) == 0: self.set_fill_color(240, 240, 240) else: self.set_fill_color(255, 255, 255) self.cell(w=col_width * 2, h=th*1.5, align='C', txt=str(taskName), fill=True) for winningSide in value: self.cell(w=col_width, h=th*1.5, align='C', txt=str(winningSide), fill=True) self.ln(th*1.5) num += 1 startx = self.get_x() starty = self.get_y() self.line(startx, starty, startx+pdf_w - 2*left ,starty) self.set_fill_color(255, 255, 255) def cardNumbersTable(self, colheaders, data, tableNumber): self.set_line_width(0.5) self.set_font('DejaVu', '', 10) th = self.font_size col_width = (pdf_w - 2 * left) / (len(colheaders)+1) # We need more space for first column for task, cardNumbers in data.items(): # Table header self.set_font('DejaVu', 'B', 12) title = "Table " + str(tableNumber) + ": Winning numbers for the card draws in " tableNumber += 1 # Task Name taskName = task.split("_") if len(taskName) == 2: taskName = taskName[0] + " Task " + taskName[1] else: taskName = "the " + taskName[0] + " Task " # write title self.write(txt=title + taskName) self.ln(th * 2) # Draw table startx = self.get_x() starty = self.get_y() self.line(startx, starty, startx + pdf_w - 2 * left ,starty) # Table header self.cell(w=col_width * 2, h=th*2, align='C', txt="", border=0) self.cell(w=col_width * (len(colheaders)-1), h=th*2, align='C', txt="Card", border=0) self.ln(th*2) for head in colheaders: if head == "Repetition": self.cell(w=col_width * 2, h=th*2, align='C', txt=head, border=0) else: self.cell(w=col_width, h=th*2, align='C', txt=head, border=0) self.ln(th*2) starty = self.get_y() self.line(startx, starty, startx + pdf_w - 2 * left ,starty) # Write Content -- Columnswise self.set_font('DejaVu', '', 10) for i in range(5): # Write repetitions if (i + 1) % 2 == 0: self.set_fill_color(240, 240, 240) else: self.set_fill_color(255, 255, 255) self.cell(w=col_width * 2, h=th*1.4, align='C', txt=str(i +1), fill=True) self.ln(th*1.4) # Go to new line for card, winningSides in cardNumbers.items(): self.set_y(starty) columnx = startx + col_width * 2 + (card-1) * col_width for idx, winningSide in enumerate(winningSides): self.set_x(columnx) if (idx + 1) % 2 == 0: self.set_fill_color(240, 240, 240) else: self.set_fill_color(255, 255, 255) self.cell(w=col_width, h=th*1.4, align='C', txt=str(winningSide), fill=True) self.ln(th*1.4) # Go to new line startx = self.get_x() starty = self.get_y() self.line(startx, starty, startx + pdf_w - 2 * left ,starty) self.set_fill_color(255, 255, 255) ## Make space before next table self.ln(5 * th) if self.get_y() >= 250: self.add_page() pdf = PDF() #our pdf object # Define width pdf_w=210 #A4 page is 240mm wide left = 10 top = 30 enumerationIndent = 10 text_height=14 option_height = 60 # Add Fonts in the usual style format #pdf.add_font('DejaVu','','/Users/maximilianvoigt/Documents/Studium/PhD/Ebert/RWExp/font/DejaVuSans.ttf', uni=True) import os cwd = os.getcwd() fonts = [ ('DejaVu', '', cwd + '/font/DejaVuSans.ttf'), ('DejaVu', 'B', cwd + '/font/DejaVuSans-Bold.ttf'), ('DejaVu', 'I', cwd + '/font/DejaVuSans-Oblique.ttf'), ('DejaVu', 'BI', cwd + '/font//DejaVuSans-BoldOblique.ttf'), ] for font in fonts: pdf.add_font(font[0], font[1], font[2], uni=True) pdf.set_font('DejaVu', '', 14) pdf.add_page() pdf.titles("Verification.pdf") intro_text = [{'text':'This document allows you to '}, {'style':'U','text':'personally verify'}, {'text':' that your bonus was determined fairly, exactly as described during the experiment. \n'}, {'text':'Specifically, you can verify the following:\n'}] pdf.write_multicell_with_styles(left, intro_text) pdf.ln() pdf.enumeration(enumerationIndent,"1.", "Your bonus task was selected fairly, because the set of bonus files that you chose from was not manipulated:") pdf.subEnumeration(enumerationIndent, "Had you chosen a different bonus file, a different task would have been your bonus task.") pdf.subEnumeration(enumerationIndent, "Each task was equally likely to be your bonus task, because each task was contained in exactly one bonus file.") pdf.enumeration(enumerationIndent,"2.", "Each (repeated) single risk that determined your bonus was resolved fairly, exactly as described during the experiment.") text = [{'style':'U','text':'Note:'}, {'text':' Because you downloaded this document at the beginning of the experiment, its content cannot depend on your behavior later in the experiment. That is, we―the experimenters―were committed to the content of this document before we knew what you would do. Had we given you a low bonus regardless of your instructions, this untruthfulness would become apparent now. This is the reason why this document allows you to verify our truthfulness.\n'},] pdf.write_multicell_with_styles(left, text) pdf.ln() numBonusFiles = len(list(bonusFileDict.keys())) taskName = "" taskType = list(bonusFileDict.values())[0][0] if taskType == "walking": taskName = taskType.capitalize() + " Task " + str(list(bonusFileDict.values())[0][2]) elif taskType == "decoloring": taskName = "the " + taskType.capitalize() + " Task" pdf.subTitles("1. Proof that your bonus task was selected fairly, exactly as described during the experiment") text = [ {'text': 'During the experiment, you downloaded one out of ' + str(numBonusFiles) + ' bonus files. '}, {'text': 'Using Table 1 below, you can verify that the bonus file that you downloaded shows your bonus task. '}, {'text': 'If you downloaded BonusFile' + str(list(bonusFileDict.keys())[0]) + ".pdf"}, {'text':', for example, you can verify that your bonus task is indeed ' + taskName + ". "}, {'text': 'Because each task from the experiment belongs to exactly one bonus file, each task was equally likely to be your bonus task.'}, ] pdf.write_multicell_with_styles(left, text) pdf.ln() pdf.table1(bonusFileDict) ### Risk Resolution pdf.add_page() pdf.subTitles("2. Proof that all bonus-relevant risks were resolved fairly, exactly as described during the experiment") # Differentiate between tasks with and without card draws if not includeCards: # Without card draws text = [ {'text': "This section explains how you can verify that the outcomes of the single risks were determined fairly. "}, {'text':"Remember that you downloaded this file before resolving any risk, i.e., before we knew the outcomes of your coin toss(es) ('H' or 'T').\n"}, ] pdf.write_multicell_with_styles(left, text) pdf.ln() text = [ {'text':'Each single risk that could have been relevant for your bonus was resolved using a coin toss. '}, {'text':"Before the experiment, we fixed a winning side ('H' or 'T') for every (possibly) bonus-relevant coin toss. "}, {'text':"For each single risk, we asked you to enter the outcome of a coin toss ('H' or 'T'), which you may have determined by physically tossing a coin or not. "}, {'text':'If the outcome of your coin toss was also the winning side, you won the single risk. '}, {'text':'If the outcome of your coin toss was different to the winning side, you lost the single risk. '}, {'text':'Table 2 below shows the winning sides for all risks in all tasks of the experiment.\n '}, ] pdf.write_multicell_with_styles(left, text) text = [ {'style':'U','text':'Example.'}, {'text':'Suppose, for example, that your bonus task was the Decoloring Task. '}, {'text': "'" + winningSideDict['decoloring'][0] + "' is the winning side for the first repetition of the single risk during the Decoloring Task and" }, {'text':'you can verify that this is written in the respective cell in Table 2.'}, {'text':"If you entered '" + winningSideDict['decoloring'][0] + "' as the outcome of this coin toss, you won the first single risk. "}, {'text':"If, instead, you entered '" + ('T' if (winningSideDict['decoloring'][0] == 'H') else 'H') + "', you lost the first single risk."}, ] pdf.write_multicell_with_styles(left, text, 'grey') pdf.ln() table_title = "Table 2: Winning sides for all coin tosses" table_headers = ["Task", "1", "2", "3", "4", "5"] table_data = winningSideDict pdf.winningSideTable(table_title, table_headers, table_data) else: # With card draws text = [ {'text': "This section explains how you can verify that the outcomes of the single risks and card draws were determined fairly. "}, {'text':"Remember that you downloaded this file before resolving any risk, i.e., before we knew the outcomes of your coin toss(es) ('H' or 'T') or which cards you would choose.\n"}, ] pdf.write_multicell_with_styles(left, text) pdf.ln() text = [ {'style':'B','text':'Single risks. '}, {'text':'Each single risk that could have been relevant for your bonus was resolved using a coin toss. '}, {'text':"Before the experiment, we fixed a winning side ('H' or 'T') for every (possibly) bonus-relevant coin toss. "}, {'text':"For each single risk, we asked you to enter the outcome of a coin toss ('H' or 'T'), which you may have determined by physically tossing a coin or not. "}, {'text':'If the outcome of your coin toss was also the winning side, you won the single risk. '}, {'text':'If the outcome of your coin toss was different to the winning side, you lost the single risk. '}, {'text':'Table 2 below shows the winning sides for all risks in all tasks of the experiment.\n '}, ] pdf.write_multicell_with_styles(left, text) text = [ {'style':'U','text':'Example.'}, {'text':'Suppose, for example, that your bonus task was the Decoloring Task. '}, {'text': "'" + winningSideDict['decoloring'][0] + "' is the winning side for the first repetition of the single risk during the Decoloring Task and" }, {'text':'you can verify that this is written in the respective cell in Table 2.'}, {'text':"If you entered '" + winningSideDict['decoloring'][0] + "' as the outcome of this coin toss, you won the first single risk. "}, {'text':"If, instead, you entered '" + ('T' if (winningSideDict['decoloring'][0] == 'H') else 'H') + "', you lost the first single risk."}, ] pdf.write_multicell_with_styles(left, text, 'grey') pdf.ln() table_title = "Table 2: Winning sides for all coin tosses" table_headers = ["Task", "1", "2", "3", "4", "5"] table_data = winningSideDict pdf.winningSideTable(table_title, table_headers, table_data) pdf.add_page() text = [ {'style':'B','text':'Card draws. '}, {'text':'You also had to draw cards to determine if risk-taking continues or stops. '}, {'text':'To draw a card, you clicked one out of ten cards numbered from 1 to 10. '}, {'text':'The card you clicked then turned around and revealed whether it is white (“continue risk-taking”) or black (“stop risk-taking”). '}, {'text':'This outcome (black or white) was determined as follows:\n'}, ] pdf.write_multicell_with_styles(left, text) pdf.ln() pdf.enumeration(enumerationIndent,"-", "For concreteness, assume that you instructed the computer to stop risk-taking at a numbered ball with a probability of 60%.") pdf.enumeration(enumerationIndent,"-", "Before the experiment, we assigned a second number―the winning number―to each card. The winning numbers also range from 1 to 10 and each of the ten cards has a different winning number.") pdf.enumeration(enumerationIndent,"-", "If the winning number of the card you drew was smaller than or equal to the tens place of the stopping probability (6 in this example with 60% stopping probability), risk-taking stopped. If, instead, the winning number was larger than the tens place of the stopping probability, risk-taking continued. Because there are 6 winning numbers from 1 to 10 that are smaller than or equal to 6, the stopping probability is indeed 60%.") text = [ {'text':'The winning numbers for all card draws are shown in the tables on the next pages. '}, {'text':'Each number between 1 and 10 appears exactly once in each row of each table. '}, {'text':'For this reason, and because we could not know which card you would draw, each winning number was equally likely.\n'}, ] pdf.write_multicell_with_styles(left, text) pdf.ln() # must determine if which card is white and which is black for text below black = [] white = [] for card, winniningNumbers in cardNumbersDict["Walking_1"].items(): if winniningNumbers[0] < 4 and len(black) == 0: # We use smaller for text black = [card, winniningNumbers[0]] elif winniningNumbers[0] > 4 and len(white) == 0: white = [card, winniningNumbers[0]] text = [ #{'text':'\n'}, {'style':'U','text':'Example.'}, {'text':' Suppose that you instructed the computer to stop risk-taking at the first repetition of Walking Task 1 with a probability of 40% '}, {'text':'(that is, you numbered the first ball in the ball triangle with a "40"). '}, {'text':'The second row of Table 3 on the next page (the table for Walking Task 1) shows you the winning numbers for all cards that you could have drawn. '}, {'text':'If you drew card number'}, {'text': '"' + str(black[0]) + '",'}, {'text': 'for example, then the winning number is'}, {'text': '"' + str(black[1]) + '."'}, {'text': 'Because this winning number is smaller than 4 (the tens place of the stopping probability), the card turned black and risk-taking stopped. '}, {'text':'If, in contrast, you drew card number'}, {'text': '"' + str(white[0]) + '",'}, {'text': 'the winning number is'}, {'text': '"' + str(white[1]) + '."'}, {'text': 'Because this winning number is larger than 4, the card turned white and risk-taking continued.'}, ] pdf.write_multicell_with_styles(left, text, 'grey') pdf.add_page() table_headers = [str(i) if i > 0 else "Repetition" for i in range(11)] pdf.cardNumbersTable(table_headers, cardNumbersDict, tableNumber = 3) pdf.output(inputfile, 'F') def createVerificationPDF( bonusFileDict, winningSideDict, cardNumbersDict, password = "Loewe50", password_bonusFile = "Loewe50", id = 1000, cardDraws = False, ): # Create Folder if not existing folder = "_static/bonusFiles/" + str(id) + "/" folder_no_pwd = "_static/bonusFiles/" + str(id) + "/no_pwd/" Path(folder).mkdir(parents=True, exist_ok=True) Path(folder_no_pwd).mkdir(parents=True, exist_ok=True) inputfile = folder_no_pwd + "Verification.pdf" outputfile = folder + "Verification.pdf" verificationPDF(inputfile, bonusFileDict, winningSideDict, cardNumbersDict,password_bonusFile, includeCards = True) protectPDF(inputfile, password, outputfile) ## For testing -- Replicate pipeline as in initi.py ''' import bonusFilesBetween as bf # Script for bonus files import treatments bfData = treatments.basic password_verification = "Loewe50" password_bonus = "Loewe50" firstTask = bfData[0][0] repetitionsSecondTask = bfData[1][2] winningSideDict = { (key+1): coinSide(5) for key in range(repetitionsSecondTask) } winningSideDict[firstTask] = coinSide(5) ## Dictionary with card draws cardNumbersDict = { 'Walking_' + str(i+1): cardNumbers(5) for i in range(repetitionsSecondTask) } cardNumbersDict["Decoloring"] = cardNumbers(5) # Write verification PDF bonusFileDict = bf.createBonusFilePDFs( bfData, password_bonus, 1000) createVerificationPDF( bonusFileDict, winningSideDict, cardNumbersDict, password_verification, password_bonus, 1000 ) '''