diff --git a/README.md b/README.md index c18aca1..38ef1d5 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,6 @@ Development for fsTimer, free, open source software for race timing. See fstimer.org for more information and for stable releases. +fsTimer is written in Python3 and uses GTK3+ via PyGObject. + Contact admin@fstimer.org if you would like to become a collaborator. diff --git a/documentation/documentation_figs/fstimer_fig1.png b/documentation/documentation_figs/fstimer_fig1.png index 6da45c1..a863b19 100644 Binary files a/documentation/documentation_figs/fstimer_fig1.png and b/documentation/documentation_figs/fstimer_fig1.png differ diff --git a/documentation/documentation_figs/fstimer_fig11.png b/documentation/documentation_figs/fstimer_fig11.png index da97632..f4b4d74 100644 Binary files a/documentation/documentation_figs/fstimer_fig11.png and b/documentation/documentation_figs/fstimer_fig11.png differ diff --git a/documentation/documentation_figs/fstimer_fig12.png b/documentation/documentation_figs/fstimer_fig12.png index e41563c..7f60443 100644 Binary files a/documentation/documentation_figs/fstimer_fig12.png and b/documentation/documentation_figs/fstimer_fig12.png differ diff --git a/documentation/documentation_figs/fstimer_fig13.png b/documentation/documentation_figs/fstimer_fig13.png index 01d953d..572a57e 100644 Binary files a/documentation/documentation_figs/fstimer_fig13.png and b/documentation/documentation_figs/fstimer_fig13.png differ diff --git a/documentation/documentation_figs/fstimer_fig14.png b/documentation/documentation_figs/fstimer_fig14.png index 9f109b0..d882d68 100644 Binary files a/documentation/documentation_figs/fstimer_fig14.png and b/documentation/documentation_figs/fstimer_fig14.png differ diff --git a/documentation/documentation_figs/fstimer_fig15.png b/documentation/documentation_figs/fstimer_fig15.png index d574b29..d578a39 100644 Binary files a/documentation/documentation_figs/fstimer_fig15.png and b/documentation/documentation_figs/fstimer_fig15.png differ diff --git a/documentation/documentation_figs/fstimer_fig16.png b/documentation/documentation_figs/fstimer_fig16.png index aa40de2..754a204 100644 Binary files a/documentation/documentation_figs/fstimer_fig16.png and b/documentation/documentation_figs/fstimer_fig16.png differ diff --git a/documentation/documentation_figs/fstimer_fig17.png b/documentation/documentation_figs/fstimer_fig17.png index ff3d82f..777c9dc 100644 Binary files a/documentation/documentation_figs/fstimer_fig17.png and b/documentation/documentation_figs/fstimer_fig17.png differ diff --git a/documentation/documentation_figs/fstimer_fig18.png b/documentation/documentation_figs/fstimer_fig18.png index ef082aa..5876cad 100644 Binary files a/documentation/documentation_figs/fstimer_fig18.png and b/documentation/documentation_figs/fstimer_fig18.png differ diff --git a/documentation/documentation_figs/fstimer_fig19.png b/documentation/documentation_figs/fstimer_fig19.png index 8a2ac46..9ccd039 100644 Binary files a/documentation/documentation_figs/fstimer_fig19.png and b/documentation/documentation_figs/fstimer_fig19.png differ diff --git a/documentation/documentation_figs/fstimer_fig2.png b/documentation/documentation_figs/fstimer_fig2.png index 80453da..4135c3e 100644 Binary files a/documentation/documentation_figs/fstimer_fig2.png and b/documentation/documentation_figs/fstimer_fig2.png differ diff --git a/documentation/documentation_figs/fstimer_fig20.png b/documentation/documentation_figs/fstimer_fig20.png deleted file mode 100644 index 2e3e90a..0000000 Binary files a/documentation/documentation_figs/fstimer_fig20.png and /dev/null differ diff --git a/documentation/documentation_figs/fstimer_fig21.png b/documentation/documentation_figs/fstimer_fig21.png index b77f90f..ce07fe5 100644 Binary files a/documentation/documentation_figs/fstimer_fig21.png and b/documentation/documentation_figs/fstimer_fig21.png differ diff --git a/documentation/documentation_figs/fstimer_fig24.png b/documentation/documentation_figs/fstimer_fig24.png deleted file mode 100644 index e660af5..0000000 Binary files a/documentation/documentation_figs/fstimer_fig24.png and /dev/null differ diff --git a/documentation/documentation_figs/fstimer_fig27.png b/documentation/documentation_figs/fstimer_fig27.png index 24666a6..e995c82 100644 Binary files a/documentation/documentation_figs/fstimer_fig27.png and b/documentation/documentation_figs/fstimer_fig27.png differ diff --git a/documentation/documentation_figs/fstimer_fig3.png b/documentation/documentation_figs/fstimer_fig3.png index c12c5f6..4c72773 100644 Binary files a/documentation/documentation_figs/fstimer_fig3.png and b/documentation/documentation_figs/fstimer_fig3.png differ diff --git a/documentation/documentation_figs/fstimer_fig30.png b/documentation/documentation_figs/fstimer_fig30.png index 76c87ac..858ca9d 100644 Binary files a/documentation/documentation_figs/fstimer_fig30.png and b/documentation/documentation_figs/fstimer_fig30.png differ diff --git a/documentation/documentation_figs/fstimer_fig31.png b/documentation/documentation_figs/fstimer_fig31.png index d0be4d7..e5c84ca 100644 Binary files a/documentation/documentation_figs/fstimer_fig31.png and b/documentation/documentation_figs/fstimer_fig31.png differ diff --git a/documentation/documentation_figs/fstimer_fig32.png b/documentation/documentation_figs/fstimer_fig32.png deleted file mode 100644 index 467ea0a..0000000 Binary files a/documentation/documentation_figs/fstimer_fig32.png and /dev/null differ diff --git a/documentation/documentation_figs/fstimer_fig33.png b/documentation/documentation_figs/fstimer_fig33.png index 445e331..5d5dc24 100644 Binary files a/documentation/documentation_figs/fstimer_fig33.png and b/documentation/documentation_figs/fstimer_fig33.png differ diff --git a/documentation/documentation_figs/fstimer_fig34.png b/documentation/documentation_figs/fstimer_fig34.png index 1a952af..30af9e8 100644 Binary files a/documentation/documentation_figs/fstimer_fig34.png and b/documentation/documentation_figs/fstimer_fig34.png differ diff --git a/documentation/documentation_figs/fstimer_fig35.png b/documentation/documentation_figs/fstimer_fig35.png index 057e351..f71d503 100644 Binary files a/documentation/documentation_figs/fstimer_fig35.png and b/documentation/documentation_figs/fstimer_fig35.png differ diff --git a/documentation/documentation_figs/fstimer_fig36.png b/documentation/documentation_figs/fstimer_fig36.png index 8d5710c..5a6a72c 100644 Binary files a/documentation/documentation_figs/fstimer_fig36.png and b/documentation/documentation_figs/fstimer_fig36.png differ diff --git a/documentation/documentation_figs/fstimer_fig37.png b/documentation/documentation_figs/fstimer_fig37.png index 9c4f49d..67feb8d 100644 Binary files a/documentation/documentation_figs/fstimer_fig37.png and b/documentation/documentation_figs/fstimer_fig37.png differ diff --git a/documentation/documentation_figs/fstimer_fig38.png b/documentation/documentation_figs/fstimer_fig38.png index 1692943..1267e4f 100644 Binary files a/documentation/documentation_figs/fstimer_fig38.png and b/documentation/documentation_figs/fstimer_fig38.png differ diff --git a/documentation/documentation_figs/fstimer_fig39.png b/documentation/documentation_figs/fstimer_fig39.png index 53c6ebb..e6cfb41 100644 Binary files a/documentation/documentation_figs/fstimer_fig39.png and b/documentation/documentation_figs/fstimer_fig39.png differ diff --git a/documentation/documentation_figs/fstimer_fig40.png b/documentation/documentation_figs/fstimer_fig40.png index 9756359..6992fbd 100644 Binary files a/documentation/documentation_figs/fstimer_fig40.png and b/documentation/documentation_figs/fstimer_fig40.png differ diff --git a/documentation/documentation_figs/fstimer_fig41.png b/documentation/documentation_figs/fstimer_fig41.png index ec581cf..712d18b 100644 Binary files a/documentation/documentation_figs/fstimer_fig41.png and b/documentation/documentation_figs/fstimer_fig41.png differ diff --git a/documentation/documentation_figs/fstimer_fig43.png b/documentation/documentation_figs/fstimer_fig43.png deleted file mode 100644 index bbbee83..0000000 Binary files a/documentation/documentation_figs/fstimer_fig43.png and /dev/null differ diff --git a/documentation/documentation_figs/fstimer_fig45.png b/documentation/documentation_figs/fstimer_fig45.png index 1276b89..0d44fb0 100644 Binary files a/documentation/documentation_figs/fstimer_fig45.png and b/documentation/documentation_figs/fstimer_fig45.png differ diff --git a/documentation/documentation_figs/fstimer_fig47.png b/documentation/documentation_figs/fstimer_fig47.png index f604320..ccd622c 100644 Binary files a/documentation/documentation_figs/fstimer_fig47.png and b/documentation/documentation_figs/fstimer_fig47.png differ diff --git a/documentation/documentation_figs/fstimer_fig48.png b/documentation/documentation_figs/fstimer_fig48.png index 1672637..e96f195 100644 Binary files a/documentation/documentation_figs/fstimer_fig48.png and b/documentation/documentation_figs/fstimer_fig48.png differ diff --git a/documentation/documentation_figs/fstimer_fig49.png b/documentation/documentation_figs/fstimer_fig49.png index 2d115bf..432897f 100644 Binary files a/documentation/documentation_figs/fstimer_fig49.png and b/documentation/documentation_figs/fstimer_fig49.png differ diff --git a/documentation/documentation_figs/fstimer_fig5.png b/documentation/documentation_figs/fstimer_fig5.png index 15ff3f2..8e98e04 100644 Binary files a/documentation/documentation_figs/fstimer_fig5.png and b/documentation/documentation_figs/fstimer_fig5.png differ diff --git a/documentation/documentation_figs/fstimer_fig50.png b/documentation/documentation_figs/fstimer_fig50.png index 76bf0ae..d8b7ff8 100644 Binary files a/documentation/documentation_figs/fstimer_fig50.png and b/documentation/documentation_figs/fstimer_fig50.png differ diff --git a/documentation/documentation_figs/fstimer_fig51.png b/documentation/documentation_figs/fstimer_fig51.png index 9c5148e..6b11450 100644 Binary files a/documentation/documentation_figs/fstimer_fig51.png and b/documentation/documentation_figs/fstimer_fig51.png differ diff --git a/documentation/documentation_figs/fstimer_fig52.png b/documentation/documentation_figs/fstimer_fig52.png index 0ea1117..372a46d 100644 Binary files a/documentation/documentation_figs/fstimer_fig52.png and b/documentation/documentation_figs/fstimer_fig52.png differ diff --git a/documentation/documentation_figs/fstimer_fig53.png b/documentation/documentation_figs/fstimer_fig53.png index 148e22b..e18540e 100644 Binary files a/documentation/documentation_figs/fstimer_fig53.png and b/documentation/documentation_figs/fstimer_fig53.png differ diff --git a/documentation/documentation_figs/fstimer_fig54.png b/documentation/documentation_figs/fstimer_fig54.png index ba02e8c..acbde4b 100644 Binary files a/documentation/documentation_figs/fstimer_fig54.png and b/documentation/documentation_figs/fstimer_fig54.png differ diff --git a/documentation/documentation_figs/fstimer_fig57.png b/documentation/documentation_figs/fstimer_fig57.png index befc0a4..429a14c 100644 Binary files a/documentation/documentation_figs/fstimer_fig57.png and b/documentation/documentation_figs/fstimer_fig57.png differ diff --git a/documentation/documentation_figs/fstimer_fig59.png b/documentation/documentation_figs/fstimer_fig59.png index 50518e1..635534b 100644 Binary files a/documentation/documentation_figs/fstimer_fig59.png and b/documentation/documentation_figs/fstimer_fig59.png differ diff --git a/documentation/documentation_figs/fstimer_fig6.png b/documentation/documentation_figs/fstimer_fig6.png index 60fd667..6169c2d 100644 Binary files a/documentation/documentation_figs/fstimer_fig6.png and b/documentation/documentation_figs/fstimer_fig6.png differ diff --git a/documentation/documentation_figs/fstimer_fig60.png b/documentation/documentation_figs/fstimer_fig60.png index 1b4cada..a496223 100644 Binary files a/documentation/documentation_figs/fstimer_fig60.png and b/documentation/documentation_figs/fstimer_fig60.png differ diff --git a/documentation/documentation_figs/fstimer_fig61.png b/documentation/documentation_figs/fstimer_fig61.png index 108d231..f5cce60 100644 Binary files a/documentation/documentation_figs/fstimer_fig61.png and b/documentation/documentation_figs/fstimer_fig61.png differ diff --git a/documentation/documentation_figs/fstimer_fig62.png b/documentation/documentation_figs/fstimer_fig62.png index 2165f13..00f1257 100644 Binary files a/documentation/documentation_figs/fstimer_fig62.png and b/documentation/documentation_figs/fstimer_fig62.png differ diff --git a/documentation/documentation_figs/fstimer_fig63.png b/documentation/documentation_figs/fstimer_fig63.png index 1e7dec2..b3fad7d 100644 Binary files a/documentation/documentation_figs/fstimer_fig63.png and b/documentation/documentation_figs/fstimer_fig63.png differ diff --git a/documentation/documentation_figs/fstimer_fig64.png b/documentation/documentation_figs/fstimer_fig64.png index faf9aad..4e55e7f 100644 Binary files a/documentation/documentation_figs/fstimer_fig64.png and b/documentation/documentation_figs/fstimer_fig64.png differ diff --git a/documentation/documentation_figs/fstimer_fig65.png b/documentation/documentation_figs/fstimer_fig65.png index 295a1a2..f309583 100644 Binary files a/documentation/documentation_figs/fstimer_fig65.png and b/documentation/documentation_figs/fstimer_fig65.png differ diff --git a/documentation/documentation_figs/fstimer_fig66.png b/documentation/documentation_figs/fstimer_fig66.png index ea9efa2..8345b06 100644 Binary files a/documentation/documentation_figs/fstimer_fig66.png and b/documentation/documentation_figs/fstimer_fig66.png differ diff --git a/documentation/documentation_figs/fstimer_fig67.png b/documentation/documentation_figs/fstimer_fig67.png index e7f54d1..50d9ada 100644 Binary files a/documentation/documentation_figs/fstimer_fig67.png and b/documentation/documentation_figs/fstimer_fig67.png differ diff --git a/documentation/documentation_figs/fstimer_fig68.png b/documentation/documentation_figs/fstimer_fig68.png index 960955a..c54c810 100644 Binary files a/documentation/documentation_figs/fstimer_fig68.png and b/documentation/documentation_figs/fstimer_fig68.png differ diff --git a/documentation/documentation_figs/fstimer_fig69.png b/documentation/documentation_figs/fstimer_fig69.png index 3db4c99..2f048b6 100644 Binary files a/documentation/documentation_figs/fstimer_fig69.png and b/documentation/documentation_figs/fstimer_fig69.png differ diff --git a/documentation/documentation_figs/fstimer_fig7.png b/documentation/documentation_figs/fstimer_fig7.png index e7ffffe..0b41cce 100644 Binary files a/documentation/documentation_figs/fstimer_fig7.png and b/documentation/documentation_figs/fstimer_fig7.png differ diff --git a/documentation/documentation_figs/fstimer_fig70.png b/documentation/documentation_figs/fstimer_fig70.png index 26816e2..a763589 100644 Binary files a/documentation/documentation_figs/fstimer_fig70.png and b/documentation/documentation_figs/fstimer_fig70.png differ diff --git a/documentation/documentation_figs/fstimer_fig72.png b/documentation/documentation_figs/fstimer_fig72.png index 31f43c2..efe5ca1 100644 Binary files a/documentation/documentation_figs/fstimer_fig72.png and b/documentation/documentation_figs/fstimer_fig72.png differ diff --git a/documentation/documentation_figs/fstimer_fig73.png b/documentation/documentation_figs/fstimer_fig73.png new file mode 100644 index 0000000..4ecec40 Binary files /dev/null and b/documentation/documentation_figs/fstimer_fig73.png differ diff --git a/documentation/documentation_figs/fstimer_fig74.png b/documentation/documentation_figs/fstimer_fig74.png new file mode 100644 index 0000000..1fd69cf Binary files /dev/null and b/documentation/documentation_figs/fstimer_fig74.png differ diff --git a/documentation/documentation_figs/fstimer_fig75.png b/documentation/documentation_figs/fstimer_fig75.png new file mode 100644 index 0000000..5a6121f Binary files /dev/null and b/documentation/documentation_figs/fstimer_fig75.png differ diff --git a/documentation/documentation_figs/fstimer_fig76.png b/documentation/documentation_figs/fstimer_fig76.png new file mode 100644 index 0000000..8cf4b1c Binary files /dev/null and b/documentation/documentation_figs/fstimer_fig76.png differ diff --git a/documentation/documentation_figs/fstimer_fig77.png b/documentation/documentation_figs/fstimer_fig77.png new file mode 100644 index 0000000..f805152 Binary files /dev/null and b/documentation/documentation_figs/fstimer_fig77.png differ diff --git a/documentation/documentation_figs/fstimer_fig78.png b/documentation/documentation_figs/fstimer_fig78.png new file mode 100644 index 0000000..612a5b3 Binary files /dev/null and b/documentation/documentation_figs/fstimer_fig78.png differ diff --git a/documentation/documentation_figs/fstimer_fig8.png b/documentation/documentation_figs/fstimer_fig8.png index 720e498..8750fc8 100644 Binary files a/documentation/documentation_figs/fstimer_fig8.png and b/documentation/documentation_figs/fstimer_fig8.png differ diff --git a/documentation/documentation_figs/fstimer_fig9.png b/documentation/documentation_figs/fstimer_fig9.png deleted file mode 100644 index 555aac7..0000000 Binary files a/documentation/documentation_figs/fstimer_fig9.png and /dev/null differ diff --git a/documentation/documentation_sec2.htm b/documentation/documentation_sec2.htm index 72780a1..67890c4 100644 --- a/documentation/documentation_sec2.htm +++ b/documentation/documentation_sec2.htm @@ -21,7 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
2.6 Creating a new project
-Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
diff --git a/documentation/documentation_sec2_1.htm b/documentation/documentation_sec2_1.htm index eaa5bba..772756f 100644 --- a/documentation/documentation_sec2_1.htm +++ b/documentation/documentation_sec2_1.htm @@ -21,10 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
2.6 Creating a new project
-Section 3 Checklist for timing with fsTimer
- +Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
-From the main fsTimer window, there are four main steps in timing a race: preregistration, registration, compilation, and finally the actual timing. Each of these forms an individual component of fsTimer, and will generally be done in order.
+From the main fsTimer window, there are four main steps in timing a race: (optionally) importing pre-registration data from a spreadsheet, registration, compilation, and finally the actual timing. Each of these forms an individual component of fsTimer.
-In addition to these components, the main fsTimer window has a "help" menu that leads to general information about fsTimer, including the GPLv3 license under which the software is released.

+In addition to these components, the main fsTimer window has a menu that leads to general information about fsTimer, including the GPLv3 license under which the software is released.

You enter a module by simply clicking on the corresponding button from the main window. We will now give a description of each of these modules.

diff --git a/documentation/documentation_sec2_2.htm b/documentation/documentation_sec2_2.htm index 61a9029..d132119 100644 --- a/documentation/documentation_sec2_2.htm +++ b/documentation/documentation_sec2_2.htm @@ -21,10 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
2.6 Creating a new project
-Section 3 Checklist for timing with fsTimer
- +Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
-From the preregister window, we can import a csv (comma separated value) spreadsheet of registration information into fsTimer. The idea is to be able to import registration information that was collected prior to race day, for example online preregistration. The csv will look something like this when opened in a spreadsheet program like Excel or LibreOffice:

+From the import window, we can import a csv (comma separated value) spreadsheet of registration information into fsTimer. The idea is to be able to import registration information that was collected prior to race day, for example online preregistration. The csv will look something like this when opened in a spreadsheet program like Excel or LibreOffice:



diff --git a/documentation/documentation_sec2_3.htm b/documentation/documentation_sec2_3.htm index b431600..b832bb5 100644 --- a/documentation/documentation_sec2_3.htm +++ b/documentation/documentation_sec2_3.htm @@ -21,10 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
2.6 Creating a new project
-Section 3 Checklist for timing with fsTimer
- +Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
-The computer registration number is important when using multiple computers for simultaneous registration, for instance, day-of. A separate registration database will be created by each registration computer, and in order to make it easier to track these databases and merge them you can assign each computer used in registration a unique number. The database file saved by each computer will have the registration number in the filename so it will be easy to keep track of all of the databases. We just leave the registration number as 1 for now. Selecting a pre-registration file is optional. If you do not select a pre-registration file, then you will be presented with an empty registration database. In order to include the pre-registration information that we imported from the csv in the preregister window, we have to select that file ("fstimer_demo_registration_prereg.json").

+The computer registration number keeps track of the different files when using multiple computers for simultaneous registration, for instance, day-of. The database file saved by each computer will have the registration number in the filename so it will be easy to keep track of them all. Selecting a pre-registration file is optional. If you do not select a pre-registration file, then you will be presented with an empty registration database. In order to include the pre-registration information that we imported from the csv in the preregister window, we have to select that file ("fstimer_demo_registration_prereg.json") and press "OK."



We have now loaded up the pre-registration database and can edit existing entries and add new entries. Editing an entry (for instance, to add a bib number) is done by selecting the entry and pressing "Edit." This opens a new window that allows you to edit any of that entry's information:

-

- -In order to find a particular entry in the database (for instance, to add an ID number to a racer that preregistered) you can simply type his or her last name into the field at the top of the registration window to filter all of the results by last name:

- -

+

-New registration entries can easily be added by pressing the "New" button. The "Add family" button is used to add a family member for an existing database entry. Selecting an entry and pressing "Add family" will bring up the same window as for a new entry, except with the last name, address, email, and other information pre-filled in. The only fields that have been cleared are first name, ID number, Age, and Gender, since these will differ for each of the family members. This saves alot of time re-typing addresses and phone numbers and such for a large group of people who share this information.

+New registration entries can easily be added by pressing the "New" button.

-When finished making changes to the registration database, press "Save" to save the database to a file. At the bottom of the window it will tell you the filename the database was saved to. After we have saved, we press "Done" to close the registration window.

+When finished making changes to the registration database, press "Save" to save the database to a file. At the bottom of the window it will tell you the filename the database was saved to. After we have saved, we press "Close" to close the registration window.

Continue on to Section 2.4 Compiling registrations. diff --git a/documentation/documentation_sec2_4.htm b/documentation/documentation_sec2_4.htm index b47e5cb..8b0d80a 100644 --- a/documentation/documentation_sec2_4.htm +++ b/documentation/documentation_sec2_4.htm @@ -21,10 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
2.6 Creating a new project
-Section 3 Checklist for timing with fsTimer
- +Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
-The timing dictionary is what allows the racer times to be correlated to the registration database entries. During timing, we assign race times to racer ID numbers (that is, bib numbers) as they cross the finish line. The timing dictionary is the database that looks up registration information (name, age, gender) by ID. Press "Choose file" and select the timing dictionary that we created at the Compile window (fstimer_demo_timing_dict.json). The "pass" ID is used to leave a marked time "blank", without any bib ID assigned to it. -This will be useful for a number of scenarios that are described in Section 3.1. We usually use 0 (as in, the number zero) as the pass ID, just be sure that 0 isn't one of your bib numbers. By default, spacebar will be used to mark times as runners cross the finish line but this can be changed to one of a few options.

+The timing dictionary is what allows the racer times to be correlated to the registration database entries. During timing, we assign race times to racer ID numbers (that is, bib numbers) as they cross the finish line. The timing dictionary is the database that looks up registration information (name, age, gender, etc.) by ID. Press "Choose file" and select the timing dictionary that we created at the Compile window (fstimer_demo_timing_dict.json). The "pass" ID is used to leave a marked time "blank", without any bib ID assigned to it. +This will be useful for a number of scenarios that are described in Section 3.1. We usually use 0 (the number zero) as the pass ID, just be sure that 0 isn't one of your bib numbers. By default, spacebar will be used to mark times as runners cross the finish line but this can be changed to one of a few options.

Pressing "OK" leads us to the timing window.

@@ -53,27 +50,27 @@

Section 2.5 - Race timing


We press "Start!" right when the race begins, and this starts the clock ticking in the software.

There are two main steps to timing: marking the times, and entering in the corresponding bib IDs. When each racer crosses the finish line, we press spacebar right when they cross and this saves the time at which they crossed. Also, the ID number of each racer that crosses must be stored in the database. -When a large crowd of racers come all at the same time, it is not feasible to enter their ID numbers as fast as they cross the finish line; thus these two jobs can be done separately, in parallel. In Section 3 we give a suggested way of operating the finish line, in which one person is responsible for marking the times for everyone that crosses, while other people are responsible for maintaining a record of the bib numbers in the order in which people crossed, and then entering those into the computer (for instance, using a barcode scanner). As long as people are checked in (that is, their IDs are entered into the system) in the same order as they cross the finish line, fsTimer will match up all of the IDs to their corresponding times.

+When a crowd of racers come all at the same time, it may not feasible to enter their ID numbers as fast as they cross the finish line; thus these two jobs can be done separately, in parallel. In Section 3 we give some tips on how to operate the finish line. As long as a time is marked for each runner and the IDs are entered into the system in the same order, fsTimer will match up the IDs to their corresponding times.

-To make this a little more concrete, we give an example here. Pressing spacebar to mark times puts "blank" times into the record, meaning, a time that does not yet have an ID associated with it. Here we have marked 3 times, and you can see that the "ID" column is blank for each time:

+Pressing spacebar to mark times puts "blank" times into the record, meaning, a time that does not yet have an ID associated with it. Here we have marked 3 times, and you can see that the "ID" column is blank for each time:



-This is the stack of marked times, with the oldest (fastest) time on the bottom and the most recent time on the top. We now give fsTimer the ID (bib) numbers associated with these three times. We just type the numbers in and press "Enter." Or, if we have a barcode scanner, we scan the barcode. fsTimer will associate the IDs to the times in the order that we enter them. So entering IDs "100", "101," and then "103" (without the quotation marks or commas of course) gives:

+This is the stack of marked times, with the oldest (fastest) time on the bottom and the most recent time on the top. We now give fsTimer the ID (bib) numbers associated with these three times. We just type the numbers in and press "Enter." Or, if we have a barcode scanner, we scan the barcode. fsTimer will associate the IDs to the times in the order that we enter them. So entering IDs 101, 102, and then 104 gives:



-That is really the key to timing: Mark the times when people cross the finish line, and then enter in the IDs in the same order that they crossed. Notice that if there is a large rush of people, you can focus on marking all of the times, and then come back and enter the IDs whenever things open up a bit; there is no hurry.

+That is really all there is to the timing: Mark the times when people cross the finish line, and then enter in the IDs in the same order that they crossed. Notice that if there is a large rush of people, you can focus on marking all of the times, and then come back and enter the IDs whenever things open up a bit; there is no hurry.

-Pressing "Save" will save the current results to a file that can be loaded by fsTimer. Pressing "Print" will generate nicely formatted results files that will show the times along with names, ages, and genders. "Print" does not actually physically print the files, rather it saves them as html files that you can open with any web browser and then physically print from there:

+Pressing "Save" will save the current results to a file that can be loaded by fsTimer. Pressing "Printouts" will generate nicely formatted results files that will show the times along with registration info for the runner, such as name, age, and gender. The results are saved as html files in the project directory that you can open with any web browser and then physically print from there:

-

+

"Print" will automatically generate two html files in fact: Overall results that include everyone, and divisional results. Note that if you enter the pass ID (0) as the bib number for a time, then that time will not show up in the results at all. Basically the pass ID "skips" times, and leaves them out of the results.

The other buttons (Options, Drop ID, Drop time, and Edit) will be described in Section 4.5.

-Press "Done" to exit the timing window. At this point the race will have finished, the results printed, and timing is done! Now that you have a general idea of how the software works, we will describe creating a new project, and then will go into details about both the process of timing the race, and details about the software.

+Press "Close" to exit the timing window. At this point the race will have finished, the results will be printed, and timing is done! Now that you have a general idea of how the software works, we will describe creating a new project, and then will go into details about both the process of timing the race, and details about the software.

Continue on to Section 2.6 Creating a new project. diff --git a/documentation/documentation_sec2_6.htm b/documentation/documentation_sec2_6.htm index 49151d2..7f60842 100644 --- a/documentation/documentation_sec2_6.htm +++ b/documentation/documentation_sec2_6.htm @@ -21,7 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
2.6 Creating a new project
-Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
@@ -43,7 +43,7 @@

Section 2.6 - Creating a new project




-First you will choose a name for the project. As it says, only letters, numbers, and underscore may be used in the project name. This will create a subdirectory in the fsTimer directory using the project name. That is, if the project name is project_name, it will create the directory fstimer/project_name. All of the files generated by fsTimer will be saved in that project directory.

+First you will choose a name for the project. As it says, only letters, numbers, and underscore may be used in the project name. This will create a subdirectory in the fsTimer directory using the project name. That is, if the project name is project_name, it will create the directory fsTimer/project_name. All of the files generated by fsTimer will be saved in that project directory.



@@ -51,17 +51,20 @@

Section 2.6 - Creating a new project




-The next thing to be specified are the registration fields. These are exactly the fields that will show up in the "Registration" window. Name, ID (bib number), age, and gender are required. If it is a handicap race then Handicap is required. Anything else is optional. New fields can be added as either an entrybox, meaning a box into which any information can be typed (like for name), or a combobox where one of a few options must be selected (like for gender).

+The next thing to be specified are the registration fields. These are exactly the fields that will show up in the "Registration" window. ID is a required field and must be filled out for runners to show up in results, but otherwise the fields can be customized to those which you want to use. New fields can be added as either an entrybox, meaning a box into which any information can be typed (like for name), or a combobox where one of a few options must be selected (like for gender).

-

+

-Recall the "Add family" (member) button in the Registration window. Basically this button performs the same task as the "New" button (creates a new registration database entry), however it pre-fills some of the fields, for instance so that you don't have to keep typing the address for a list of people that are all in the same family. In this window, you can specify for your project which fields should be cleared. Generally first name, ID, age, and gender will be cleared as these are not shared by family members.

+When you press the "Printouts" button from the Timing window, fsTimer will automatically generate two nicely formatted sets of results: overall results listing everyone, and automatically generated divisional results. This is the window where you can specify what the divisions should be for generating the divisional results. You specify the division name, and what the parameters for the division should be. The parameters can be a range of ages, and/or a collection of combobox values.

-

+

+ +In the next window you can specify which fields to include in the results printouts. You can include or not include any of the registration fields defined earlier, Time, Pace (calculated using the specified distance), or any custom expression. Section 4.1 explains how custom expressions can be used.

+ +

-When you press the "Print" button from the Timing window, fsTimer will automatically generate two nicely formatted sets of results: overall results listing everyone, and automatically generated divisional results. This is the window in fsTimer where you can specify what the divisions should be for generating the divisional results. You should coordinate with the race director what divisions they want (for instance, it may depend on how many medals they want to buy).

+Finally, you can choose how the results in the printouts should be ranked, for both overall results and for divisional results. Typically this will be Time, but you can make it whatever you want it to be (including any custom expression defined on the previous window).

-You specify the division name, and what the parameters for the division should be. The parameters can be a range of ages, and/or a collection of combobox values.

And that's all there is to starting a new fsTimer project!

diff --git a/documentation/documentation_sec3.htm b/documentation/documentation_sec3.htm index 5e35fb2..afdba5a 100644 --- a/documentation/documentation_sec3.htm +++ b/documentation/documentation_sec3.htm @@ -1,14 +1,13 @@ - + -fsTimer documentation - Section 3: Checklist for timing with fsTimer +fsTimer documentation - Section 3.1: Suggestions for race setup - +
-

fsTimer documentation

+

fsTimer documentation

@@ -22,8 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
--> -Section 3 Checklist for timing with fsTimer
-
3.1 Suggestions for race setup
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
+ +We tear the bib bottoms after people cross the finish line and can keep them stacked, producing a record of the order in which everyone crossed. We stack them on a dowel stand like this:

+ +

+ +Using a barcode scanner to enter the bib IDs into fsTimer is a great way to speed up the process and minimize errors. We use a cheap ($20) USB barcode scanner (this one). We print barcode labels and attach them to the bib bottoms (see here for printable barcodes). Of course, using barcodes and a barcode scanner is entirely optional; you can always enter the bib IDs just using the keyboard or a numpad.

+ +

Pre-registration and registration


+ +Racers appreciate not having to wait in a long registration line the morning of the race, and allowing racers to pre-register helps alot. We allow racers to pre-register on our website, and then enter the information into a spreadsheet which is then imported into fsTimer before the day of the race. Information could also be entered directly into fsTimer using the Registration window. We don't assign bib numbers when people register; we simply collect their information so that it is in the database and does not need to be typed in the day of the race.

+ +After pre-registration is closed, we import or enter the information into fsTimer and generate a pre-registration database which is saved into the project subdirectory (the files for a project named project_name will be saved in fstimer/project_name).

+ +fsTimer was designed to allow multiple computers for day-of registration. If there is only one computer for day-of registrations, there will likely be a long queue which means racers will have less fun - nobody likes waiting in line! We have found that the right number of computers (and thus the right number of people helping out at the registration table) is about one per 75 racers, when registration lasts for one hour before the race. Thus, if you are expecting 300 racers at your race, you probably want to have four people day of the race with computers running fsTimer.

+ +Morning of the race, the registration computers are prepared by doing the following actions: -In the next section we give detailed instructions of what the roles of people helping at the registration and finish line would be.

-The day of the race: Setup +Because the pre-registration database is loaded on all of the registration computers, a runner who pre-registered can walk up to any computer and their information will be there. We put a stack of bibs next to each registration computer, as the registration crew will be responsible for assigning bib numbers in fsTimer and handing out the bibs.

+ +When a person walks up to the registration counter, the person running the registration computer will need to follow one of two sets of steps. We train the volunteers at the registration table to do these things:

+ +A pre-registered runner:
+This runner's information is already in the database, but we need to give them a bib and assign the corresponding bib number to their entry in fsTimer. -In the next section we give details about how we like to set up the finish line.

-The day of the race: Registration +A runner that has not pre-registered:
+We need to type in the information for this runner, assign them a bib number, and give them the corresponding bib. -The day of the race: Timing +For individuals that haven't pre-registered, we have paper forms for them to register day-of, and we put these on a separate table away from the registration tables and make people fill the form out before coming to the registration table. This way they don't hold up the line while they stand in front of the computer filling out their registration form.

+ +Bib ID, and any other registration fields that will show up on the result printouts, are very important fields. Errors in registration will mean errors in results, so we ask the registration volunteers to triple check these!

+ +Keeping the queue short the morning of the race is important for keeping things running smoothly, and making sure racers have fun. One thing that we have found makes a huge difference is to actually allow racers to check-in and pick up their bibs the night before the race. We specify a 2-hour period where we will be at the race location, and people can show up and do exactly the same things that they do day-of (pick up their bib if they pre-registered, and register and pick up their bib if not). We found that about half of racers exercised this option, thus cutting the morning-of crowd in half.

+ +

Compiling


+ +We close registration 15 minutes before the race is to begin. Note that in order to be reasonable doing this, it is important to not have a long queue of people still waiting to register - see the above tips on how to keep the line short. If people show up after registration has closed and want to run, it actually isn't a big issue. We give them a bib and they will still show up in the overall results by their bib number, it just won't have their name/age/etc. since they won't be in the registration database.

+ +Because we use multiple computers for the registration, we use a USB flash drive to collect the registration databases from each of the computers. The database file will be in the project subdirectory, and will have in the filename the registration number that you assigned to that computer. (You can see on the bottom of the registration window what the filename is every time you hit "Save"). We put all of the registration databases onto the timing computer, and compile them ("Compile" button in fsTimer).

+ +When compiling, fsTimer will check that no IDs are overloaded (meaning, one bib ID was assigned to multiple people). This can and does happen due to entry error - have the registration volunteers triple check bib ID when they enter it in and hand out the bib! If fsTimer finds this type of error, it will tell you. We generally don't have time to try and find out who actually has that bib number since the race is about to start, so fsTimer will autocorrect the error by removing the ID from all entries (see Section 4.4 for details on the autocorrect). We then sort out who actually had which ID after the timing has finished - it is easy to make these types of edits before printing the final results.

+ +The end result of compilation is the timing dictionary, which is what basically a big table that matches bib IDs to registration data. We take the computer out to the finish line, enter the Timing module, load the timing dictionary we just created, choose a pass ID (we use 0, the number zero) and enter the timing window.

+ +

Timing and results


+ +Note: The tips about operating the finish line below are for races with only one lap, where the racers have a bib bottom that can be torn off. Races with multiple laps will not allow for the bib bottom to be torn off at each lap, and so these tips won't apply. see Section 4.6 gives a description of how lap timing works.

+ +The key to successful timing with fsTimer is to make sure that all of the times are marked, and all of the bib IDs are recorded in the order that they crossed. For races with 100 or fewer runners, this is not too difficult - it is easy to have one person marking times on the computer, and another entering in the bib IDs in the order that they cross. For larger races, things get quite hectic and we have found a more complex strategy is important to handle the load. We have done 5k races with 500 runners, and from around 25 minutes to around 35 minutes it is a solid stream of runners, and one could not possibly record bib numbers as quickly as runners arrive. We have found the following finish line setup to work with 500 runners on a 5k - you could make adjustments and scale it as you think necessary for your race. The main issue that when bib IDs can't be entered as quickly as times are marked, they can easily become out of sync: maybe an extra time was marked, or maybe a bib ID was accidentally skipped. We describe here various strategies for first reducing the likelihood of this type of issue, and second for making it easy to correct these issues when they do arise.

+ +

+ +From the finish line, we form a "funnel" out of cones and caution tape that forces down into a single-file line. The chute needs to be long enough for the fast runners to slow down enough by the end of the chute for their bib bottom to be torn off. It also needs to be long enough that if things start to get a little backed up due to runners coming in faster than their bib tags can be torn off, the queue doesn't spill back over the finish line. We find that about 40 feet is the right chute length. It seems really long, but if you have fast runners or a large number of runners, you'll be glad you did it. The chute is indicated with dashed lines in the illustration above. At the finish line, there is a table on which the timing computer is put.

+ +We now describe the roles of each of the finish line crew. Keep in mind the general philosophy of timing with fsTimer: keep track of crossing times, separately keep track of bib IDs that cross, and then fsTimer will line those two stacks up. We'll first describe the procedure for "normal" operations, and then will get into the complications that will arise and how the finish line crew need to be ready for them.

+ +Crew 1: Crew 1 is responsible for marking times. This person presses the "Start!" button at the same time as the race begins. Whenever a racer crosses the finish line, he or she presses spacebar to mark the time.

+ +Crew 5: Crew 5 is responsible for tearing off the bib bottoms and handing them to Crew 4. Their focus is to be sure that for every person that crossed the finish line, a bib bottom is handed to Crew 4 so that the bib ID record stays synchronized with the marked times.

+ +Crew 4: Crew 4 holds the dowel stand, takes the bib bottoms from Crew 5 and places them, face down, on the dowel stand, just like in the picture above. The stack of bibs on the dowel stand forms a record of who crossed.

+ +Crews 2 and 3: It takes some time to enter in all of the bib IDs, and so to get results as quickly as possible after the race this is best done while the race progresses. While the race is going, Crew 2 is responsible for entering the bib IDs into fsTimer, either directly into the timing computer or into a separate computer just for inputting bib IDs. They do this using a barcode scanner hooked into the computer. Crew 3 is responsible for bringing the bib bottoms from Crew 4 (who is forming the stack) to Crew 2. With a chute length of 40 feet, this means that Crew 2 does a lot of walking back and forth. We have multiple dowel stands which we use to transfer bib bottoms from Crew 4 to Crew 2, making sure that when Crew 2 is done scanning a batch of bib bottoms, they are transferred in their proper order to a 'master record' dowel stand.

+ +For small races we usually mark the times and enter the bib IDs on the same computer (the timing computer). For races with hundreds of runners, during the peak flow (which can be around one runner per second) we can easily mark all of the times that quickly, but we can't enter the bib IDs that quickly. This isn't a problem as long as we keep the bib IDs in the same order as the times, but since there will be a large gap between the IDs being entered in and the latest time that has been marked, it can be easier to enter IDs on a separate computer. We essentially set up two timing computers, by copying over the project directory with its timing dictionary to a second computer. On the first computer, we only mark times (no entering IDs). On the second computer, we only enter IDs (no marking times). After the race has finished, we press "Save" on the ID computer, and copy its saved timing session (the file "...times.json") over to the timing computer. We can then merge the IDs in with the marked times on the timing computer using the "Merge in saved IDs or times" menu item, as described in Section 4.5.

+ +That is the workflow for "normal" operations: Crew 1 marks times, Crew 5 tears bib bottoms, Crew 4 collects bib bottoms, Crew 3 takes bib bottoms from Crew 4 to Crew 2, and Crew 2 enters the bib IDs into fsTimer. There are, however, a number of complications that can arise, and the remaining 3 crew members (6-8) have roles that are important for handling these complications and making it possible to correct errors. These three roles become important for races with hundreds of racers.

+ +Crew 7: For larges races, there are clumps of people that come faster than Crew 5 can rip off the bib bottoms, and a line will start to form inside the chute. Crew 7 has two important jobs: The first job is to make sure that nobody cuts out of the chute (this person already had their time marked, and we must collect a bib bottom from them or the bib stack and the time stack will get out of sync). The second job is to actually go through the line of people in the chute and help them rip off their bib bottoms, and have the runners holding their own bib bottoms. That way when they reach the end of the chute (the front of the line), they just have to hand their bib bottom to Crew 5 and the line can move faster. Note, however, that Crew 7 should help runners tear their bottoms, but should not collect them, because Crew 4 would not be able to merge stacks of bib bottoms from both Crew 5 and Crew 7 in the correct order.

+ +Alternative: This process is a little complex, and is not strictly necessary as entering IDs can be done entirely asynchronously from marking times. An easier alternative is simply to have Crew 4 collect all of the bib IDs on one dowel stand, and then when the race is finished have Crew 1 enter all of the IDs in one go. We just prefer to enter bib IDs as the race progresses to have results as quickly as possible.

+ +Pro tip:One might be tempted to try to actually use the barcode scanner at the finish line to directly scan people's bibs as they exit the chute. This saves the whole process of tearing and tracking bib bottoms. While this is great when it works, we have found that this is in general a very bad idea because if something happens (for example, if the barcodes get faded from rubbing against people's jackets and the scanner has a hard time reading them, or if a small child trips on the USB cable and unplugs the scanner - both of these things happened) then no one can be checked in, a queue will quickly spill back over the finish line, and times will be lost. Even in small races, your timing strategy should never rely on being able to enter IDs into fsTimer as fast as runners arrive. Having Crew 4 means that no matter what happens we have a record of who crossed and in what order. For large races (300-500), at the peak runners will be arriving faster than Crew 2 can enter their IDs into fsTimer, even with a barcode scanner.

+ +Crew 3: Crew 3 can also have a side-duty of ensuring that runners are not cutting out of the chute on that side. If racers leave the chute, then their time has been marked but no bib will be collected and so those two stacks will get out of sync.

+ + +Crew 8: In large races, there is a reasonably large chance that the stack of IDs and marked times will get out of sync - there may be more times than there are IDs, or vice-versa. There are a number of reasons why this can happen - maybe a runner crossed the finish line but left the chute. Maybe Crew 1 marked a time for a child with no bib that Crew 5 didn't notice. Regardless, Crew 8's role is to collect the necessary data to allow us to correct any of these errors after the race is completed. Specifically, Crew 8 will take a manual record of a sampling of the finish times using a stopwatch that was started at the same time as the race clock and this form. This will not be a complete record, but rather Crew 8 will note the bib ID and finish time for one racer about every 20 seconds. When the race is finished and all of the bib IDs have been entered, we can then look through the results on the timing window and find each of the marked IDs and check that their times correspond to those noted by Crew 8. If we see that a time is incorrect, then we will also see that there is an extra ID or time that was marked and will be able to drop the extra using the editing tools described in Section 4.5. If Crew 8 tracks one completed racer every 20 seconds, then even if times and IDs get out of sync we will be able to correct them and we can guarantee no errors of more than 20 seconds in the times.

+ +Crew 5: Crew 5's role is to rip off bib bottoms, and ensure that for every person that crossed the finish line, a bib bottom is handed to Crew 4 so that Crew 4's record will be synchronized to the marked times. Unfortunately, probably about 10 times per 100 racers there will be situations where Crew 5 will not be able to tear off a bib bottom, or will not want to. Some of these situations (the common ones that we have seen repeatedly) are: + -The procedure for operating the finish line can be very simple for small races (50 or fewer people), but for large races we have found a number of details that are very important for ensuring accurate results. These details are discussed in the next section.

-Continue on to Section 3.1 Suggestions for race setup. +The issue with these situations is that Crew 1 already marked a time for these people, so we must put something in the stack of bibs or else the stack of bibs and the stack of marked times will be out of sync for all later people. For all of these situations (and any other new situation that might arise where a bib bottom is either unavailable, not quickly available, or in bad shape and might fall off of the dowel), Crew 5 will use what we call a "blank." A blank is a laminated card the size of a bib bottom that has the "pass" ID barcode on it.

+ +

+ +The most important thing for Crew 5 to keep in mind is (again) that for every person that crosses the finish line, something needs to be handed to Crew 4 - either the runner's bib bottom, or, if it isn't available, a blank. The blanks thus keep the bib IDs synchronized with the marked times, regardless of complicated situations that may arise. Crew 2 will then scan the blanks (thus entering the "pass" ID) just the same as if they were a proper bib bottom.

+ +Crew 6: Crew 6 is then what we refer to as the "error-corrector." Anytime Crew 5 uses a blank, the runner for whom the blank was used will be given to Crew 6 who will then make a record that will be used to retroactively fill in the correct bib ID in the place of the "pass" ID. Crew 6 will have a clipboard and a sheet of paper in which to write the true bib IDs of the runners for which blanks were used. He/she will also write the bib number of the runner that followed the "blank"-ed runner, so that the appropriate blank in the results can be found and filled. You can download our sheet here:

+ +

+ +When the race is finished (the last bib ID has been entered into fsTimer), we first use the error tracking sheet from Crew 6 to retroactively correct any blank times with the correct bib ID. We then use the sheet of known-good values from Crew 8 to correct any sync issues that may have occurred. This whole process takes only a few minutes, and then the times are ready to be printed and posted.

+ +Pro tip: Any time you press "Print" in the timing window, the "as-of-now" formatted results are saved to html files that can be printed. When possible, we try to post "as-of-now" results at around the 70% mark of the race so that the fastest runners don't have to wait quite so long to see what their results are. We emphasize that these are preliminary results, especially as the people from Crew 6's list will not be entered until the end of the race. Of course, printing results mid-race requires using a USB flash drive to copy the html files off of the race timing computer, to be taken to another computer for printing. This works great for smaller races, but for larger races there aren't any pauses in the flow of runners long enough to copy the files to the USB drive (notice that Crews 1 and 2 will have to pause their work for the duration of time required to get the files onto the USB drive). This also isn't possible if the IDs and times are being recorded on separate computers.

+ + +Continue on to Section 4 Detailed descriptions of fsTimer components.
- - \ No newline at end of file + + \ No newline at end of file diff --git a/documentation/documentation_sec3_1.htm b/documentation/documentation_sec3_1.htm deleted file mode 100644 index 32853f3..0000000 --- a/documentation/documentation_sec3_1.htm +++ /dev/null @@ -1,214 +0,0 @@ - - - -fsTimer documentation - Section 3.1: Suggestions for race setup - - - - -
-

fsTimer documentation

-
- -
- -Section 1 Installing fsTimer
-Section 2 Overview
- -Section 3 Checklist for timing with fsTimer
-
3.1 Suggestions for race setup
-Section 4 Detailed descriptions of fsTimer components
- -Section 5 Additional details for developers
-
- -
-

Section 3.1 - Suggestions for managing a race


- -The race timing software (fsTimer) is only one part of running (as in managing) a successful race. Having things organized in the right way is also extremely important. Here we describe everything that we do to organize and time a race using fsTimer, and you can take this strategy and adapt it as needed to your race.

- -The general process of timing the race will be: - - -We'll go over the whole process in detail.

- -

Bibs, barcodes, project setup


- -The first thing you will have to do is order race bibs. We find that the best race bibs are ones with tear-away bottoms and a hole in the tear-away part, like the one:

- -

- -We use the tear-away bottoms to form our ordered record of bib IDs that crossed the finish line, by stacking them on a dowel stand (you'll have to make or buy those as well) like this:

- -

- -Using a barcode scanner to enter the bib IDs into fsTimer is a great way to speed up the process and minimize errors. We have had great success even with cheap ($20) USB barcode scanners (like this one). Sometimes the bibs will come with barcode stickers already on them, otherwise it is easy to print them and attach them yourself (see fsBarcodes). Make sure that the barcode is on the tear-away bottom part of the bib, as that is the part that is collected from the racers. Of course, using barcodes and a barcode scanner is entirely optional; you can always enter the bib IDs just using the keyboard or numpad.

- -To create the new project in fsTimer, you will have to work with the race director to determine what age/gender divisions to use for divisional results, and what the registration fields should be. fsTimer requires name, age, and gender as registration fields, and you can add as many or as few as you'd like to those, although you won't want to be typing in large amounts of information during day-of registration.

- -

Pre-registration and registration


- -Racers appreciate not having to wait in a long registration line the morning of the race, and allowing racers to pre-register helps alot. We allow racers to pre-register on our website, and then enter the information into a spreadsheet which is then imported into fsTimer before the day of the race. For those without a website, pre-registration can also be done using paper forms, and again the information is entered into fsTimer before the day of the race. Information could also be entered directly into fsTimer using the Registration window. Notice that these racers are not being assigned bib numbers when they pre-register; we are simply collecting their information so that it is in the database and does not need to be typed in the day of the race.

- -Pro tip: If entering data from pre-registration by hand either into a spreadsheet or directly into fsTimer, double check the age and gender. Having males incorrectly listed as females can really make a mess of divisional results.

- -After pre-registration is closed, you will import or enter the information into fsTimer and generate a pre-registration database which is saved into the project subdirectory (the files for a project named project_name will be saved in fstimer/project_name).

- -fsTimer was designed to allow multiple computers for day-of registration. If there is only one computer for day-of registrations, there will likely be a long queue which means racers will have less fun - nobody likes waiting in line! We have found that the right number of computers (and thus the right number of people helping out at the registration table) is about one per 50 racers, when registration lasts for one hour before the race. Thus, if you are expecting 300 racers at your race, you probably want to have six people day of the race with computers running fsTimer.

- -We have had success with sending registration volunteers directly to the fsTimer webpage (http://fstimer.org/download.htm) to download the software and install it on their own computers, which they then bring the day of the race. fsTimer is truly cross-platform and will work on most computers, although not on android or iOS.

- -Morning of the race, the registration computers are prepared by doing the following actions: - - -Because the pre-registration databaes is loaded on all of the registration computers, a runner who pre-registered can walk up to any computer and their information will be there. We put a stack of bibs next to each registration computer, as the registration crew will be responsible for assigning bib numbers in fsTimer and handing out the bibs.

- -When a person walks up to the registration counter, the person running the registration computer will need to follow one of two sets of steps. These are the steps that you will need to train the volunteers at the registration table to do:

- -A pre-registered runner:
-This runner's information is already in the database, but we need to give them a bib and assign the corresponding bib number to their entry in fsTimer. - - -A runner that has not pre-registered:
-We need to type in the information for this runner, assign them a bib number, and give them the corresponding bib. - - -Pro tip: For individuals that haven't pre-registered, we have paper forms for them to register day-of, and we put these on a separate table away from the registration tables and make people fill the form out before coming to the registration table. You don't want to have people holding up the line while they stand in front of the computer filling out their registration form.

- -Pro tip: Name, age, gender, and bib ID are the very important fields. Errors in registration will mean errors in results, so double check these!

- -Pro tip: Name, age, gender, and bib ID are the only truly important fields. When people have not pre-registered, we usually skip typing in the other fields (things like address that are good to know but not critical for running the race). This helps to keep the queue short - you don't want people waiting in line while you type in information that could just as well be typed in after the race is finished!

- -Pro tip: Keeping the queue short the morning of the race is important for keeping things running smoothly, and making sure racers have fun. One thing that we have found makes a huge difference is to actually allow racers to check-in and pick up their bibs the night before the race. We specify a 2-hour period where we will be at the race location, and people can show up and do exactly the same things that they do day-of (pick up their bib if they pre-registered, and register and pick up their bib if not). We found that about half of racers exercised this option, thus cutting the morning-of crowd in half.

- -

Compiling, timing, and results


- -Close registration 15 minutes before the race is to begin. Note that in order to be reasonable doing this, it is important to not have a long queue of people still waiting to register - See the above tips on how to keep the line short. If people show up after registration has closed and want to run, it actually isn't a big issue. You can give them a bib and they will still show up in the overall results, just not in the divisional results (because fsTimer won't know their name/age/gender).

- -Use a USB flash drive to collect the registration databases from each of the computers used in registration. The database file will be in the project subdirectory, and will have in the filename the registration number that you assigned to that computer. (You can see on the bottom of the registration window what the filename is every time you hit "Save"). Put all of the registration databases onto the computer that you will use for timing at the finish line, and compile them. - -When compiling, fsTimer will check that no IDs are overloaded (meaning, one bib ID was assigned to multiple people). This can and does happen due to entry error - have the registration volunteers triple check bib ID when they enter it in and hand out the bib! If fsTimer finds this type of error, it will tell you, but you generally won't have time to try and find out who actually has that bib number, and so fsTimer will autocorrect the error for you in the only reasonable way (see Section 4.4 for details on the autocorrect).

- -The end result of compilation is the timing dictionary, which is what basically a big table that matches bib IDs to name, age, and gender. Take the computer out to the finish line, enter the Timing module, load the timing dictionary you just created, choose a pass ID (usually 0, the number zero) and enter the timing window.

- -Note: The tips about operating the finish line below are for races with only one lap, where the racers have a bib bottom that can be torn off. Races with multiple laps will not allow for the bib bottom to be torn off at each lap, and so these tips won't apply. see Section 4.6 gives a description of how lap timing works.

- -We will now discuss how to organize the finish line crew. The key to successful timing with fsTimer is to make sure that all of the times are marked, and all of the bib IDs are recorded in the order that they crossed. For races with 50 or fewer runners, this is not too difficult - it is easy to have one person marking times on the computer, and one or two others manually (as in, writing down on a pad of paper) listing the bib IDs in the order that they cross. With more than 50 runners, a more complex strategy will be important to be able to handle the load. We have done 5k races with 500 runners, and from around 25 minutes to around 35 minutes it is a solid stream of runners, and one could not possibly write all of the bib numbers down manually. We have found the following finish line setup to work with 500 runners on a 5k - you could make adjustments and scale it as you think necessary for your race.

- -

- -From the finish line, we form a "funnel" out of cones and caution tape that forces down into a single-file line. The chute needs to be long enough for the fast runners to slow down enough by the end of the chute for their bib bottom to be torn off. It also needs to be long enough that if things start to get a little backed up due to runners coming in faster than their bib tags can be torn off, the queue doesn't spill back over the finish line. We find that about 40 feet is the right chute length. It will seem long, but if you have fast runners or a large number of runners, you'll be glad you did it. The chute is indicated with dashed lines in the illustration above. At the finish line, there is a table on which the timing computer is put.

- -We now describe the roles of each of the finish line crew. Keep in mind the general philosophy of timing with fsTimer: keep track of crossing times, separately keep track of bib IDs that cross, and then fsTimer will line those two stacks up. We'll first describe the procedure for "normal" operations, and then will get into the complications that will arise and how the finish line crew need to be ready for them.

- -Crew 1: Crew 1 is responsible for marking times. They will be on the phone with the person starting the race, and will press the "Start!" button at the same time as the race begins. Whenever a racer crosses the finish line, they will press spacebar to mark the time.

- -Crew 5: Crew 5 is responsible for tearing off the bib bottoms and handing them to Crew 4. Their focus is to be sure that every person that for every person that crossed the finish line, a bib bottom is handed to Crew 4 so that the bib ID record stays synchronized with the marked times.

- -Crew 4: Crew 4 holds the dowel stand, takes the bib bottoms from Crew 5 and places them, face down, on the dowel stand, just like in the picture above. The stack of bibs on the dowel stand forms a record of who crossed.

- -Crew 7: For larges races, things will get busy in the middle and there will be clumps of people that come faster than Crew 5 can rip off the bib bottoms, and a line will start to form inside the chute. Crew 7 has two important jobs: The first job is to make sure that nobody cuts out of the chute (this person already had their time marked, and we must collect a bib bottom from them or things will get out of sync). The second job is to actually go through the line of people in the chute and help them rip off their bib bottoms, and have the runners holding their own bib bottoms. That way when they reach the end of the chute (the front of the line), they just have to hand their bib bottom to Crew 5 and the line can move faster.

- -Pro tip: It is extremely important that all bib bottoms go through Crew 5, so that they stay in the right order. Crew 7 should help runners tear their bottoms, but should not collect them, because Crew 4 would not be able to merge stacks of bib bottoms from both Crew 5 and Crew 4 in the correct order.

- -Crews 2 and 3: It takes some time to enter in all of the bib IDs, and so to get results as quickly as possible after the race this is best done while the race progresses. While the race is going, Crew 2 will be responsible for entering the bib IDs into fsTimer, on the timing computer. They will do this using either a barcode scanner if the bib bottoms have barcodes (highly recommended), or using a usb keyboard or numpad that is plugged into the timing computer. Crew 3 is responsible for bringing the bib bottoms from Crew 4 (who is forming the stack) to Crew 2. With a chute length of 40 feet, this means that Crew 2 will be doing a lot of running back and forth.

- -We use a collection of 4 dowel stands for transferring bib bottoms from the end of the chute, where they are torn off, to Crew 2 where they are entered into fsTimer. We call them the "collection," "transfer," "scanning," and "entered" dowels. - - - -Alternative: This process is a little complex, and is not strictly necessary as entering IDs is entirely asynchronous from marking times. So, there is not necessarily any hurry to enter in the IDs. An easier alternative is simply to have Crew 4 collect all of the bib IDs on one dowel stand, and then when the race is finished have Crew 1 enter all of the IDs in one go. We just prefer to enter bib IDs as the race progresses to have results as quickly as possible.

- -Alternative: It is also possible to record times on one computer, and have Crew 2 using a separate computer to record IDs. These two lists (the list of times and the list of IDs) can then be combined using the "Merge" feature that is discussed in Section 4.5.

- -Pro tip:One might be tempted to try to actually use the barcode scanner at the finish line to directly scan people's bibs as they exit the chute. This saves the whole process of tearing and tracking bib bottoms. While this is great when it works, we have found that this is in general a very bad idea because if something happens (for example, if the barcodes get faded from rubbing against people's jackets and the scanner has a hard time reading them, or if a small child trips on the USB cable and unplugs the scanner - both of these things happened) then no one can be checked in, a queue will quickly spill back over the finish line, and times will be lost. Even in small races, your timing strategy should never rely on being able to enter IDs into fsTimer as fast as runners arrive. Having Crew 4 means that no matter what happens we have a record of who crossed and in what order. For large races (300-500), at the peak runners will be arriving faster than Crew 2 can enter their IDs into fsTimer, even with a barcode scanner.

- -Pro tip:Another tempting strategy would be to use a USB extension cable to move Crew 2 next to Crew 4, so that Crew 3 doesn't have to run back and forth the whole race. We have found that this is also a bad idea as long cables can easily come loose or disconnected (e.g., by being tripped on by small children), and the IDs might not be getting scanned in.

- -Crew 3: Crew 3 can also have a side-duty of ensuring that runners are not cutting out of the chute on that side. If racers leave the chute, then their time has been marked but no bib will be collected and so those two stacks will get out of sync.

- -That is the workflow for "normal" operations: Crew 1 marks times, Crew 5 tears bib bottoms, Crew 4 collects bib bottoms, Crew 3 takes bib bottoms from Crew 4 to Crew 2, and Crew 2 enters the bib IDs into fsTimer. There are, however, a number of complications that can arise, and Crew 5, 6, and 8 will be responsible for handling these complications and ensuring there are no errors.

- - -Crew 8: In races of 300+ people, there is a reasonably large chance that the stack of IDs and times will get out of sync. There are a number of reasons why this can happen - maybe a runner crossed the finish line but left the chute. Maybe Crew 1 marked a time for a child with no bib that Crew 5 didn't notice. Regardless, Crew 8's role is to collect the necessary data to allow us to correct any of these errors after the race is completed. Specifically, Crew 8 will take a manual record of a sampling of the finish times using a stopwatch that was started at the same time as the race clock and this form. This will not be a complete record, but rather Crew 8 will note the bib ID and finish time for one racer about every 20 seconds. When the race is finished and all of the bib IDs have been entered, we can then look through the results on the timing window and find each of the marked IDs and check that their times correspond to those noted by Crew 8. If we see that a time is incorrect, then we will also see that there is an extra ID or time that was marked and will be able to drop the extra using the editing tools described in Section 4.5. If Crew 8 tracks one completed racer every 20 seconds, then even if times and IDs get out of sync we will be able to correct them and we can guarantee no errors of more than 20 seconds in the times.

- -Crew 8 can also ensure that nobody crosses the finish line, and then attempts to back out and go around the chute. Of course, if someone doesn't cross the finish line and wants to run outside the chute, that is OK; it's only a problem if Crew 1 marked the time for them, which will generally happen when they crossed the finish line.

- - -Crew 5: Crew 5's role is to rip off bib bottoms, and ensure that for every person that crossed the finish line, a bib bottom is handed to Crew 4 so that Crew 4's record will be synchronized to the marked times. Unfortunately, probably about 10 times per 100 racers there will be situations where Crew 5 will not be able to tear off a bib bottom, or will not want to. Some of these situations (the common ones that we have seen repeatedly) are: - - - -For all of these situations (and any other new situation that might arise where a bib bottom is either unavailable, not quickly available, or in bad shape and might fall off of the dowel), Crew 5 will use what we call a "blank." A blank is a piece of heavy stock paper (or something better if you think there might be rain) that is approximately the same size as the bib bottom, and has the "pass" ID barcode on it.

- -

- -The most important thing for Crew 5 to keep in mind is (again) that for every person that crosses the finish line, something needs to be handed to Crew 4 - either the runner's bib bottom, or, if it isn't available, a blank. The blanks thus keep the bib IDs synchronized with the marked times, regardless of complicated situations that may arise. Crew 2 will then scan the blanks (thus entering the "pass" ID) just the same as if they were a proper bib bottom.

- -Crew 6: Crew 6 is then what we refer to as the "error-corrector." Anytime Crew 5 uses a blank, the runner for whom the blank was used will be given to Crew 6 who will then make a record that will be used to retroactively fill in the correct bib ID in the place of the "pass" ID. Crew 6 will have a clipboard and a sheet of paper in which to write the true bib IDs of the runners for which blanks were used. He/she will also write the bib number of the runner that followed the "blank"-ed runner, so that the appropriate blank in the results can be found and filled. You can download our sheet here:

- -

- -We strongly recommend gathering the finish line crew together the week before the race, going over each of their roles, describing the types of complications that can arise (like those listed above), and actually simulating the whole finish line process (together with some complications). If everyone knows what to do, then things will go smoothly and you will have accurate times.

- -When the race is finished (the last bib ID has been entered into fsTimer), you must use the error tracking sheet from Crew 6 to retroactively correct any blank times with the correct bib ID. You must then use the sheet of known-good values from Crew 8 to correct any sync issues that may have occurred. This should not take more than a few minutes, and then the times are ready to be printed and posted.

- -Pro tip: Any time you press "Print" in the timing window, the "as-of-now" formatted results are saved to html files that can be printed. When possible, it's great to post "as-of-now" results at around the 70% mark of the race so that the fastest runners don't have to wait quite so long to see what their results are. Be sure to emphasize that these are preliminary results, especially as the people from Crew 6's list will not be entered until the end of the race. Of course, printing results mid-race requires using a USB flash drive to copy the html files off of the race timing computer, to be taken to another computer for printing. This works great for smaller races, but for larger races there might not be any pauses in the flow of runners long enough to copy the files to the USB drive (notice that Crews 1 and 2 will have to pause their work for the duration of time required to get the files onto the USB drive).

- - -Continue on to Section 4 Detailed descriptions of fsTimer components. -
- - - \ No newline at end of file diff --git a/documentation/documentation_sec4.htm b/documentation/documentation_sec4.htm index 9dfe60b..c1d0884 100644 --- a/documentation/documentation_sec4.htm +++ b/documentation/documentation_sec4.htm @@ -14,7 +14,7 @@

fsTimer documentation

Section 1 Installing fsTimer
Section 2 Overview
-Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -29,7 +29,7 @@
4.7 Handicap races

Section 4 - Detailed descriptions of fsTimer components


-Section 2 gave an overview of fsTimer, and Section 3 gave the procedures and organization that you'll want in order to use fsTimer. We now dive into the details, so that after reading this section you will know everything there is to know about using fsTimer. You should absolutely read this section before using fsTimer to time a race.

+Section 2 gave an overview of fsTimer, and Section 3 gave the procedures and organization that we use when using fsTimer. We now dive into the details, so that after reading this section you will know everything there is to know about using fsTimer. You should absolutely read this section before using fsTimer to time a race.

We first describe how everything is done for a standard, single-lap race. Then in Section 4.6 we describe the additional considerations for timing multiple laps, and in Section 4.7 considerations for handicap races. Both of these sections build on Sections 4.1-4.5, so you should read those sections even if you are doing a lap and/or handicap race.

diff --git a/documentation/documentation_sec4_1.htm b/documentation/documentation_sec4_1.htm index a03ab77..a6415f0 100644 --- a/documentation/documentation_sec4_1.htm +++ b/documentation/documentation_sec4_1.htm @@ -21,7 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
--> -Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -44,11 +44,13 @@

Section 4.1 - Setting up for a new race


If you want to use a name that is already taken by an existing file or folder, you will have to rename or delete that file or folder.

+Using the drop-down menu you can (optionally) select a project from which to import the settings. This will pre-fill all of the later windows in the project creation process with the settings from that project.

+ The next window allows you to choose the type of the race:



-A standard race is one where all racers have the same start time. In a handicap race, a "Handicap" time must be specified for all racers. Their final time is then calculated as the marked time minus the handicap. This allows racers to start at different times - a racer could start five minutes late, and by specifying a handicap of "0:05:00" for that racer, his time in the results would be corrected. Usually this is done for a race where all runners provide their handicap relative to a base time, and runners start at different times with the hope of all crossing the finish line around the same time.

+A standard race is one where all racers have the same start time. In a handicap race, a "Handicap" time must be specified for all racers. Their final time is then calculated as the marked time minus the handicap. This allows racers to start at different times - a racer could start five minutes late, and by specifying a handicap of "0:05:00" for that racer, his or her time in the results would be corrected. Usually this is done for a race where all runners provide their handicap relative to a base time, and runners start at different times with the hope of all crossing the finish line around the same time.

For lap timing, times are marked each time a racer completes a lap. The results printout will then display the total time along with the time for each of the individual laps.

@@ -56,9 +58,9 @@

Section 4.1 - Setting up for a new race




-The "Up" and "Down" buttons allow you to change the order of the fields. This is the order in which they will appear in the registration database: The top entry in this window will be the furthest left column in the registration window, and the bottom entry in this window will be the furthest right column in the registration window. If you have many fields and some of them you want to be able to see without having to sideways scroll in the registration window, you should put them towards the top of this lsit.

+The "Up" and "Down" buttons allow you to change the order of the fields. This is the order in which they will appear in the registration database: The top entry in this window will be the furthest left column in the registration window, and the bottom entry in this window will be the furthest right column in the registration window. This is also the order that they will show up in the results printouts, should you choose to have them in the results printouts.

-The "Remove" button removes a field. Last name, First name, ID, Age, and Gender, however, are hard-coded fields due to their importance for creating divisional results. (If it is a "handicap" race, then the field "Handicap" is also required). These fields cannot be removed. The "New.." buttons allow you to create a new entrybox or combobox. A combobox means in the registration database the entry will have to take one of a set collection of values. In the window for creating a new registration entry below, "Gender" is a combobox with options "male" and "female" (and blank). An entrybox is a box for freely entering text, like "Last name" and "First name" in the registration entry below.

+The "Remove" button removes a field. ID and Age, however, are hard-coded fields and cannot be removed. ID is critical for the timing itself. If you don't want to use Age, you can simply leave that field blank. If it is a "handicap" race, then the field "Handicap" is also required. The "New.." buttons allow you to create a new entrybox or combobox. A combobox means in the registration database the entry will have to take one of a set collection of values. In the window for creating a new registration entry below, "Gender" is a combobox with options "male" and "female" (and blank). An entrybox is a box for freely entering text, like "Last name" and "First name" in the registration entry below.



@@ -72,25 +74,45 @@

Section 4.1 - Setting up for a new race


fsTimer will not allow you to create two fields with the same name.

-

+Next you can specify the divisionals. Again, "Up" and "Down" change the order in which the divisions will appear in the divisional results.

-When you have finished specifying the registration fields, you move on to controlling the behavior of the "Add family" button, as described in Section 2.6. You should select all of the fields that are not the same for all members of a family, probably First name, ID, Age, and Gender.

+

-

+"New" allows you to add a division. Divisions may be defined using a combination of age and any of the comboboxes that were defined as registration fields. For example, if "League" was specified as a combobox in when creating the registration fields, then you could define divisionals using "League." The name of the division that you specify will be used in the divisional results. Then, use the checkboxes to specify which fields you want to use to define the division. In the example below, "Gender" and "League" have been checked, so only those two fields will be used to define the division.

-Finally you must specify the divisionals. Again, "Up" and "Down" change the order in which the divisions will appear in the divisional results.

+

-

+Note that it is OK to have overlapping divisions, if you want. If a runner falls into multiple divisions (e.g., "All females" and "Female, North league") then he or she will simply be listed in each of the divisional results.

-"New" allows you to add a division. Divisions may be defined using a combination of age and any of the comboboxes that were defined as registration fields. For example, if "Grade" was specified as a combobox in when creating the registration fields, then you could define divisionals using "Grade." The name of the division that you specify will be used in the divisional results. Then, use the checkboxes to specify which fields you want to use to define the division. In the example below, "Gender" and "Grade" have been checked, so only those two fields will be used to define the division.

+"Edit" and "Remove" work exactly as you would expect. "Copy" creates a new division that copies the settings of the currently selected division.

-

+In the next window we choose which information we want to include on the results printouts. These can include any of the registration fields, Time, Pace, and any custom combination of registration field data and time.

+ +

+ +From the top group of checkboxes, you can select which registration fields to include on the results printouts. Next you can select to include Time and/or Pace. If the check box next to Pace is marked, then Pace will be included in the results, computed as Time / distance using the distance that you specify in the Distance entry box. If you enter "5" and your race is a 5k, then the Pace that is calculated will be min/km. If you enter "3.107" for your 5k, then the pace that is calculated will be min/mile.

+ +The last section on the window allows you to create custom computed fields, which can be calculated using any of the registration fields, plus Time. You refer to a field by putting the field name (case sensitive!) in curly brackets, as in the screenshot above. So, in the computed results {First name} refers to field {First name}. You can then use any Python operations that you could use on those fields. {Age} and {Time} are numbers (floats), all other registration fields will be treated as text (strings). {Time} will be the time in seconds.

+ +For example, the expression:
+{First name} + ' ' + {Last name}
+concatenates First name with Last name, with a space in between (notice that there is a space between the single quotes). Pressing "New" and entering the expression below produces a field in which each runner's time is reduced by 10 seconds for each year of age:

+ +

+ +This field would allow us to have a race where older racers are given a time discount according to their age (each year means a 10 second advantage). If you enter fields that don't exist or if the expression isn't proper Python syntax, fsTimer will tell you that it is invalid.

+ +The final window in creating a new project allows us to choose which field the results will be ranked by, for both overall results and for division results. You can rank by any field that will be used in the results (those selected or defined on the previous window). In the example below the overall results will be ranked by Adjusted Time (defined in the previous window), and the divisional results will be sorted by Time, except 70 and up which is sorted by Name:

+ +

+ +Once the rankings are specified, a new subdirectory in the fstimer directory with the specified project name will be created (that is, if the project name was project_name, then the directory fstimer/project_name is created). Inside of this directory will be a file project_name.reg. This file stores all of the information that you just specified (registration fields, divisionals, results fields, and ranking fields) in json format. If you want to use the same project on multiple computers (as you will if you want to do registration on multiple computers), then just copy the entire project_name directory into the fstimer directory on all computers.

-Note that it is OK to have overlapping divisions, if you want. If a runner falls into multiple divisions (e.g., "boys" and "7th grade boys") then he/she will simply be listed in each of the divisional results.

+At any point in the future you can edit the project settings by selecting "Edit project settings" from the "Menu" on the main window:

-"Edit" and "Remove" work exactly as you would expect.

+

-Once the divisions are specified, a new subdirectory in the fstimer directory with the specified project name will be created (that is, if the project name was project_name, then the directory fstimer/project_name is created). Inside of this directory will be a file project_name.reg. This file stores all of the information that you just specified (registration fields, which fields to blank when adding a family member, and divisional divisions) in json format. If you want to use the same project settings across multiple computers (as you will when you want to do registration on multiple computers), then just copy the entire project_name directory into the fstimer directory on all computers.

+You cannot edit the race type or the change the registration fields. You can, however, edit the divisions, change which fields show up in the rankings, and change how results are ranked. Pressing "Edit project settings" will take you back to the window where you specify divisions, and you can move forward from there. For the project to be updated with the new settings you must press "Next" through all of the windows, and after the final (rankings) window the project settings will be updated.

Continue on to Section 4.2 Importing preregistration - details. diff --git a/documentation/documentation_sec4_2.htm b/documentation/documentation_sec4_2.htm index dc96022..b8e85f1 100644 --- a/documentation/documentation_sec4_2.htm +++ b/documentation/documentation_sec4_2.htm @@ -21,7 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
--> -Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -36,7 +36,7 @@
4.7 Handicap races

Section 4.2 - Importing preregistration - details


-The preregistration module allows for a csv file to be imported into an fsTimer registration database. Starting from an Excel or Libreoffice spreadsheet, you can do "Save as" to save it as a csv file.

+The import module allows for a csv file to be imported into an fsTimer registration database. Starting from an Excel or Libreoffice spreadsheet, you can do "Save as" to save it as a csv file.



@@ -58,11 +58,11 @@

Section 4.2 - Importing preregistration - details


"Found csv fields" lists all of the columns found in the csv file (these will be exactly all of the column headers). "Matched csv fields" lists all of the columns from the csv file that were automatically matched with the registration database fields by having identical names. "Did not match csv fields" lists the columns from the csv file that did not have exact matches in the registration database. "Did not find in csv" lists the registration fields that were not automtically matched with CSV columns - these fields were either manually assigned or specified to be left blank. In this case, "ID" is a registration field but was not in the csv (that will be the case if you assign bib IDs when runners pick up their bibs, as we do).

-If you are importing data into a combobox (recall, this is the type of field that must take one of a few values) then all of the entries in that column in the csv file must match exactly one of the combobox values. For example, the "Gender" registration field is a combobox that takes values "male", "female", or blank. If "Gender" is matched to a column in the csv file, then all of the values in that column must be either "male", "female", or blank. This is case sensitive - "Male" will not match "male". If you try to import a csv file for which the values in a combobox column do not take valid values, you will get an error:

+If you are importing data into a combobox (recall, this is the type of field that must take one of a few values) then all of the entries in that column in the csv file must match exactly one of the combobox values. For example, by default the "Gender" registration field is a combobox that takes values "male", "female", or blank. If "Gender" is matched to a column in the csv file, then all of the values in that column must be either "male", "female", or blank. This is case sensitive - "Male" will not match "male". If you try to import a csv file for which the values in a combobox column do not take valid values, you will get an error:



-The error tells you what the invalid value is ("Male" instead of "male", "female", or blank), and where in the csv the error is (row 12). Correct any errors until the csv imports directly. When the csv is successfully imported, it while write the corresponding registration database file to a file named project_name_registration_prereg.json, in the directory fstimer/project_name. The file name is printed in the text field on the pre-registration window. Note that if a file project_name_registration_prereg.json already exists (for instance, because you already imported another csv in this same project), it will be overwritten.

+The error tells you what the invalid value is ("Male" instead of "male", "female", or blank), and where in the csv the error is (row 25). Correct any errors until the csv imports directly. When the csv is successfully imported, it will write the corresponding registration database file to a file named project_name_registration_prereg.json, in the directory fstimer/project_name. The file name is printed in the text field on the pre-registration window. Note that if a file project_name_registration_prereg.json already exists (for instance, because you already imported another csv in this same project), it will be overwritten.

Continue on to Section 4.3 Registration - details. diff --git a/documentation/documentation_sec4_3.htm b/documentation/documentation_sec4_3.htm index 20e8335..5e96505 100644 --- a/documentation/documentation_sec4_3.htm +++ b/documentation/documentation_sec4_3.htm @@ -21,7 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
--> -Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -42,25 +42,21 @@

Section 4.3 - Registration - details


When opening the registration window you may also load a pre-registration database. You can actually load any fsTimer registration database, including one that was previously saved from the registration window. For instance, suppose you were working in the registration window adding and editing entries, and then saved the results as project_name_registration_4.json. If you later want to make further edits/additions to the registration database, you can simply select "project_name_registration_4.json" as the pre-registration to load, and it will be loaded into the registration window.

-The "New" button allows you to add a new entry by filling in data for all of the fields. Any of the fields can be left blank and this will not cause problems (for instance, if someone does not want to provide a certain piece of information, or does not have a certain piece of information, like an email address). However, if a field is left blank that is required for divisionals (like "Gender" if there are divisions that split by gender), then that individual will show up in the overall results but not in the divisional results.

+The "New" button allows you to add a new entry by filling in data for all of the fields. Any of the fields can be left blank and this will not cause problems (for instance, if someone does not want to provide a certain piece of information, or does not have a certain piece of information, like an email address). However, if a field is left blank that is required for divisionals (like "Age" if there are divisions that split by age), then that individual will show up in the overall results but not in the divisional results.

-The "Edit", "Remove", and "Add family" buttons require an entry to be selected. If no entry is selected, then these buttons will simply not do anything. When an entry is selected, then the "Edit" button allows you to change the values in any of the fields for the selected entry; it is pretty self explanatory. The "Remove" button deletes the selected entry from the registration database. This cannot be undone (other than closing without saving, and loading a previously saved file from before the entry was removed) and so you should exercise extreme caution.

+The "Edit" and "Remove" buttons require an entry to be selected. If no entry is selected, then these buttons will simply not do anything. When an entry is selected, then the "Edit" button allows you to change the values in any of the fields for the selected entry; it is pretty self explanatory. The "Remove" button deletes the selected entry from the registration database. This cannot be undone (other than closing without saving, and loading a previously saved file from before the entry was removed) and so you should exercise extreme caution.

-The "Add family" button would be better called the "Add family member" button, as its purpose is to add a family member for the selected entry. This button has exactly the same effect as the "New" button, except some of the fields (the ones specified when creating the project) will be pre-filled in with data from the selected entry (typically data like address, phone number, etc.).

+Typing into the text field at the top of the window will filter the registration entries by whichever field is selected (here Last name):

-Important note for Mac users: There's a strange behavior in the Mac version of fsTimer where sometimes clicking on the buttons in the registration window won't work. This behavior will generally exhibit itself on the day of registration when you are trying to edit an entry or add a new entry, and clicking on the button will not bring up the "Edit" or "New" window. When this happens, if you look closely, you will notice that there is a light blue box around the button - instead of clicking on the button, somehow the area around the button becomes selected. This never happens in Linux or in Windows, so I think it might be an issue with PyGTK for Mac? Whatever is happening, this behavior is annoying but is not critical. Just hit "Enter" and the button will be selected. Alternatively, click on a different part of the window (like back on the entry in the center of the registration window), and you will see the light blue box around the button disappear. Try again to click on the button, and it will work (or maybe after a few iterations of this).

+

-Typing into the text field at the top of the window will filter the registration entries by last name:

- -

- -Pressing the "Clear" button will clear the filter and show all entries again (or, just erase all of the text in the filter window). Notice that after you use "Edit" or "New" to edit/create an entry, fsTimer will automatically filter by the last name of the edited/new entry. This is just so you can easily see the edited/new entry and verify that the information is correct in the database.

+Pressing the "Clear" button will clear the filter and show all entries again (or, just erase all of the text in the filter window). After you use "Edit" or "New" to edit/create an entry, fsTimer will automatically filter by the field of the edited/new entry corresponding to how the filter is set. This is just so you can easily see the edited/new entry and verify that the information is correct in the database.

Clicking on any of the field names above the registration entries (like "Last name" in the below image) will sort all of the registration entries using that field:

-

+

-Press "Save" frequently, and press "Done" when you are finished with registration.

+The results will automatically be saved any time a new entry is added, or an entry is removed. If the blue text is present at the bottom of the window, this indicates that the save is up to date.

Continue on to Compiling registrations - details.
diff --git a/documentation/documentation_sec4_4.htm b/documentation/documentation_sec4_4.htm index 6d3ff68..6f08fe2 100644 --- a/documentation/documentation_sec4_4.htm +++ b/documentation/documentation_sec4_4.htm @@ -21,7 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
--> -Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -36,38 +36,35 @@
4.7 Handicap races

Section 4.4 - Compiling registrations - details


-When registration is done on multiple computers, each one will have saved on it (in the project directory) a registration database. For a project named project_name and 3 computers that were given registration IDs 1, 2, and 3, you will create the combined database by merging the files project_name_registration_1.json, project_name_registration_2.json, and project_name_registration_3.json.

+When registration is done on multiple computers, each one will have saved on it (in the project directory) a registration database. For a project named project_name and 2 computers that were given registration IDs 1 and 4, you will create the merged database by compiling the files project_name_registration_1.json and project_name_registration_4.json.

All of these files are put on one computer (we use a USB flash stick), and are then merged using the "Compile" module in fsTimer. The "Add" button is used to select the files that are to be merged:



-The "Remove" button removes a selected filename from the list. When all of the registration databases to be compiled are on the list, then press "Merge". The "Merge" operation will create two new files: A merged registration, that will be saved as project_name_registration_compiled.json; and a timing dictionary, that will be saved as project_name_timing_dict.json. The merged registration is a normal fsTimer registration database file that could be opened, for instance, in the Registration window. It contains all of the entries from all of the compiled databases. The timing dictionary is a dictionary that allows fsTimer to look up runner information (name, age, and gender) by bib ID. When a runner's bib ID is entered into the Timing module of fsTimer, it will use the the timing dictionary to look up what the corresponding name, age, and gender are. Under normal operation, pressing merge will work nicely, the registration databases will be combined and the files written:

+The "Remove" button removes a selected filename from the list. When all of the registration databases to be compiled are on the list, then press "Compile". The "Compile" operation will create two new files: A merged registration, that will be saved as project_name_registration_compiled.json; and a timing dictionary, that will be saved as project_name_timing_dict.json. The merged registration is a normal fsTimer registration database file that could be opened, for instance, in the Registration window. It contains all of the entries from all of the compiled databases. The timing dictionary is a dictionary that allows fsTimer to look up runner information (registration fields) by bib ID. When a runner's bib ID is entered into the Timing module of fsTimer, it will use the the timing dictionary to look up what the corresponding registration information is. Under normal operation, pressing Compile will work nicely, the registration databases will be combined and the files written.

- -In some instances, fsTimer will find a particular type of error in the registration databases. The particular error that it checks for are overloaded IDs. This means that the same ID was (in the registration databases) assigned to multiple runners. For instance, ID "110" might have been assigned to "John Smith" in project_name_registration_1.json, and assigned to "John Doe" in project_name_registration_2.json. This is an issue when forming the timing dictionary, because now when fsTimer wants to look up the information for bib ID 110, it doesn't know if it should use John Smith or John Doe. If fsTimer finds overloaded IDs, it will raise an error and tell you:

+In some instances, fsTimer will find a particular type of error in the registration databases. The particular error that it checks for are overloaded IDs. This means that the same ID was (in the registration databases) assigned to multiple runners. For instance, ID "110" might have been assigned to "John Smith" in project_name_registration_1.json, and assigned to "John Doe" in project_name_registration_4.json. This is an issue when forming the timing dictionary, because now when fsTimer wants to look up the information for bib ID 110, it doesn't know if it should use John Smith or John Doe. If fsTimer finds overloaded IDs, it will raise an error and tell you:



-Notice that on the Compile window it now says "Errors found!" in red on the bottom, and a new window has popped up with a list of the overloaded IDs. In the above screenshot, IDs 110 and 120 were overloaded, meaning that they were assigned to multiple entries. We should briefly note that this means unique entries - if the same ID is used in multiple registration database files along with the same other information (Name, age,... all of the registration fields), then this is not a problem.

+Notice that on the Compile window it now says "Errors found!" in red on the bottom, and a new window has popped up with a list of the overloaded IDs. In the above screenshot, IDs 103 and 110 were overloaded, meaning that they were assigned to multiple entries. We should briefly note that this means unique entries - if the same ID is used in multiple registration database files along with the same other information (Name, age,... all of the registration fields), then this is not a problem.

The most likely source of overloaded IDs is entry error by the people running the registration computers. If you click on an ID and press "View ID entries", it will bring up another window that will list for you the conflicting registration entries:

-

- -In the image above, we see that Willie Hansen and Nicholas Dyer were both assigned bib ID 120. When this happens, there are two ways to proceed. If you know which of the entries is the actual runner with bib 120, then you can select that entry and press "Keep entry". For example, selecting Willie Hansen and clicking "Keep entry" will mean that in the merged database (and the timing dictionary), Willie will have the ID 120, and Nicholas will be left with a blank ID field. Usually, however, you will not know which entry is the correct one. In a real race scenario, the time interval between closing registration and starting the race is usually pretty short (we close registration 15 minutes before the race starts), and so there simply is not time to figure out which is the correct entry. In this situation, just press "OK" and then press "OK" in the overloaded IDs window:

+

-

+In the image above, we see that Gene Gomez and Tiffany Parker were both assigned bib ID 103. When this happens, there are two ways to proceed. If you know which of the entries is the actual runner with bib 103, then you can select that entry and press "Keep entry". For example, selecting Gene Gomez and clicking "Keep entry" will mean that in the merged database (and the timing dictionary), Willie will have the ID 103, and Tiffany will be left with a blank ID field. In a real race scenario, the time interval between closing registration and starting the race is usually pretty short (we close registration 15 minutes before the race starts), and so there simply is not time to figure out which is the correct entry. In this situation, just press "OK" and then press "OK" in the overloaded IDs window.

-When you press "OK" in the window above, it leaves all overloaded IDs unassigned. This is the right thing to do, unless you actually know which are the correct entries. When the runners cross the finish line, they will still show up in the results, but they will just show up by bib number alone, and without their name, age, and gender. Unfortunately this means that they will not show up in the divisional results, but this is something that you can correct after the race when you have time to actually figure out what the correct entries are.

+When you press "OK" in overloaded IDs window, it leaves all overloaded IDs unassigned. This is the right thing to do, unless you actually know which are the correct entries. When the runners cross the finish line, they will still show up in the results, but they will just show up by bib number alone, and without their registration info (name, etc.). Unfortunately this means that they will not show up in the divisional results, but this is something that you can correct after the race when you have time to actually figure out what the correct entries are.

-Pressing "OK" in the overloaded IDs window will unassign all of the overloaded IDs, thus allowing the merging and creation of the timing dictionary to continue.

+Once all of the overloaded IDs are corrected, compilation will continue:

-

+

-The Compile window will then say at the bottom "errors corrected" and will print the names of the compiled registration database file and the timing dictionary file.

+The Compile window says at the bottom "errors corrected" and will print the names of the compiled registration database file and the timing dictionary file.

-In addition to the timing dictionary and the compiled registration database in fsTimer format, fsTimer will also save a csv spreadhseet containing the merged registration information to the file project_name_registration.csv. This is useful for giving the final registration information to the race director, to use for contacting people the following year.

+In addition to the timing dictionary and the compiled registration database in fsTimer format, fsTimer will also save a csv spreadhseet containing the merged registration information to the file project_name_registration.csv. This is useful for having a portable spreadsheet with all of the registration information collected day-of.

As a final note, as was mentioned in Section 2.4, even if there is only one registration database to be used for timing (i.e., there is nothing to merge), you must still compile that one registration database so as to create the timing dictionary.

diff --git a/documentation/documentation_sec4_5.htm b/documentation/documentation_sec4_5.htm index 08d9e40..01663d1 100644 --- a/documentation/documentation_sec4_5.htm +++ b/documentation/documentation_sec4_5.htm @@ -21,7 +21,7 @@
2.3 Registration
2.4 Compiling registrations
2.5 Race timing
--> -Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -41,8 +41,8 @@

Section 4.5 - Race timing - details




@@ -52,25 +52,25 @@

Section 4.5 - Race timing - details


The first thing you will do in a race will be to press "Start!" at the exact moment that the race begins. This starts the clock ticking and all marked times will be recorded as time elapsed since this moment.

-

+

-At the bottom of the window in the screenshot above it says "19 registrants. Checked in: 0". The 19 registrants are the number of bib IDs in the timing dictionary. This will generally be the number of people registered for the race, but can differ from the actual number if, for instance, there were overloaded IDs that were de-assigned during compilation. It can also differ from the actual number of racers on the course if there are racers who register and pick up a bib (and thus are in the timing dictionary), but don't end up running the race. As you check in racers (by entering their bib numbers into the timing window), if the bib number is one of those in the timing dictionary, then the "Checked in" number will increment.

+At the bottom of the window in the screenshot above it says "20 registrants. Checked in: 0". The 20 registrants are the number of bib IDs in the timing dictionary. This will generally be the number of people registered for the race, but can differ from the actual number if, for instance, there were overloaded IDs that were de-assigned during compilation. It can also differ from the actual number of racers on the course if there are racers who register and pick up a bib (and thus are in the timing dictionary), but don't end up running the race. As you check in racers (by entering their bib numbers into the timing window), if the bib number is one of those in the timing dictionary, then the "Checked in" number will increment.

All of the tasks related to the timing process take place in the entrybox at the bottom of the window. Make sure that you have clicked in that entrybox, otherwise any input (like pressing spacebar to mark a time, or entering a bib ID) will not be recognized.

-Pressing spacebar (or whichever key you chose for marking times when opening the Timing window) will record the current time (relative to the Start! time) as a blank time in the window. The time is listed as hr:min:sec, and the "ID" column will be blank. As multiple times are marked, they will be recorded in their proper order.

+Pressing spacebar (or whichever key you chose for marking times when opening the Timing window) will record the current time (relative to the Start! time) as a blank time in the window. The time is listed as [hr:]min:sec, and the "ID" column will be blank. As multiple times are marked, they will be recorded in their proper order.



-This is the stack of marked times, and their corresponding bib IDs must be entered (although there is no hurry; as long as the record of bib IDs stays synchronized with the list of marked times, you can enter them all in at your convenience). In our suggested finish line design in Section 3.1, Crew 2 is the individual entering the times. Bib IDs are entered by typing them into the entrybox (the same one that must be selected to mark times) and pressing Enter. Note that if you use a USB barcode scanner, then the default behavior of most (all?) USB barcode scanners is to type whatever is in the barcode followed by Enter, so the software will work very naturally with a barcode scanner. Entering "100" will assign that bib ID to the oldest time that has not yet been assigned an ID:

+This is the stack of marked times, and their corresponding bib IDs must be entered (although there is no hurry; as long as the record of bib IDs stays synchronized with the list of marked times, you can enter them all in at your convenience). Bib IDs are entered by typing them into the entrybox (the same one that must be selected to mark times) and pressing Enter. Note that if you use a USB barcode scanner, then the default behavior of most (all?) USB barcode scanners is to type whatever is in the barcode followed by Enter, so the software will work very naturally with a barcode scanner. Entering "101" will assign that bib ID to the oldest time that has not yet been assigned an ID:



-Thus as long as the bib IDs are entered in the same order that they crossed the finish line, they will be correlated with their correct marked times. Entering "101" and "103" fills in the IDs for the next two times:

+Thus as long as the bib IDs are entered in the same order that they crossed the finish line, they will be correlated with their correct marked times. Entering "102" and "104" fills in the IDs for the next two times:



-If an ID is entered and there aren't any "blank" times available, it will just be added to the stack of bib IDs and the next marked time will be assigned to it. Here we have entered "104" without any un-assigned marked times, so the time is left blank but will be filled in with the next time that is marked:

+If an ID is entered and there aren't any "blank" times available, it will just be added to the stack of bib IDs and the next marked time will be assigned to it. Here we have entered "105" without any un-assigned marked times, so the time is left blank but will be filled in with the next time that is marked:



@@ -82,21 +82,21 @@

Section 4.5 - Race timing - details


Editing times

-The "Edit" button on the right side of the window allows you to edit the ID and/or time of any of the marked times. For instance, suppose one of the pass IDs used in the above screenshot was a placeholder for a runner whose bib ID could not be obtained immediately (see Section 3.1 for some situations where you might want to do this). We can later go back and replace the pass ID (the 0) with his true ID by pressing "Edit", and replacing "0" with his true ID:

+The "Edit" button on the right side of the window allows you to edit the ID and/or time of any of the marked times. For instance, suppose one of the pass IDs used in the above screenshot was a placeholder for a runner whose bib ID could not be obtained immediately (see Section 3 for some situations where you might want to do this). We can later go back and replace the pass ID (the 0) with his or her true ID by pressing "Edit", and replacing "0" with the true ID:

-

+

As the red warning states, you should exercise extreme caution when making these edits, as they cannot be undone if you forget the original values.

There are actually two editing modes in the timing window. The first is shown above, when a single entry is selected and "Edit" is pressed. Then, the ID and time of that entry can be modified as desired. Alternatively, an entire block of times can be modified together. This is accomplished by selecting the block of times (using shift) and pressing "Edit":

-

+

The block edit window allows for all of the selected times to be adjusted forward or backwards by a certain amount of time. In the screenshot above, pressing "OK" will add 25 seconds to all of the times:



-The "Drop ID" and "Drop time" functionalities drop the ID (or, respectively, time) from the selected row, and shift all IDs (resp. times) downward to match. This is useful for keeping the ID stack and the times stack properly synced, for instance, if an ID was accidentally entered twice, or spacebar was accidentally hit twice. Consider the following example, where the ID "106" was erroneously entered twice, and so the stacks are out of sync:

+The "Drop ID" and "Drop time" functionalities drop the ID (or, respectively, time) from the selected row, and shift all IDs (resp. times) downward to match. This is useful for keeping the ID stack and the times stack properly synced, for instance, if an ID was accidentally entered twice, or spacebar was accidentally hit twice. Consider the following example, where the ID "106" was erroneously entered twice, and so the stacks are out of sync (there are more IDs than there are marked times):



@@ -108,41 +108,39 @@

Section 4.5 - Race timing - details


Saving and printing results

-The "Save" button on the right saves the results (starting time, marked times, and bib IDs) to a file whose name will include the date and time that the Timing window was opened (this way, results will never be accidentally overwritten if you open the Timing window at another time). If the Timing window was opened on Jun 1, 2013 at 2:10pm and 30 seconds, then the results are saved to a file project_name_Sat_Jun_1_141030_2013_times.json.

+The "Save" button on the right saves the results (starting time, marked times, and bib IDs) to a file whose name will include the date and time that the Timing window was opened (this way, results will never be accidentally overwritten if you open the Timing window at another time). If the Timing window was opened on Jun 1, 2013 at 2:10pm and 30 seconds, then the results are saved to a file project_name_Sat_Jun_1_141030_2013_times.json. The timing session is saved in such a way that it can be loaded from the timing window without any loss.

-It is a good idea to save periodically during timing in case something catastrophic happens (like the computer dies), however keep in mind that any time the entrybox in the timing window is not selected (for instance, because you have used the mouse to press the "Save" button), no times may be marked and no IDs may be entered, until the mouse has moved back to select the entrybox. In our suggested finish line setup (Section 3.1), Crew 1 is operating the computer while Crew 2 enters the bib IDs with the barcode scanner. Crew 1 would need to tell Crew 2 to stop scanning for the period of time it takes to save, and then to resume when the entrybox has been reselected.

+It is a good idea to save periodically during timing in case something catastrophic happens (like the computer dies), however keep in mind that any time the entrybox in the timing window is not selected (for instance, because you have used the mouse to press the "Save" button), no times may be marked and no IDs may be entered, until the mouse has moved back to select the entrybox.

-The "Print" button automatically generates nicely formatted results printouts. As mentioned in Section 2.5, it does not physically print the results, rather it saves them (the overall results and the divisional results) as html files in the project directory. These html files can then be opened in any web browser and printed from there. The actual filenames will again incorporate the date and time at which the Timing window was opened, so as to not be automatically overwritten by a later session in which the Timing window is opened. With the same date and time as above, the filenames would be project_name_Sat_Jun_1_141030_2013_alltimes.html for the overall results and project_name_Sat_Jun_1_141030_2013_divtimes.html for the divisional results.

+The "Printouts" button automatically generates nicely formatted results printouts, saved to html in the project directory. These html files can then be opened in any web browser and printed from there. The actual filenames will again incorporate the date and time at which the Timing window was opened, so as to not be automatically overwritten by a later session in which the Timing window is opened. With the same date and time as above, the filenames would be project_name_Sat_Jun_1_141030_2013_alltimes.html for the overall results and project_name_Sat_Jun_1_141030_2013_divtimes.html for the divisional results.

We will now describe the results for a standard, single-lap race. The overall results might look like this:

-

+

-The results will automatically be sorted into places, and the name, age, and gender will be filled in from the timing dictionary. If some information is blank in the database, this is not a problem; it will simply be left blank in the results. For example, 5th place in the screenshot above (bib 128) had a blank name in the database, so the name is left blank in the results. 4th place (bib 101) had a blank gender in the database, so it is blank in the results. Notice also bibs 110 and 120. These were the overloaded IDs from Section 4.4 that were left unassigned to any particular racer. They still show up in the results, however they do not have any name/age/gender associated with them. The same will be true for any bib that is not in the registration database, for example, if someone shows up after registration has closed but is given a bib anyway and runs the race.

+The results will automatically be sorted into places, and all of the fields that were chosen when creating the project will be filled in from the timing dictionary. If some information is blank in the database, this is not a problem; it will simply be left blank in the results. In particular, if a bib ID is not assigned to any racer (perhaps because it was overloaded) then the Place, Time, and ID will be filled in, but the remaining registration fields will just be blank.

Notice also that the "pass" IDs (0) do not show up in the results. Any times with pass IDs will be ignored when creating the results.

The divisional times are automatically generated using the registration information in the timing dictionary, and the divisions specified when creating the new project. The printed results will look something like this:

-

+

-Because the divisions are defined by age and gender, notice that any IDs that do not have both age and gender in the timing dictionary will be left out of the divisional results. From two screenshots up, bibs 109, 110, 101, 120, and 103 do not have complete gender and age information and so are left out of the divisional results. (In a normal race setting, there will usually not be very many entries missing age and gender information - this will usually only happen if there are errors from the people entering information at the registration table, or for individuals that showed up after registration had already closed and were just handed bibs without being entered into fsTimer).

- -If you create divisionals using fields other than age and gender, note that all of the fields used to create divisions will be shown as columns in the results.

+These divisions are defined by gender, so any IDs that do not have gender in the timing dictionary will be left out of the divisional results. Thus bib IDs that are not assigned to any racers in the timing dictionary will show up in the overall results as the bib ID, but will not show up in the divisional results at all.

In addition to writing out nicely formatted html files, you can write the results out to a csv spreadsheet by clicking on the "Options" button and then selecting "Save results to CSV." This will produce two csv spreadsheets, one for all times and the other for divisional results, with all of the same data that is found in the html files.

Loading and merging times

-Times that have been saved using the "Save" button can be loaded by clicking on the "Options" button and selecting "Load saved timing session." A warning: This will replace any IDs or times that are currently in the timing window, and so if these data have not been saved they will be lost forever. Keep in mind that this will load the marked times, the bib IDs assigned to them, and the Start time. It will not reload the timing dictionary (this will still be whatever was selected when opening the Timing window), which allows you to make edits to the timing dictionary, and then re-load the times. We'll give some more details about this later below.

+Timing sessions that have been saved using the "Save" button can be loaded by clicking on the "Options" button and selecting "Load saved timing session." A warning: This will replace any IDs or times that are currently in the timing window, and so if these data have not been saved they will be lost forever. Keep in mind that this will load the marked times, the bib IDs assigned to them, and the Start time. It will not reload the timing dictionary, this will still be whatever was selected when opening the Timing window.

-It is possible to mark times on one computer and enter bib IDs on a separate computer - that is, create these two stacks separately - and then merge them after timing is finished. This way, Crew 1 and Crew 2 do not have to synchronize, nor to be close to each other. The two stacks can then be merged using these steps: +It is possible to mark times on one computer and enter bib IDs on a separate computer - that is, create these two stacks separately - and then merge them after timing is finished. The two stacks can then be merged using these steps: @@ -152,42 +150,23 @@

Section 4.5 - Race timing - details


Pressing "Options" and selecting "Edit starting time" opens a dialog through which the starting time can be edited:

-

+

+ +The long number in the entry box is the number of seconds since the start of the Unix epoch - but this isn't important. If you add or subtract seconds from that number, they will be correspondingly added or subtracted from the start time. For example, subtracting 300 from the number in the above screenshot yields 1460241732.76. If we were to replace the number currently there with 1460241732.76 then the start time will be pushed five minutes earlier. This will add five minutes to the current running clock time.

-The long number in the entry box is the number of seconds since the start of the Unix epoch - but this isn't important. If you add or subtract seconds from that number, they will be correspondingly added or subtracted from the start time. For example, subtracting 300 from the number in the above screenshot yields 1415048032.83. If we were to replace the number currently there with 1415048032.83 then the start time will be pushed five minutes earlier.

+Importantly, if you change this time mid-race, it will not adjust any times that have already been recorded. Those times could be adjusted using the block edit functionality described above.

-If you change this time mid-race, it will not adjust any times that have already been recorded. Those times could be adjusted using the block edit functionality described above.

+Pressing "Restart clock" in the "Options" menu will restart the clock to 0. As before, it will not change any already recorded times.

Making corrections to results

-We will now briefly discuss making corrections to the results, which can typically only be done after the race has finished.

-As mentioned above, times and their associated bib IDs can be edited using the "Edit" button in the timing window. You can edit times so that they are no longer in order in the window, like 100 in the screenshot below, and this is not a problem; fsTimer will correctly sort them when generating the results printouts:

+As mentioned above, times and their associated bib IDs can be edited using the "Edit" button in the timing window. You can edit times so that they are no longer in order in the window, like 110 in the screenshot below, and this is not a problem; fsTimer will correctly sort them when generating the results printouts:



-When editing times, it is important to keep the leading 0 ("0:22:37" instead of "22:37") or the results will not sort correctly.

- -Making corrections to anything other than bib IDs and times will require a few more steps. Some examples of the types of corrections you might want to make are: - - -Correcting all of these errors requires making edits to the registration database, which requires leaving the timing window. The following is the procedure you would follow: - - +You can also edit the registration data from the timing window, for example in order to correct overloaded IDs after the timing has finished, or to fix other registration errors that you notice in the printouts.

-When all necessary corrections have been made, the whole timing process is finished!

+Choose "Edit registration data" from the "Options" menu, and this will open the registration database so that it can be edited. Changes will not be autosaved, so be sure to press "Save" before closing. The timing dictionary will be immediately updated upon closing, and so it will require that all of the IDs are unique. Once the edits are made and this window is closed, you will be back in the Timing window, but with the corrected registration data loaded. Pressing "printouts" will print results with the corrections.

This completes all of the details about using fsTimer for a standard, single-lap race - You are now ready to time the race! The next two sections describe additional details to know if you are timing a multi-lap race, or a handicap race.

diff --git a/documentation/documentation_sec4_6.htm b/documentation/documentation_sec4_6.htm index fae06b9..5db4ee8 100644 --- a/documentation/documentation_sec4_6.htm +++ b/documentation/documentation_sec4_6.htm @@ -14,7 +14,7 @@

fsTimer documentation

Section 1 Installing fsTimer
Section 2 Overview
-Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -37,21 +37,23 @@

Section 4.6 - Timing multiple laps




-First, there is an additional column "Completed laps." When an ID is entered, this column will show how many laps this racer has completed. Second, the bottom of the window now shows three counts for checked in racers: "0 | 0 | 0". These correspond to each of the laps (in this project there are three). The first number will increment when a racer completes his/her first lap, the second number after his/her second lap, and so on. For example, here racer 100 has completed two laps and racer 101 has completed one lap:

+First, there is an additional column "Completed laps." When an ID is entered, this column will show how many laps this racer has completed. Second, the bottom of the window now shows three counts for checked in racers: "0 | 0 | 0". These correspond to each of the laps (in this project there are three). The first number will increment when a racer completes his/her first lap, the second number after his/her second lap, and so on. For example, here racer 101 has completed two laps and racer 102 has completed one lap:



-Note that the completed laps column updates all of the entries for an ID, so when racer 100 completed his/her second lap, the "Completed laps" for the first entry was also updated from 1 to 2.

+Note that the completed laps column updates all of the entries for an ID, so when racer 101 completed the second lap, the "Completed laps" for the first entry was also updated from 1 to 2.

+ +If you edit an entry to have a different ID, the lap counts will not be updated.

Consider the following marked results:



-When we press "Print," the results will be sorted by total time and will contain automatically calculated lap times:

+When we press "Printouts," the results will be sorted by total time and will contain automatically calculated lap times:



-Racers that did not complete the correct number of laps (above, racer 102) will not have a total time and so will be placed at the end of the results. Divisional times are similar, and contain automatically calculated lap times.

+Racers that did not complete the correct number of laps will not have a total time and so will be placed at the end of the results. Divisional times are similar, and contain automatically calculated lap times.

All of the same tools for editing times from Section 4.5 apply for lap timing.

diff --git a/documentation/documentation_sec4_7.htm b/documentation/documentation_sec4_7.htm index a3ac949..01a192a 100644 --- a/documentation/documentation_sec4_7.htm +++ b/documentation/documentation_sec4_7.htm @@ -14,7 +14,7 @@

fsTimer documentation

Section 1 Installing fsTimer
Section 2 Overview
-Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
4.1 Setting up for a new race
4.2 Importing preregistration - details
@@ -39,7 +39,7 @@

Section 4.7 - Handicap races


All racers must be given a handicap or they will not show up in the results printout. The handicap must be specified as hh:mm:ss. When inputting a handicap in the registration window, it will verify that the handicap is in the right format and will not allow the entry to be created/edited if handicap is in the wrong format:

: -

+

Above is a handicap of one minute. If a racer has no handicap, then it must be specified as 0:00:00.

@@ -47,14 +47,14 @@

Section 4.7 - Handicap races




-Racer 100 had a handicap of 0:01:00. Racer 104 had no handicap specified. Racer 101 had a handicap of 0:00:50. Racer 102 had a handicap of 0:00:00. Note that racer 102 is the correct way to specify that the racer has no delay in starting.

+Here racer 101 had a handicap of 0:01:00, racer 102 had a handicap of 0:00:00, and racer 103 had a handicap of 0:00:50. Racer 104 had no handicap specified (it was left blank in the registration database). Note that racer 102 is the correct way to specify that the racer has no delay in starting (set it to 0:00:00).

-The results printout will show the corrected time:

+The results printout will show the corrected time (and the Handicap, if specified when creating the project):



-Note that racer 104 does not show up in the results because he did not have a handicap specified. The same thing would happen if his handicap was not correctly formatted. This can be corrected by simpling saving the times, editing the registration file to have a correctly formatted handicap, generating a new timing dictionary, starting the timing window with that timing dictionary, and then loading the saved timing session. (This procedure is described in more detail in Section 4.5).

+During project creation we specified the times to be ranked by Time, which for a handicap race means the corrected time. Note that 104 has no time listed, because no handicap was specified. This could be corrected by editing the registration data ("Options" menu, "Edit registration data") to add a handicap for 104.

Continue on to Additional details for developers.
diff --git a/documentation/documentation_sec5.htm b/documentation/documentation_sec5.htm index c7620aa..cf71e04 100644 --- a/documentation/documentation_sec5.htm +++ b/documentation/documentation_sec5.htm @@ -14,7 +14,7 @@

fsTimer documentation

Section 1 Installing fsTimer
Section 2 Overview
-Section 3 Checklist for timing with fsTimer
+Section 3 Suggestions for race setup
Section 4 Detailed descriptions of fsTimer components
Section 5 Additional details for developers
@@ -28,15 +28,19 @@

Section 5 - Additional details for developers


Contributions of code are always welcome!

-Features for the future

- -The next version of fsTimer will be 0.6, and will probably be released around March 2015. There are several items that are on the "to-do" list for a future version, possibly 0.6. +Changelog for fsTimer 0.6

+fsTimer 0.5 was released on April 9, 2016 and is the fifth major release. The major changes since version 0.5 are: +
  • The entire codebase was migrated from python2 to python3, and from Gtk2/PyGTK to Gtk3/PyGObject. +
  • The user can now specify which fields to include on the printouts, including fields that are computed from other fields by providing a Python expression. +
  • During project creation, the user can specify to include "Pace" on the results printouts. +
  • The user can specify the field according to which results will be ranked (previously had to be Time), and can also choose different ranking fields for each division, if so desired. +
  • Project settings (divisions, printout fields, and ranking fields) can be edited, from the main window menu. +
  • Registration data can be edited from the timing window, allowing for easy correction of errors that are observed in the printouts. +
  • We no longer use any stock icon functionality, which was deprecated in Gtk 3.1. Icon buttons are manually created. +
  • First name, Last name, and Gender are no longer required registration fields. +
  • Various UI changes (moving buttons around, renaming things) based on user feedback. +

    Changelog for fsTimer 0.5

    fsTimer 0.5 was released on Nov. 11, 2014 and is the fourth major release. Version 0.5 came with major improvements, thanks in large part to the development efforts of Sebastien. The major changes since version 0.4 are: diff --git a/fstimer.ico b/fstimer.ico new file mode 100644 index 0000000..ede146a Binary files /dev/null and b/fstimer.ico differ diff --git a/fstimer.py b/fstimer.py old mode 100644 new mode 100755 index d4e79ce..ac2db97 --- a/fstimer.py +++ b/fstimer.py @@ -1,6 +1,17 @@ +#!/usr/bin/env python3 + import fstimer.timer -import gtk +from gi.repository import Gtk -if __name__ == '__main__': +def main(): pytimer = fstimer.timer.PyTimer() - gtk.main() + #####These next two lines are to get icons on stock buttons in windows + ####settings = Gtk.Settings.get_default() + ####settings.props.gtk_button_images = True + Gtk.main() + + +if __name__ == '__main__': + main() + + diff --git a/fstimer/__init__.py b/fstimer/__init__.py index 549785c..9fe96ba 100755 --- a/fstimer/__init__.py +++ b/fstimer/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #fsTimer - free, open source software for race timing. #Copyright 2012-14 Ben Letham diff --git a/fstimer/data/adwaita_icons/COPYING b/fstimer/data/adwaita_icons/COPYING new file mode 100644 index 0000000..c2e0168 --- /dev/null +++ b/fstimer/data/adwaita_icons/COPYING @@ -0,0 +1,10 @@ +This work is licenced under the terms of either the GNU LGPL v3 or +Creative Commons Attribution-Share Alike 3.0 United States License. + +To view a copy of the CC-BY-SA licence, visit +http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative +Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. + +When attributing the artwork, using "GNOME Project" is enough. +Please link to http://www.gnome.org where available. + diff --git a/fstimer/data/adwaita_icons/actions/document-new.png b/fstimer/data/adwaita_icons/actions/document-new.png new file mode 100644 index 0000000..a995737 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/document-new.png differ diff --git a/fstimer/data/adwaita_icons/actions/document-save.png b/fstimer/data/adwaita_icons/actions/document-save.png new file mode 100644 index 0000000..7b0b3b7 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/document-save.png differ diff --git a/fstimer/data/adwaita_icons/actions/edit-clear.png b/fstimer/data/adwaita_icons/actions/edit-clear.png new file mode 100644 index 0000000..72ed789 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/edit-clear.png differ diff --git a/fstimer/data/adwaita_icons/actions/edit-copy.png b/fstimer/data/adwaita_icons/actions/edit-copy.png new file mode 100644 index 0000000..a454881 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/edit-copy.png differ diff --git a/fstimer/data/adwaita_icons/actions/go-down.png b/fstimer/data/adwaita_icons/actions/go-down.png new file mode 100644 index 0000000..d228f04 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/go-down.png differ diff --git a/fstimer/data/adwaita_icons/actions/go-next.png b/fstimer/data/adwaita_icons/actions/go-next.png new file mode 100644 index 0000000..1ae0411 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/go-next.png differ diff --git a/fstimer/data/adwaita_icons/actions/go-previous.png b/fstimer/data/adwaita_icons/actions/go-previous.png new file mode 100644 index 0000000..12fed7f Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/go-previous.png differ diff --git a/fstimer/data/adwaita_icons/actions/go-up.png b/fstimer/data/adwaita_icons/actions/go-up.png new file mode 100644 index 0000000..c7189e9 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/go-up.png differ diff --git a/fstimer/data/adwaita_icons/actions/help-about.png b/fstimer/data/adwaita_icons/actions/help-about.png new file mode 100644 index 0000000..3d9d4da Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/help-about.png differ diff --git a/fstimer/data/adwaita_icons/actions/help-faq.png b/fstimer/data/adwaita_icons/actions/help-faq.png new file mode 100644 index 0000000..ade6da9 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/help-faq.png differ diff --git a/fstimer/data/adwaita_icons/actions/list-add.png b/fstimer/data/adwaita_icons/actions/list-add.png new file mode 100644 index 0000000..3f1347e Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/list-add.png differ diff --git a/fstimer/data/adwaita_icons/actions/list-remove.png b/fstimer/data/adwaita_icons/actions/list-remove.png new file mode 100644 index 0000000..28db302 Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/list-remove.png differ diff --git a/fstimer/data/adwaita_icons/actions/window-close.png b/fstimer/data/adwaita_icons/actions/window-close.png new file mode 100644 index 0000000..a033c4e Binary files /dev/null and b/fstimer/data/adwaita_icons/actions/window-close.png differ diff --git a/fstimer/data/adwaita_icons/apps/accessories-text-editor.png b/fstimer/data/adwaita_icons/apps/accessories-text-editor.png new file mode 100644 index 0000000..31c1aa6 Binary files /dev/null and b/fstimer/data/adwaita_icons/apps/accessories-text-editor.png differ diff --git a/fstimer/data/adwaita_icons/emblems/emblem-default.png b/fstimer/data/adwaita_icons/emblems/emblem-default.png new file mode 100644 index 0000000..8e07437 Binary files /dev/null and b/fstimer/data/adwaita_icons/emblems/emblem-default.png differ diff --git a/fstimer/data/adwaita_icons/status/dialog-error.png b/fstimer/data/adwaita_icons/status/dialog-error.png new file mode 100644 index 0000000..49ed530 Binary files /dev/null and b/fstimer/data/adwaita_icons/status/dialog-error.png differ diff --git a/fstimer/data/adwaita_icons/status/dialog-information.png b/fstimer/data/adwaita_icons/status/dialog-information.png new file mode 100644 index 0000000..cca5804 Binary files /dev/null and b/fstimer/data/adwaita_icons/status/dialog-information.png differ diff --git a/fstimer/data/adwaita_icons/status/dialog-question.png b/fstimer/data/adwaita_icons/status/dialog-question.png new file mode 100644 index 0000000..2361f74 Binary files /dev/null and b/fstimer/data/adwaita_icons/status/dialog-question.png differ diff --git a/fstimer/data/adwaita_icons/status/dialog-warning.png b/fstimer/data/adwaita_icons/status/dialog-warning.png new file mode 100644 index 0000000..a9ff09b Binary files /dev/null and b/fstimer/data/adwaita_icons/status/dialog-warning.png differ diff --git a/fstimer/data/adwaita_icons/status/folder-open.png b/fstimer/data/adwaita_icons/status/folder-open.png new file mode 100644 index 0000000..9e0b397 Binary files /dev/null and b/fstimer/data/adwaita_icons/status/folder-open.png differ diff --git a/fstimer/data/fstimer_default_project.reg b/fstimer/data/fstimer_default_project.reg index 1947db7..b751cdb 100644 --- a/fstimer/data/fstimer_default_project.reg +++ b/fstimer/data/fstimer_default_project.reg @@ -1 +1 @@ -{"fields": ["Last name", "First name", "ID", "Age", "Gender", "Address", "Email", "Telephone", "Contact for future races", "How did you hear about race"], "clear_for_fam": ["First name", "ID", "Age", "Gender"], "fieldsdic": {"How did you hear about race": {"max": 40, "type": "entrybox"}, "Last name": {"max": 30, "type": "entrybox"}, "Gender": {"type": "combobox", "options": ["male", "female"]}, "Age": {"max": 3, "type": "entrybox"}, "Telephone": {"max": 20, "type": "entrybox"}, "Email": {"max": 40, "type": "entrybox"}, "First name": {"max": 30, "type": "entrybox"}, "Address": {"max": 90, "type": "entrybox"}, "Contact for future races": {"type": "combobox", "options": ["yes", "no"]}, "ID": {"max": 6, "type": "entrybox"}}, "divisions": [["All females", {"Gender": "female"}], ["All males", {"Gender": "male"}], ["Female, ages 9 and under", {"Gender": "female", "Age": [0, 9]}], ["Male, ages 9 and under", {"Gender": "male", "Age": [0, 9]}], ["Female, ages 10-14", {"Gender": "female", "Age": [10, 14]}], ["Male, ages 10-14", {"Gender": "male", "Age": [10, 14]}], ["Female, ages 15-19", {"Gender": "female", "Age": [15, 19]}], ["Male, ages 15-19", {"Gender": "male", "Age": [15, 19]}], ["Female, ages 20-24", {"Gender": "female", "Age": [20, 24]}], ["Male, ages 20-24", {"Gender": "male", "Age": [20, 24]}], ["Female, ages 25-29", {"Gender": "female", "Age": [25, 29]}], ["Male, ages 25-29", {"Gender": "male", "Age": [25, 29]}], ["Female, ages 30-34", {"Gender": "female", "Age": [30, 34]}], ["Male, ages 30-34", {"Gender": "male", "Age": [30, 34]}], ["Female, ages 35-39", {"Gender": "female", "Age": [35, 39]}], ["Male, ages 35-39", {"Gender": "male", "Age": [35, 39]}], ["Female, ages 40-44", {"Gender": "female", "Age": [40, 44]}], ["Male, ages 40-44", {"Gender": "male", "Age": [40, 44]}], ["Female, ages 45-49", {"Gender": "female", "Age": [45, 49]}], ["Male, ages 45-49", {"Gender": "male", "Age": [45, 49]}], ["Female, ages 50-54", {"Gender": "female", "Age": [50, 54]}], ["Male, ages 50-54", {"Gender": "male", "Age": [50, 54]}], ["Female, ages 55-59", {"Gender": "female", "Age": [55, 59]}], ["Male, ages 55-59", {"Gender": "male", "Age": [55, 59]}], ["Female, ages 60-64", {"Gender": "female", "Age": [60, 64]}], ["Male, ages 60-64", {"Gender": "male", "Age": [60, 64]}], ["Female, ages 65-69", {"Gender": "female", "Age": [65, 69]}], ["Male, ages 65-69", {"Gender": "male", "Age": [65, 69]}], ["Female, ages 70-74", {"Gender": "female", "Age": [70, 74]}], ["Male, ages 70-74", {"Gender": "male", "Age": [70, 74]}], ["Female, ages 75-79", {"Gender": "female", "Age": [75, 79]}], ["Male, ages 75-79", {"Gender": "male", "Age": [75, 79]}], ["Female, ages 80 and up", {"Gender": "female", "Age": [80, 120]}], ["Male, ages 80 and up", {"Gender": "male", "Age": [80, 120]}]], "projecttype": "standard", "numlaps": 1} \ No newline at end of file +{"fields": ["Last name", "First name", "ID", "Age", "Gender", "Email"], "fieldsdic": {"Last name": {"max": 30, "type": "entrybox"}, "Gender": {"type": "combobox", "options": ["male", "female"]}, "Age": {"max": 3, "type": "entrybox"}, "Email": {"max": 40, "type": "entrybox"}, "First name": {"max": 30, "type": "entrybox"}, "ID": {"max": 6, "type": "entrybox"}}, "divisions": [["All females", {"Gender": "female"}], ["All males", {"Gender": "male"}], ["Female, ages 19 and under", {"Gender": "female", "Age": [0, 19]}], ["Male, ages 19 and under", {"Gender": "male", "Age": [0, 19]}], ["Female, ages 20-49", {"Gender": "female", "Age": [20, 49]}], ["Male, ages 20-49", {"Gender": "male", "Age": [20, 49]}], ["Female, ages 50 and up", {"Gender": "female", "Age": [50, 120]}], ["Male, ages 50 and up", {"Gender": "male", "Age": [50, 120]}], ["70 and up", {"Age": [70, 120]}]], "projecttype": "standard", "numlaps": 1, "printfields": {"Time": "{Time}", "ID": "{ID}", "Age": "{Age}", "Gender": "{Gender}", "Handicap": "{Handicap}", "Name": "{First name} + ' ' + {Last name}"}, "rankings": {"Overall": "Time", "All females": "Time", "All males": "Time", "Female, ages 19 and under": "Time", "Male, ages 19 and under": "Time", "Female, ages 20-49": "Time", "Male, ages 20-49": "Time", "Female, ages 50 and up": "Time", "Male, ages 50 and up": "Time"}} \ No newline at end of file diff --git a/fstimer/gui/GtkStockButton.py b/fstimer/gui/GtkStockButton.py new file mode 100644 index 0000000..72d835c --- /dev/null +++ b/fstimer/gui/GtkStockButton.py @@ -0,0 +1,59 @@ +#fsTimer - free, open source software for race timing. +#Copyright 2012-15 Ben Letham + +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation, either version 3 of the License, or +#(at your option) any later version. + +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. + +#You should have received a copy of the GNU General Public License +#along with this program. If not, see . + +#The author/copyright holder can be contacted at bletham@gmail.com + +'''This class reproduces the stock buttons from Gtk3, while avoiding the +"The property GtkButton:use-stock is deprecated" warnings. +''' + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +import os + +icon_files = {'new': 'actions/document-new-symbolic.svg', + 'close': 'actions/window-close-symbolic.svg', + 'ok': 'emblems/emblem-ok-symbolic.svg', + 'remove': 'actions/list-remove-symbolic.svg', + 'add': 'actions/list-add-symbolic.svg', + 'up': 'actions/go-up-symbolic.svg', + 'down': 'actions/go-down-symbolic.svg', + 'edit': 'apps/text-editor-symbolic.svg', + 'copy': 'actions/edit-copy-symbolic.svg', + 'back': 'actions/go-previous-symbolic.svg', + 'forward': 'actions/go-next-symbolic.svg', + 'open': 'actions/folder-open-symbolic.svg', + 'clear': 'actions/edit-clear-symbolic.svg', + 'save': 'actions/document-save-symbolic.svg', + 'clock': 'actions/document-open-recent-symbolic.svg', + } + +class GtkStockButton(Gtk.Button): + + def __init__(self, icon_name, label_text): + #Init a regular Gtk.Button + Gtk.Button.__init__(self) + #Add the icon and the label. + fname = os.path.join('fstimer/data/adwaita_icons', icon_files[icon_name]) + btnIcon = Gtk.Image.new_from_file(fname) + btnLabel = Gtk.Label(label_text+' ') + #pack 'em into an HBox + btnHbox = Gtk.HBox(False, 0) + btnHbox.pack_start(btnIcon, False, False, 0) + btnHbox.pack_start(btnLabel, True, True, 0) + self.add(btnHbox) + return \ No newline at end of file diff --git a/fstimer/gui/__init__.py b/fstimer/gui/__init__.py index 051753d..ef29b22 100644 --- a/fstimer/gui/__init__.py +++ b/fstimer/gui/__init__.py @@ -17,7 +17,7 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''This module provides a set of GTK windows that are used by fsTimer for its gui''' -import gtk +from gi.repository import Gdk # background color for all windows -bgcolor = gtk.gdk.color_parse('#F0F0F0') +bgcolor = Gdk.color_parse('#F0F0F0') \ No newline at end of file diff --git a/fstimer/gui/about.py b/fstimer/gui/about.py index 87263c5..c43f250 100644 --- a/fstimer/gui/about.py +++ b/fstimer/gui/about.py @@ -17,26 +17,36 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handles the about window of the application''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GdkPixbuf +import os -class AboutWin(gtk.AboutDialog): +class AboutWin(Gtk.AboutDialog): '''Handles the about window of the application''' - def __init__(self): + def __init__(self, parent): '''Creates the about window''' super(AboutWin, self).__init__() - self.set_logo(gtk.gdk.pixbuf_new_from_file("fstimer/data/icon.png")) + self.set_transient_for(parent) + self.set_modal(True) + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/icon.png')) + self.set_logo(GdkPixbuf.Pixbuf.new_from_file(fname)) self.set_program_name('fsTimer') - self.set_version('0.5') - self.set_copyright("""Copyright 2012-14 Ben Letham\ + self.set_version('0.6') + self.set_copyright("""Copyright 2012-16 Ben Letham\ \nThis program comes with ABSOLUTELY NO WARRANTY; for details see license.\ \nThis is free software, and you are welcome to redistribute it under certain conditions; see license for details""") - self.set_comments('free, open source software for race timing.') - self.set_website('http://fstimer.org') + self.set_comments('free, open source software for race timing.\nhttp://fstimer.org') self.set_wrap_license(False) - with open('COPYING', 'r') as fin: + fname_c = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../../COPYING')) + with open(fname_c, 'r') as fin: gpl = fin.read() self.set_license(gpl) self.set_authors(['Ben Letham', diff --git a/fstimer/gui/compile.py b/fstimer/gui/compile.py index 79e19f6..dc2f48b 100644 --- a/fstimer/gui/compile.py +++ b/fstimer/gui/compile.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,36 +17,41 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window dedicated to compilation of registrations from multiple computers''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui import os +from fstimer.gui.util_classes import GtkStockButton -class CompilationWin(gtk.Window): +class CompilationWin(Gtk.Window): '''Handling of the window dedicated to compilation of registrations from multiple computers''' def __init__(self, path, merge_cb): '''Builds and display the compilation window''' - super(CompilationWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(CompilationWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.path = path self.merge_cb = merge_cb - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_icon_from_file('fstimer/data/icon.png') - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/icon.png')) + self.set_icon_from_file(fname) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) self.connect('delete_event', lambda b, jnk: self.hide()) self.set_border_width(10) self.set_size_request(600, 450) # We will use a liststore to hold the filenames of the # registrations to be merged, and put the liststore in a scrolledwindow - compregsw = gtk.ScrolledWindow() - compregsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - compregsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.reglist = gtk.ListStore(str) - self.comptreeview = gtk.TreeView() - rendererText = gtk.CellRendererText() - column = gtk.TreeViewColumn('Registration files', rendererText, text=0) + compregsw = Gtk.ScrolledWindow() + compregsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + compregsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self.reglist = Gtk.ListStore(str) + self.comptreeview = Gtk.TreeView() + rendererText = Gtk.CellRendererText() + column = Gtk.TreeViewColumn('Registration files', rendererText, text=0) column.set_sort_column_id(0) self.comptreeview.append_column(column) self.comptreeview.set_model(self.reglist) @@ -54,43 +59,43 @@ def __init__(self, path, merge_cb): # We have text below the window to explain what is happening during merging self.comblabel = [] for i in range(3): - label = gtk.Label('') + label = Gtk.Label(label='') label.set_alignment(0, 0.5) self.comblabel.append(label) # Pack it all - regvbox1 = gtk.VBox(False, 8) - regvbox1.pack_start(gtk.Label('Select all of the registration files to merge'), False, False, 0) + regvbox1 = Gtk.VBox(False, 8) + regvbox1.pack_start(Gtk.Label('Select all of the registration files to compile', True, True, 0), False, False, 0) regvbox1.pack_start(compregsw, True, True, 0) for i in range(3): regvbox1.pack_start(self.comblabel[i], False, False, 0) - vbox1align = gtk.Alignment(0, 0, 1, 1) + vbox1align = Gtk.Alignment.new(0, 0, 1, 1) vbox1align.add(regvbox1) # The buttons in a table - regtable = gtk.Table(2, 1, False) + regtable = Gtk.Table(2, 1, False) regtable.set_row_spacings(5) regtable.set_col_spacings(5) regtable.set_border_width(5) - btnREMOVE = gtk.Button(stock=gtk.STOCK_REMOVE) + btnREMOVE = GtkStockButton('remove','Remove') btnREMOVE.connect('clicked', self.rm_clicked) - btnADD = gtk.Button(stock=gtk.STOCK_ADD) + btnADD = GtkStockButton('add','Add') btnADD.connect('clicked', self.add_clicked) - btnMERGE = gtk.Button('Merge') + btnMERGE = Gtk.Button('Compile') btnMERGE.connect('clicked', self.merge_clicked) - btnOK = gtk.Button('Done') + btnOK = GtkStockButton('close','Close') btnOK.connect('clicked', lambda jnk: self.hide()) - vsubbox = gtk.VBox(False, 8) + vsubbox = Gtk.VBox(False, 8) vsubbox.pack_start(btnMERGE, False, False, 0) vsubbox.pack_start(btnOK, False, False, 0) - regvspacer = gtk.Alignment(1, 1, 0, 0) + regvspacer = Gtk.Alignment.new(1, 1, 0, 0) regvspacer.add(vsubbox) regtable.attach(regvspacer, 0, 1, 1, 2) - regvbox2 = gtk.VBox(False, 8) + regvbox2 = Gtk.VBox(False, 8) regvbox2.pack_start(btnREMOVE, False, False, 0) regvbox2.pack_start(btnADD, False, False, 0) - regvbalign = gtk.Alignment(1, 0, 0, 0) + regvbalign = Gtk.Alignment.new(1, 0, 0, 0) regvbalign.add(regvbox2) regtable.attach(regvbalign, 0, 1, 0, 1) - reghbox = gtk.HBox(False, 8) + reghbox = Gtk.HBox(False, 8) reghbox.pack_start(vbox1align, True, True, 0) reghbox.pack_start(regtable, False, False, 0) #Add and show @@ -107,15 +112,15 @@ def rm_clicked(self, jnk_unused): def add_clicked(self, jnk_unused): '''Handling click on Add button, using a FileChooser''' - chooser = gtk.FileChooserDialog(title='Select registration files', action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_ADD, gtk.RESPONSE_OK)) + chooser = Gtk.FileChooserDialog(title='Select registration files', parent=self, action=Gtk.FileChooserAction.OPEN, buttons=('Cancel', Gtk.ResponseType.CANCEL, 'Add', Gtk.ResponseType.OK)) chooser.set_select_multiple(True) - ffilter = gtk.FileFilter() + ffilter = Gtk.FileFilter() ffilter.set_name('Registration files') ffilter.add_pattern('*registration_*.json') chooser.add_filter(ffilter) - chooser.set_current_folder(os.path.join(os.getcwd(), self.path)) + chooser.set_current_folder(self.path) response = chooser.run() - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: filenames = chooser.get_filenames() for filenm in filenames: self.reglist.append([filenm]) @@ -125,7 +130,7 @@ def merge_clicked(self, jnk_unused): '''Handling click on Merge button''' # Grab all of the filenames from the liststore filenames = [] - self.reglist.foreach(lambda model, path, titer: filenames.append(model.get_value(titer, 0))) + self.reglist.foreach(lambda model, gtkpath, titer: filenames.append(model.get_value(titer, 0))) self.merge_cb(filenames) def resetLabels(self): diff --git a/fstimer/gui/compileerrors.py b/fstimer/gui/compileerrors.py index 9a3cc59..cb1bf1b 100644 --- a/fstimer/gui/compileerrors.py +++ b/fstimer/gui/compileerrors.py @@ -17,17 +17,19 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window dedicated to the display of registration's compilation's errors''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +import os +from fstimer.gui.util_classes import GtkStockButton -class CompilationErrorsWin(gtk.Window): +class CompilationErrorsWin(Gtk.Window): '''Handling of the window dedicated to the display of registration's compilation's errors''' def __init__(self, path, parent, errors, fields, timedict, allok_cb): '''Builds and display the compilation error window''' - super(CompilationErrorsWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(CompilationErrorsWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.path = path self.errors = errors self.fields = fields @@ -36,23 +38,23 @@ def __init__(self, path, parent, errors, fields, timedict, allok_cb): self.corerrorswin = None self.corerrorlist = None self.corerrortreeview = None - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.set_transient_for(parent) self.set_modal(True) - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) self.connect('delete_event', lambda b, jnk: self.hide()) self.set_border_width(10) self.set_size_request(450, 300) # make a liststore with all of the overloaded IDs (that is, # the keys of errors) and put it in a scrolled window - comperrorsw = gtk.ScrolledWindow() - comperrorsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - comperrorsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.errorlist = gtk.ListStore(str) - self.errortreeview = gtk.TreeView() - rendererText = gtk.CellRendererText() - column = gtk.TreeViewColumn('Overloaded IDs', rendererText, text=0) + comperrorsw = Gtk.ScrolledWindow() + comperrorsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + comperrorsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self.errorlist = Gtk.ListStore(str) + self.errortreeview = Gtk.TreeView() + rendererText = Gtk.CellRendererText() + column = Gtk.TreeViewColumn('Overloaded IDs', rendererText, text=0) column.set_sort_column_id(0) self.errortreeview.append_column(column) # add on the IDs from errors @@ -60,22 +62,22 @@ def __init__(self, path, parent, errors, fields, timedict, allok_cb): self.errorlist.append([errorid]) self.errortreeview.set_model(self.errorlist) comperrorsw.add(self.errortreeview) - errvbox1 = gtk.VBox(False, 8) - errvbox1.pack_start(gtk.Label('These IDs were assigned to multiple entries.\nThey will be left unassigned.'), False, False, 0) + errvbox1 = Gtk.VBox(False, 8) + errvbox1.pack_start(Gtk.Label('These IDs were assigned to multiple entries.\nThey will be left unassigned.', True, True, 0), False, False, 0) errvbox1.pack_start(comperrorsw, True, True, 0) - vbox1align = gtk.Alignment(0, 0, 1, 1) + vbox1align = Gtk.Alignment.new(0, 0, 1, 1) vbox1align.add(errvbox1) # buttons - btnVIEW = gtk.Button('View ID entries') + btnVIEW = Gtk.Button('View ID entries') btnVIEW.connect('clicked', self.view_entries_clicked) - btnOK = gtk.Button(stock=gtk.STOCK_OK) + btnOK = GtkStockButton('ok','OK') btnOK.connect('clicked', self.ok_error) - errvbalign = gtk.Alignment(1, 0, 0, 0) - vbox2 = gtk.VBox(False, 10) + errvbalign = Gtk.Alignment.new(1, 0, 0, 0) + vbox2 = Gtk.VBox(False, 10) vbox2.pack_start(errvbalign, True, True, 0) vbox2.pack_start(btnVIEW, False, False, 0) vbox2.pack_start(btnOK, False, False, 0) - hbox = gtk.HBox(False, 10) + hbox = Gtk.HBox(False, 10) hbox.pack_start(vbox1align, False, False, 0) hbox.pack_start(vbox2, False, False, 0) self.add(hbox) @@ -90,25 +92,25 @@ def view_entries_clicked(self, jnk_unused): if treeiter: current_id = self.errorlist.get_value(treeiter, 0) # Define the new window - self.corerrorswin = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.corerrorswin.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.corerrorswin = Gtk.Window(Gtk.WindowType.TOPLEVEL) + self.corerrorswin.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.corerrorswin.set_transient_for(self) self.corerrorswin.set_modal(True) - self.corerrorswin.set_title('fsTimer - ' + self.path) - self.corerrorswin.set_position(gtk.WIN_POS_CENTER) + self.corerrorswin.set_title('fsTimer - ' + os.path.basename(self.path)) + self.corerrorswin.set_position(Gtk.WindowPosition.CENTER) self.corerrorswin.connect('delete_event', lambda b, jnk: self.corerrorswin.hide()) self.corerrorswin.set_border_width(10) self.corerrorswin.set_size_request(800, 300) # This will be a liststore in a treeview in a scrolled window, as usual - corerrorsw = gtk.ScrolledWindow() - corerrorsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - corerrorsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self.corerrorlist = gtk.ListStore(*[str for field in self.fields]) - self.corerrortreeview = gtk.TreeView() + corerrorsw = Gtk.ScrolledWindow() + corerrorsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + corerrorsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self.corerrorlist = Gtk.ListStore(*[str for field in self.fields]) + self.corerrortreeview = Gtk.TreeView() # Now define each column in the treeview # Take these from self.fields for (colid, field) in enumerate(self.fields): - column = gtk.TreeViewColumn(field, gtk.CellRendererText(), text=colid) + column = Gtk.TreeViewColumn(field, Gtk.CellRendererText(), text=colid) column.set_sort_column_id(colid) self.corerrortreeview.append_column(column) # Add in the info from self.errors @@ -116,33 +118,33 @@ def view_entries_clicked(self, jnk_unused): self.corerrorlist.append([reg[field] for field in self.fields]) self.corerrortreeview.set_model(self.corerrorlist) corerrorsw.add(self.corerrortreeview) - errvbox1 = gtk.VBox(False, 8) - errvbox1.pack_start(gtk.Label('''These entries share the same ID. + errvbox1 = Gtk.VBox(False, 8) + errvbox1.pack_start(Gtk.Label('''These entries share the same ID. Use "Keep entry" to associate an entry with the ID. Otherwise, press "OK" to continue, no entry will be associated with this ID.'''), False, False, 0) errvbox1.pack_start(corerrorsw, True, True, 0) - vbox1align = gtk.Alignment(0, 0, 1, 1) + vbox1align = Gtk.Alignment.new(0, 0, 1, 1) vbox1align.add(errvbox1) #Now build a table for the buttons - errtable = gtk.Table(2, 1, False) + errtable = Gtk.Table(2, 1, False) errtable.set_row_spacings(5) errtable.set_col_spacings(5) errtable.set_border_width(5) - btnKEEP = gtk.Button('Keep entry') + btnKEEP = Gtk.Button('Keep entry') btnKEEP.connect('clicked', self.keep_correct, current_id, treeiter) - btnCANCEL = gtk.Button(stock=gtk.STOCK_OK) + btnCANCEL = GtkStockButton('close','Close') btnCANCEL.connect('clicked', lambda b: self.corerrorswin.hide()) - vsubbox = gtk.VBox(False, 8) + vsubbox = Gtk.VBox(False, 8) vsubbox.pack_start(btnCANCEL, False, False, 0) - errvspacer = gtk.Alignment(1, 1, 0, 0) + errvspacer = Gtk.Alignment.new(1, 1, 0, 0) errvspacer.add(vsubbox) errtable.attach(errvspacer, 0, 1, 1, 2) - errvbox2 = gtk.VBox(False, 8) + errvbox2 = Gtk.VBox(False, 8) errvbox2.pack_start(btnKEEP, False, False, 0) - errvbalign = gtk.Alignment(1, 0, 0, 0) + errvbalign = Gtk.Alignment.new(1, 0, 0, 0) errvbalign.add(errvbox2) errtable.attach(errvbalign, 0, 1, 0, 1) - errhbox = gtk.HBox(False, 8) + errhbox = Gtk.HBox(False, 8) errhbox.pack_start(vbox1align, True, True, 0) errhbox.pack_start(errtable, False, False, 0) self.corerrorswin.add(errhbox) diff --git a/fstimer/gui/definedivisions.py b/fstimer/gui/definedivisions.py index 78f5b76..ca22cea 100644 --- a/fstimer/gui/definedivisions.py +++ b/fstimer/gui/definedivisions.py @@ -17,56 +17,57 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window where divisions are defined''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton -class DivisionsWin(gtk.Window): +class DivisionsWin(Gtk.Window): '''Handling of the window where divisions are defined''' - def __init__(self, fields, fieldsdic, divisions, back_clicked_cb, next_clicked_cb, parent): + def __init__(self, fields, fieldsdic, divisions, back_clicked_cb, next_clicked_cb, parent, edit): '''Creates divisions window''' - super(DivisionsWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(DivisionsWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.divisions = divisions self.fields = fields self.fieldsdic = fieldsdic self.winnewdiv = None - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.set_transient_for(parent) self.set_modal(True) - self.set_title('fsTimer - New project') - self.set_position(gtk.WIN_POS_CENTER) + self.set_title('fsTimer - Divisions') + self.set_position(Gtk.WindowPosition.CENTER) self.set_border_width(20) self.set_size_request(800, 500) self.connect('delete_event', lambda b, jnk_unused: self.hide()) # Now create the vbox. - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) self.add(vbox) # Now add the text. - label2_0 = gtk.Label("""Specify the divisions for reporting divisional places.\ + label2_0 = Gtk.Label("""Specify the divisions for reporting divisional places.\ \nPress 'Forward' to continue with the default settings, or make edits below.\ \n\nDivisions can be any combination of age range and combobox fields.""") # Make the liststore, with columns: # name | min age | max age | (... all other combobox fields...) # To do this we first count the number of combobox fields ncbfields = len([field for field in fields if fieldsdic[field]['type'] == 'combobox']) - self.divmodel = gtk.ListStore(*[str for i_unused in range(ncbfields+3)]) + self.divmodel = Gtk.ListStore(*[str for i_unused in range(ncbfields+3)]) #We will put the liststore in a treeview - self.divview = gtk.TreeView() + self.divview = Gtk.TreeView() #Add each of the columns Columns = {} - Columns[1] = gtk.TreeViewColumn('Division name', gtk.CellRendererText(), text=0) + Columns[1] = Gtk.TreeViewColumn('Division name', Gtk.CellRendererText(), text=0) self.divview.append_column(Columns[1]) - Columns[2] = gtk.TreeViewColumn('Min age', gtk.CellRendererText(), text=1) + Columns[2] = Gtk.TreeViewColumn('Min age', Gtk.CellRendererText(), text=1) self.divview.append_column(Columns[2]) - Columns[3] = gtk.TreeViewColumn('Max age', gtk.CellRendererText(), text=2) + Columns[3] = Gtk.TreeViewColumn('Max age', Gtk.CellRendererText(), text=2) self.divview.append_column(Columns[3]) #And now the additional columns textcount = 3 for field in fields: if fieldsdic[field]['type'] == 'combobox': - Columns[field] = gtk.TreeViewColumn(field, gtk.CellRendererText(), text=textcount) + Columns[field] = Gtk.TreeViewColumn(field, Gtk.CellRendererText(), text=textcount) textcount += 1 self.divview.append_column(Columns[field]) #Now we populate the model with the default fields @@ -92,48 +93,54 @@ def __init__(self, fields, fieldsdic, divisions, back_clicked_cb, next_clicked_c self.divview.set_model(self.divmodel) selection = self.divview.get_selection() #And put it in a scrolled window, in an alignment - divsw = gtk.ScrolledWindow() - divsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - divsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + divsw = Gtk.ScrolledWindow() + divsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + divsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) divsw.add(self.divview) - divalgn = gtk.Alignment(0, 0, 1, 1) + divalgn = Gtk.Alignment.new(0, 0, 1, 1) divalgn.add(divsw) #Now we put the buttons on the side. - vbox2 = gtk.VBox(False, 10) - btnUP = gtk.Button(stock=gtk.STOCK_GO_UP) + vbox2 = Gtk.VBox(False, 10) + btnUP = GtkStockButton('up','Up') btnUP.connect('clicked', self.div_up, selection) vbox2.pack_start(btnUP, False, False, 0) - btnDOWN = gtk.Button(stock=gtk.STOCK_GO_DOWN) + btnDOWN = GtkStockButton('down','Down') btnDOWN.connect('clicked', self.div_down, selection) vbox2.pack_start(btnDOWN, False, False, 0) - btnEDIT = gtk.Button(stock=gtk.STOCK_EDIT) + btnEDIT = GtkStockButton('edit','Edit') btnEDIT.connect('clicked', self.div_edit, selection) vbox2.pack_start(btnEDIT, False, False, 0) - btnREMOVE = gtk.Button(stock=gtk.STOCK_REMOVE) + btnREMOVE = GtkStockButton('remove','Remove') btnREMOVE.connect('clicked', self.div_remove, selection) vbox2.pack_start(btnREMOVE, False, False, 0) - btnNEW = gtk.Button(stock=gtk.STOCK_NEW) + btnNEW = GtkStockButton('new','New') btnNEW.connect('clicked', self.div_new, ('', {}), None) vbox2.pack_start(btnNEW, False, False, 0) + btnCOPY = GtkStockButton('copy','Copy') + btnCOPY.connect('clicked', self.div_copy, selection) + vbox2.pack_start(btnCOPY, False, False, 0) #And an hbox for the fields and the buttons - hbox4 = gtk.HBox(False, 0) + hbox4 = Gtk.HBox(False, 0) hbox4.pack_start(divalgn, True, True, 10) hbox4.pack_start(vbox2, False, False, 0) ##And an hbox with 3 buttons - hbox3 = gtk.HBox(False, 0) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + hbox3 = Gtk.HBox(False, 0) + btnCANCEL = GtkStockButton('close', 'Close') btnCANCEL.connect('clicked', lambda btn: self.hide()) - alignCANCEL = gtk.Alignment(0, 0, 0, 0) + alignCANCEL = Gtk.Alignment.new(0, 0, 0, 0) alignCANCEL.add(btnCANCEL) - btnBACK = gtk.Button(stock=gtk.STOCK_GO_BACK) - btnBACK.connect('clicked', back_clicked_cb) - btnNEXT = gtk.Button(stock=gtk.STOCK_GO_FORWARD) - btnNEXT.connect('clicked', next_clicked_cb) + btnBACK = GtkStockButton('back', 'Back') + if edit: + btnBACK.set_sensitive(False) + else: + btnBACK.connect('clicked', back_clicked_cb) + btnNEXT = GtkStockButton('forward', 'Next') + btnNEXT.connect('clicked', next_clicked_cb, edit) ##And populate hbox3.pack_start(alignCANCEL, True, True, 0) hbox3.pack_start(btnBACK, False, False, 2) hbox3.pack_start(btnNEXT, False, False, 0) - alignText = gtk.Alignment(0, 0, 0, 0) + alignText = Gtk.Alignment.new(0, 0, 0, 0) alignText.add(label2_0) vbox.pack_start(alignText, False, False, 0) vbox.pack_start(hbox4, True, True, 0) @@ -177,17 +184,17 @@ def div_edit(self, jnk_unused, selection): def div_new(self, jnk_unused, divtupl, treeiter): '''handles a click on NEW button''' - self.winnewdiv = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.winnewdiv.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.winnewdiv = Gtk.Window(Gtk.WindowType.TOPLEVEL) + self.winnewdiv.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.winnewdiv.set_transient_for(self) self.winnewdiv.set_modal(True) - self.winnewdiv.set_title('fsTimer - New project') - self.winnewdiv.set_position(gtk.WIN_POS_CENTER) + self.winnewdiv.set_title('fsTimer - New division') + self.winnewdiv.set_position(Gtk.WindowPosition.CENTER) self.winnewdiv.set_border_width(20) self.winnewdiv.connect('delete_event', lambda b, jnk_unused: self.winnewdiv.hide()) #Prepare for packing. - vbox = gtk.VBox(False, 10) - windescr = gtk.Label('Use the checkboxes to select which fields to use to define this division,\nand then select the corresponding value to be used for this division.') + vbox = Gtk.VBox(False, 10) + windescr = Gtk.Label('Use the checkboxes to select which fields to use to define this division,\nand then select the corresponding value to be used for this division.') vbox.pack_start(windescr, False, False, 0) HBoxes = {} CheckButtons = {} @@ -196,30 +203,33 @@ def div_new(self, jnk_unused, divtupl, treeiter): divnamein = divtupl[0] divdic = divtupl[1] #First name of the divisional. - divnamelbl = gtk.Label('Division name:') - divnameentry = gtk.Entry(max=80) + divnamelbl = Gtk.Label(label='Division name:') + divnameentry = Gtk.Entry() + divnameentry.set_max_length(80) divnameentry.set_width_chars(40) divnameentry.set_text(divnamein) #set to initial value - HBoxes[1] = gtk.HBox(False, 10) #an int as key so it will never collide with a user field + HBoxes[1] = Gtk.HBox(False, 10) #an int as key so it will never collide with a user field HBoxes[1].pack_start(divnamelbl, False, False, 0) HBoxes[1].pack_start(divnameentry, False, False, 0) vbox.pack_start(HBoxes[1], False, False, 0) #Then do Age - CheckButtons['Age'] = gtk.CheckButton(label='Age:') + CheckButtons['Age'] = Gtk.CheckButton(label='Age:') if 'Age' in divdic: #if minage, then also maxage - we always have both. CheckButtons['Age'].set_active(True) - minageadj = gtk.Adjustment(value=divdic['Age'][0], lower=0, upper=120, step_incr=1) - maxageadj = gtk.Adjustment(value=divdic['Age'][1], lower=0, upper=120, step_incr=1) + minageadj = Gtk.Adjustment(value=divdic['Age'][0], lower=0, upper=120, step_incr=1) + maxageadj = Gtk.Adjustment(value=divdic['Age'][1], lower=0, upper=120, step_incr=1) else: - minageadj = gtk.Adjustment(value=0, lower=0, upper=120, step_incr=1) - maxageadj = gtk.Adjustment(value=120, lower=0, upper=120, step_incr=1) - minagelbl = gtk.Label('Min age (inclusive):') - minagebtn = gtk.SpinButton(minageadj, digits=0, climb_rate=0) - maxagelbl = gtk.Label('Max age (inclusive):') - maxagebtn = gtk.SpinButton(maxageadj, digits=0, climb_rate=0) + minageadj = Gtk.Adjustment(value=0, lower=0, upper=120, step_incr=1) + maxageadj = Gtk.Adjustment(value=120, lower=0, upper=120, step_incr=1) + minagelbl = Gtk.Label(label='Min age (inclusive):') + minagebtn = Gtk.SpinButton(digits=0, climb_rate=0) + minagebtn.set_adjustment(minageadj) + maxagelbl = Gtk.Label(label='Max age (inclusive):') + maxagebtn = Gtk.SpinButton(digits=0, climb_rate=0) + maxagebtn.set_adjustment(maxageadj) #Make an hbox of it. - HBoxes['Age'] = gtk.HBox(False, 10) + HBoxes['Age'] = Gtk.HBox(False, 10) HBoxes['Age'].pack_start(CheckButtons['Age'], False, False, 0) HBoxes['Age'].pack_start(minagelbl, False, False, 0) HBoxes['Age'].pack_start(minagebtn, False, False, 0) @@ -230,26 +240,26 @@ def div_new(self, jnk_unused, divtupl, treeiter): for field in self.fields: if self.fieldsdic[field]['type'] == 'combobox': #Add it. - CheckButtons[field] = gtk.CheckButton(label=field+':') - ComboBoxes[field] = gtk.combo_box_new_text() + CheckButtons[field] = Gtk.CheckButton(label=field+':') + ComboBoxes[field] = Gtk.ComboBoxText() for option in self.fieldsdic[field]['options']: ComboBoxes[field].append_text(option) if field in divdic and divdic[field]: CheckButtons[field].set_active(True) #the box is checked ComboBoxes[field].set_active(self.fieldsdic[field]['options'].index(divdic[field])) #set to initial value #Put it in an HBox - HBoxes[field] = gtk.HBox(False, 10) + HBoxes[field] = Gtk.HBox(False, 10) HBoxes[field].pack_start(CheckButtons[field], False, False, 0) HBoxes[field].pack_start(ComboBoxes[field], False, False, 0) vbox.pack_start(HBoxes[field], False, False, 0) #On to the bottom buttons - btnOK = gtk.Button(stock=gtk.STOCK_OK) + btnOK = GtkStockButton('ok','OK') btnOK.connect('clicked', self.winnewdivOK, treeiter, CheckButtons, ComboBoxes, minagebtn, maxagebtn, divnameentry) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + btnCANCEL = GtkStockButton('close','Cancel') btnCANCEL.connect('clicked', lambda b: self.winnewdiv.hide()) - cancel_algn = gtk.Alignment(0, 0, 0, 0) + cancel_algn = Gtk.Alignment.new(0, 0, 0, 0) cancel_algn.add(btnCANCEL) - hbox3 = gtk.HBox(False, 10) + hbox3 = Gtk.HBox(False, 10) hbox3.pack_start(cancel_algn, True, True, 0) hbox3.pack_start(btnOK, False, False, 0) vbox.pack_start(hbox3, False, False, 0) @@ -266,6 +276,33 @@ def div_remove(self, jnk_unused, selection): self.divisions.pop(row) selection.select_path((row, )) + def div_copy(self, jnk_unused, selection): + '''handles a click on COPY button''' + treeiter1 = selection.get_selected()[1] + if treeiter1: + row = self.divmodel.get_path(treeiter1) + row = row[0] + div = self.divisions[row] + new_name = div[0] + ' (Copy)' + self.divisions.append([new_name, div[1]]) + #Add in the divisional name + divmodelrow = [new_name] + #Next the two age columns + if 'Age' in div[1]: + divmodelrow.extend([str(div[1]['Age'][0]), str(div[1]['Age'][1])]) + else: + divmodelrow.extend(['', '']) + #And then all other columns + for field in self.fields: + if self.fieldsdic[field]['type'] == 'combobox': + if field in div[1]: + divmodelrow.append(div[1][field]) + else: + divmodelrow.append('') + #All done! Add this row in. + self.divmodel.append(divmodelrow) + selection.select_path((len(self.divisions), )) + def winnewdivOK(self, jnk_unused, treeiter, CheckButtons, ComboBoxes, minagebtn, maxagebtn, divnameentry): '''handles a click on OK button''' #First get the division name @@ -287,8 +324,8 @@ def winnewdivOK(self, jnk_unused, treeiter, CheckButtons, ComboBoxes, minagebtn, #And now update the divmodel self.divmodel.set_value(treeiter, 0, div[0]) if 'Age' in div[1]: - self.divmodel.set_value(treeiter, 1, div[1]['Age'][0]) - self.divmodel.set_value(treeiter, 2, div[1]['Age'][1]) + self.divmodel.set_value(treeiter, 1, str(div[1]['Age'][0])) + self.divmodel.set_value(treeiter, 2, str(div[1]['Age'][1])) else: self.divmodel.set_value(treeiter, 1, '') self.divmodel.set_value(treeiter, 2, '') diff --git a/fstimer/gui/definefamilyreset.py b/fstimer/gui/definefamilyreset.py deleted file mode 100644 index c1e9ed9..0000000 --- a/fstimer/gui/definefamilyreset.py +++ /dev/null @@ -1,69 +0,0 @@ -#fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham - -#This program is free software: you can redistribute it and/or modify -#it under the terms of the GNU General Public License as published by -#the Free Software Foundation, either version 3 of the License, or -#(at your option) any later version. - -#This program is distributed in the hope that it will be useful, -#but WITHOUT ANY WARRANTY; without even the implied warranty of -#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -#GNU General Public License for more details. - -#You should have received a copy of the GNU General Public License -#along with this program. If not, see . - -#The author/copyright holder can be contacted at bletham@gmail.com -'''Handling of the window dedicated to the definition of the field - to reset when registering several members of a family''' - -import pygtk -pygtk.require('2.0') -import gtk -import fstimer.gui - -class FamilyResetWin(gtk.Window): - '''Handling of the window dedicated to the definition of the field - to reset when registering several members of a family''' - - def __init__(self, fields, clear_for_fam, back_clicked_cb, next_clicked_cb, parent): - '''Creates family reset window''' - super(FamilyResetWin, self).__init__(gtk.WINDOW_TOPLEVEL) - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_transient_for(parent) - self.set_modal(True) - self.set_title('fsTimer - New project') - self.set_position(gtk.WIN_POS_CENTER) - self.set_border_width(20) - self.connect('delete_event', lambda b, jnk: self.hide()) - # Now create the vbox. - vbox = gtk.VBox(False, 2) - self.add(vbox) - # Now add the text. - label1_0 = gtk.Label("Choose the fields to clear when adding a new family member.\nPress 'Forward' to continue with the default settings, or make edits below.") - vbox.pack_start(label1_0, False, False, 0) - btnlist = [] - for field in fields: - btnlist.append(gtk.CheckButton(field)) - if field in clear_for_fam: - btnlist[-1].set_active(True) - else: - btnlist[-1].set_active(False) - vbox.pack_start(btnlist[-1]) - # And an hbox with 2 buttons - hbox = gtk.HBox(False, 0) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) - btnCANCEL.connect('clicked', lambda btn: self.hide()) - alignCANCEL = gtk.Alignment(0, 0, 0, 0) - alignCANCEL.add(btnCANCEL) - btnBACK = gtk.Button(stock=gtk.STOCK_GO_BACK) - btnBACK.connect('clicked', back_clicked_cb) - btnNEXT = gtk.Button(stock=gtk.STOCK_GO_FORWARD) - btnNEXT.connect('clicked', next_clicked_cb, btnlist) - ##And populate - hbox.pack_start(alignCANCEL, True, True, 0) - hbox.pack_start(btnBACK, False, False, 2) - hbox.pack_start(btnNEXT, False, False, 0) - vbox.pack_start(hbox, False, False, 8) - self.show_all() diff --git a/fstimer/gui/definefields.py b/fstimer/gui/definefields.py index cfb944a..84c6e77 100644 --- a/fstimer/gui/definefields.py +++ b/fstimer/gui/definefields.py @@ -18,49 +18,50 @@ '''Handling of the window dedicated to the definition of the fields used in a project''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton -class DefineFieldsWin(gtk.Window): +class DefineFieldsWin(Gtk.Window): '''Handles the definition of the fields in a project''' def __init__(self, fields, fieldsdic, projecttype, back_clicked_cb, next_clicked_cb, parent): '''Creates fields definition window''' - super(DefineFieldsWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(DefineFieldsWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.fields = fields self.fieldsdic = fieldsdic - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.winnewcombo = None self.winnewentry = None self.set_transient_for(parent) self.set_modal(True) - self.set_title('fsTimer - New project') - self.set_position(gtk.WIN_POS_CENTER) + self.set_title('fsTimer - Fields') + self.set_position(Gtk.WindowPosition.CENTER) self.set_border_width(20) self.set_size_request(600, 400) self.connect('delete_event', lambda b, jnk_unused: self.hide()) #Specify the fields that are required and so will be locked. if projecttype == 'handicap': - self.reqfields = ['Last name', 'First name', 'ID', 'Age', 'Gender', 'Handicap'] + self.reqfields = ['ID', 'Age', 'Handicap'] else: - self.reqfields = ['Last name', 'First name', 'ID', 'Age', 'Gender'] + self.reqfields = ['ID', 'Age'] ##Now create the vbox. - vbox1 = gtk.VBox(False, 10) + vbox1 = Gtk.VBox(False, 10) self.add(vbox1) ##Now add the text. - label2_0 = gtk.Label("Specify the information to be collected during registration.\nPress 'Forward' to continue with the default settings, or make edits below.") + label2_0 = Gtk.Label("Specify the information to be collected during registration.\nPress 'Forward' to continue with the default settings, or make edits below.") #Now we put in a liststore with the settings. We start with the default settings. #Make the liststore, with 3 columns (title, type, settings) - self.regfieldsmodel = gtk.ListStore(str, str, str) + self.regfieldsmodel = Gtk.ListStore(str, str, str) #We will put the liststore in a treeview - self.regfieldview = gtk.TreeView() - column = gtk.TreeViewColumn('Field', gtk.CellRendererText(), text=0) + self.regfieldview = Gtk.TreeView() + column = Gtk.TreeViewColumn('Field', Gtk.CellRendererText(), text=0) self.regfieldview.append_column(column) - column = gtk.TreeViewColumn('Type', gtk.CellRendererText(), text=1) + column = Gtk.TreeViewColumn('Type', Gtk.CellRendererText(), text=1) self.regfieldview.append_column(column) - column = gtk.TreeViewColumn('Settings', gtk.CellRendererText(), text=2) + column = Gtk.TreeViewColumn('Settings', Gtk.CellRendererText(), text=2) self.regfieldview.append_column(column) #Now we populate the model with the default fields for field in fields: @@ -75,46 +76,46 @@ def __init__(self, fields, fieldsdic, projecttype, back_clicked_cb, next_clicked self.regfieldview.set_model(self.regfieldsmodel) selection = self.regfieldview.get_selection() #And put it in a scrolled window, in an alignment - regfieldsw = gtk.ScrolledWindow() - regfieldsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - regfieldsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + regfieldsw = Gtk.ScrolledWindow() + regfieldsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + regfieldsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) regfieldsw.add(self.regfieldview) - regfieldalgn = gtk.Alignment(0, 0, 1, 1) + regfieldalgn = Gtk.Alignment.new(0, 0, 1, 1) regfieldalgn.add(regfieldsw) #Now we put the buttons on the side. - vbox2 = gtk.VBox(False, 10) - btnUP = gtk.Button(stock=gtk.STOCK_GO_UP) + vbox2 = Gtk.VBox(False, 10) + btnUP = GtkStockButton('up',"Up") btnUP.connect('clicked', self.regfield_up, selection) vbox2.pack_start(btnUP, False, False, 0) - btnDOWN = gtk.Button(stock=gtk.STOCK_GO_DOWN) + btnDOWN = GtkStockButton('down',"Down") btnDOWN.connect('clicked', self.regfield_down, selection) vbox2.pack_start(btnDOWN, False, False, 0) - btnEDIT = gtk.Button(stock=gtk.STOCK_EDIT) + btnEDIT = GtkStockButton('edit',"Edit") btnEDIT.connect('clicked', self.regfield_edit, selection) vbox2.pack_start(btnEDIT, False, False, 0) - btnREMOVE = gtk.Button(stock=gtk.STOCK_REMOVE) + btnREMOVE = GtkStockButton('remove',"Remove") btnREMOVE.connect('clicked', self.regfield_remove, selection) vbox2.pack_start(btnREMOVE, False, False, 0) - btnNEWentry = gtk.Button('New entrybox') + btnNEWentry = Gtk.Button('New entrybox') btnNEWentry.connect('clicked', self.regfield_new_entrybox, '', 0, None) vbox2.pack_start(btnNEWentry, False, False, 0) - btnNEWcombo = gtk.Button('New combobox') + btnNEWcombo = Gtk.Button('New combobox') btnNEWcombo.connect('clicked', self.regfield_new_combobox, '', '', None) vbox2.pack_start(btnNEWcombo, False, False, 0) selection.connect('changed', self.regfield_lock_required_fields, btnREMOVE, btnEDIT) #And an hbox for the fields and the buttons - hbox4 = gtk.HBox(False, 0) + hbox4 = Gtk.HBox(False, 0) hbox4.pack_start(regfieldalgn, True, True, 10) hbox4.pack_start(vbox2, False, False, 0) ##And an hbox with 3 buttons - hbox3 = gtk.HBox(False, 0) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + hbox3 = Gtk.HBox(False, 0) + btnCANCEL = GtkStockButton('close',"Close") btnCANCEL.connect('clicked', lambda btn: self.hide()) - alignCANCEL = gtk.Alignment(0, 0, 0, 0) + alignCANCEL = Gtk.Alignment.new(0, 0, 0, 0) alignCANCEL.add(btnCANCEL) - btnBACK = gtk.Button(stock=gtk.STOCK_GO_BACK) + btnBACK = GtkStockButton('back',"Back") btnBACK.connect('clicked', back_clicked_cb) - btnNEXT = gtk.Button(stock=gtk.STOCK_GO_FORWARD) + btnNEXT = GtkStockButton('forward',"Next") btnNEXT.connect('clicked', next_clicked_cb) ##And populate hbox3.pack_start(alignCANCEL, True, True, 0) @@ -167,39 +168,41 @@ def regfield_edit(self, jnk_unused, selection): def regfield_new_entrybox(self, jnk_unused, name, maxchar, treeiter): '''Handled click on the New entrybox button''' - self.winnewentry = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.winnewentry.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.winnewentry = Gtk.Window(Gtk.WindowType.TOPLEVEL) + self.winnewentry.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.winnewentry.set_transient_for(self) self.winnewentry.set_modal(True) - self.winnewentry.set_title('fsTimer - New project') - self.winnewentry.set_position(gtk.WIN_POS_CENTER) + self.winnewentry.set_title('fsTimer - New entrybox') + self.winnewentry.set_position(Gtk.WindowPosition.CENTER) self.winnewentry.set_border_width(20) self.winnewentry.connect('delete_event', lambda b, jnk_unused: self.winnewentry.hide()) - label0 = gtk.Label('An entrybox allows for any text to be entered.') - label1 = gtk.Label('Field name:') - nameentry = gtk.Entry(max=50) + label0 = Gtk.Label(label='An entrybox allows for any text to be entered.') + label1 = Gtk.Label(label='Field name:') + nameentry = Gtk.Entry() + nameentry.set_max_length(50) nameentry.set_text(name) - hbox1 = gtk.HBox(False, 10) + hbox1 = Gtk.HBox(False, 10) hbox1.pack_start(label1, False, False, 0) hbox1.pack_start(nameentry, False, False, 0) - label2 = gtk.Label('Max characters:') - maxcharadj = gtk.Adjustment(value=1, lower=1, upper=120, step_incr=1) - maxcharbtn = gtk.SpinButton(maxcharadj, digits=0, climb_rate=0) + label2 = Gtk.Label(label='Max characters:') + maxcharadj = Gtk.Adjustment(value=1, lower=1, upper=120, step_incr=1) + maxcharbtn = Gtk.SpinButton(digits=0, climb_rate=0) + maxcharbtn.set_adjustment(maxcharadj) maxcharbtn.set_value(int(maxchar)) - hbox2 = gtk.HBox(False, 10) + hbox2 = Gtk.HBox(False, 10) hbox2.pack_start(label2, False, False, 0) hbox2.pack_start(maxcharbtn, False, False, 0) - label3 = gtk.Label('') - btnOK = gtk.Button(stock=gtk.STOCK_OK) + label3 = Gtk.Label(label='') + btnOK = GtkStockButton('ok',"OK") btnOK.connect('clicked', self.winnewentryOK, treeiter, nameentry, maxcharbtn, label3) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + btnCANCEL = GtkStockButton('close',"Cancel") btnCANCEL.connect('clicked', lambda b: self.winnewentry.hide()) - cancel_algn = gtk.Alignment(0, 0, 0, 0) + cancel_algn = Gtk.Alignment.new(0, 0, 0, 0) cancel_algn.add(btnCANCEL) - hbox3 = gtk.HBox(False, 10) + hbox3 = Gtk.HBox(False, 10) hbox3.pack_start(cancel_algn, True, True, 0) hbox3.pack_start(btnOK, False, False, 0) - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) vbox.pack_start(label0, False, False, 0) vbox.pack_start(hbox1, False, False, 0) vbox.pack_start(hbox2, False, False, 0) @@ -225,38 +228,40 @@ def regfield_lock_required_fields(self, selection, btnREMOVE, btnEDIT): def regfield_new_combobox(self, jnk_unused, name, options, treeiter): '''Handled click on the New combobox button''' - self.winnewcombo = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.winnewcombo.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.winnewcombo = Gtk.Window(Gtk.WindowType.TOPLEVEL) + self.winnewcombo.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.winnewcombo.set_transient_for(self) self.winnewcombo.set_modal(True) - self.winnewcombo.set_title('fsTimer - New project') - self.winnewcombo.set_position(gtk.WIN_POS_CENTER) + self.winnewcombo.set_title('fsTimer - New combobox') + self.winnewcombo.set_position(Gtk.WindowPosition.CENTER) self.winnewcombo.set_border_width(20) self.winnewcombo.connect('delete_event', lambda b, jnk_unused: self.winnewcombo.hide()) - label0 = gtk.Label('A combobox allows the value to be one of a few options.') - label1 = gtk.Label('Field name:') - nameentry = gtk.Entry(max=50) + label0 = Gtk.Label(label='A combobox allows the value to be one of a few options.') + label1 = Gtk.Label(label='Field name:') + nameentry = Gtk.Entry() + nameentry.set_max_length(50) nameentry.set_text(name) - hbox1 = gtk.HBox(False, 10) + hbox1 = Gtk.HBox(False, 10) hbox1.pack_start(label1, False, False, 0) hbox1.pack_start(nameentry, False, False, 0) - label2 = gtk.Label('Options, separated by commas:') - optionentry = gtk.Entry(max=50) + label2 = Gtk.Label('Options, separated by commas:') + optionentry = Gtk.Entry() + optionentry.set_max_length(50) optionentry.set_text(options) - hbox2 = gtk.HBox(False, 10) + hbox2 = Gtk.HBox(False, 10) hbox2.pack_start(label2, False, False, 0) hbox2.pack_start(optionentry, False, False, 0) - label3 = gtk.Label('') - btnOK = gtk.Button(stock=gtk.STOCK_OK) + label3 = Gtk.Label(label='') + btnOK = GtkStockButton('ok',"OK") btnOK.connect('clicked', self.winnewcomboOK, treeiter, nameentry, optionentry, label3) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + btnCANCEL = GtkStockButton('close',"Cancel") btnCANCEL.connect('clicked', lambda b: self.winnewcombo.hide()) - cancel_algn = gtk.Alignment(0, 0, 0, 0) + cancel_algn = Gtk.Alignment.new(0, 0, 0, 0) cancel_algn.add(btnCANCEL) - hbox3 = gtk.HBox(False, 10) + hbox3 = Gtk.HBox(False, 10) hbox3.pack_start(cancel_algn, True, True, 0) hbox3.pack_start(btnOK, False, False, 0) - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) vbox.pack_start(label0,False,False,0) vbox.pack_start(hbox1, False, False, 0) vbox.pack_start(hbox2, False, False, 0) @@ -294,9 +299,8 @@ def winnewcomboOK(self, jnk_unused, treeiter, nameentry1, optionentry1, label3): self.fieldsdic[nameentry]['options'] = optionlist self.regfieldsmodel.set_value(treeiter, 2, 'options: '+optstr) self.winnewcombo.hide() - elif nameentry in self.fields: - #Not the original name, but an existing name. - label3.set_markup('This field already exists! Try again.') + elif not self.name_validate(nameentry, label3): + pass else: #A completely new name. self.fields[self.fields.index(oldname)] = nameentry #replace the name in self.fields @@ -307,8 +311,8 @@ def winnewcomboOK(self, jnk_unused, treeiter, nameentry1, optionentry1, label3): self.winnewcombo.hide() else: #no treeiter- this was a new entry. Two possibilities... - if nameentry in self.fields: - label3.set_markup('This field already exists! Try again.') + if not self.name_validate(nameentry, label3): + pass else: self.fields.append(nameentry) self.fieldsdic[nameentry] = {'type':'combobox', 'options':optionlist} #new entry @@ -327,9 +331,8 @@ def winnewentryOK(self, jnk_unused, treeiter, nameentry1, maxchar1, label3): self.fieldsdic[nameentry]['max'] = int(maxchar) self.regfieldsmodel.set_value(treeiter, 2, 'max characters: '+maxchar) self.winnewentry.hide() - elif nameentry in self.fields: - #Not the original name, but an existing name. - label3.set_markup('This field already exists! Try again.') + elif not self.name_validate(nameentry, label3): + pass else: #A completely new name. self.fields[self.fields.index(oldname)] = nameentry #replace the name in self.fields @@ -340,8 +343,8 @@ def winnewentryOK(self, jnk_unused, treeiter, nameentry1, maxchar1, label3): self.winnewentry.hide() else: #no treeiter- this was a new entry. Two possibilities... - if nameentry in self.fields: - label3.set_markup('This field already exists! Try again.') + if not self.name_validate(nameentry, label3): + pass else: self.fields.append(nameentry) self.fieldsdic[nameentry] = {'type':'entrybox', 'max':int(maxchar)} #new entry @@ -349,3 +352,14 @@ def winnewentryOK(self, jnk_unused, treeiter, nameentry1, maxchar1, label3): self.winnewentry.hide() return + def name_validate(self, nameentry, label3): + if nameentry in self.fields: + label3.set_markup('This field already exists! Try again.') + return False + elif '{' in nameentry or '}' in nameentry: + label3.set_markup('{ and } cannot be used in field name! Try again.') + return False + elif nameentry in ['Time', 'Pace', 'Place', 'Lap Times']: + label3.set_markup('Name "{}" is reserved and cannot be used. Try again.'.format(nameentry)) + else: + return True \ No newline at end of file diff --git a/fstimer/gui/definerankings.py b/fstimer/gui/definerankings.py new file mode 100644 index 0000000..728e6ca --- /dev/null +++ b/fstimer/gui/definerankings.py @@ -0,0 +1,142 @@ +#fsTimer - free, open source software for race timing. +#Copyright 2012-14 Ben Letham + +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation, either version 3 of the License, or +#(at your option) any later version. + +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. + +#You should have received a copy of the GNU General Public License +#along with this program. If not, see . + +#The author/copyright holder can be contacted at bletham@gmail.com +'''Handling of the window where rankings are defined''' + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton + +class RankingsWin(Gtk.Window): + '''Handling of the window where rankings are defined''' + + def __init__(self, rankings, divisions, printfields, back_clicked_cb, next_clicked_cb, parent, edit): + '''Creates divisions window''' + super(RankingsWin, self).__init__(Gtk.WindowType.TOPLEVEL) + self.rankings = rankings + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + self.set_transient_for(parent) + self.set_modal(True) + self.set_title('fsTimer - Rankings') + self.set_position(Gtk.WindowPosition.CENTER) + self.set_border_width(20) + self.set_size_request(600, 400) + self.connect('delete_event', lambda b, jnk_unused: self.hide()) + # top label + top_label = Gtk.Label('Select which field the results will be ranked by.\nYou can use any one of the results fields defined on the previous window.') + self.fieldslist = list(printfields.keys()) + try: + self.indx = self.fieldslist.index(self.rankings['Overall']) + except ValueError: + self.indx = 0 + #overall_align = Gtk.Alignment.new(0, 0, 0, 0) + #overall_align.add(Gtk.Label('Rank overall results by: ')) + ov_hbox = Gtk.HBox(False, 5) + ov_hbox.pack_start(Gtk.Label('Rank overall results by:'), False, False, 0) + # Prep the combobox + combobox = Gtk.ComboBoxText() + for field in self.fieldslist: + combobox.append_text(field) + combobox.set_active(self.indx) + combobox.connect('changed', self.overall_edit) + ov_hbox.pack_start(combobox, False, False, 0) + apply_btn = Gtk.Button('Apply to divisions') + apply_btn.connect('clicked', self.apply_to_divs) + ov_hbox.pack_start(apply_btn, False, False, 5) + # Create a treeview and add columns + self.rankingview = Gtk.TreeView() + # Make the model, a liststore with columns str, str + self.rankingmodel = Gtk.ListStore(str, str) + for div in divisions: + indx_div = self.fieldslist.index(self.rankings[div[0]]) + self.rankingmodel.append([div[0], self.fieldslist[indx_div]]) + self.rankingview.set_model(self.rankingmodel) + selection = self.rankingview.get_selection() + # Add following columns to the treeview : + # Division | Ranking field + ranking_renderer = Gtk.CellRendererText() + ranking_renderer.set_property("editable", False) + column = Gtk.TreeViewColumn('Division', ranking_renderer, text=0) + self.rankingview.append_column(column) + #Prepare the combobox liststore + liststore_fields = Gtk.ListStore(str) + for field in self.fieldslist: + liststore_fields.append([field]) + combo_renderer = Gtk.CellRendererCombo() + combo_renderer.set_property("editable", True) + combo_renderer.set_property("model", liststore_fields) + combo_renderer.set_property("text-column", 0) + combo_renderer.set_property("has-entry", False) + combo_renderer.connect("edited", self.ranking_edit) + column = Gtk.TreeViewColumn('Rank by:', combo_renderer, text=1) + self.rankingview.append_column(column) + # Create scrolled window, in an alignment + rankingsw = Gtk.ScrolledWindow() + rankingsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + rankingsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + rankingsw.add(self.rankingview) + rankingalgn = Gtk.Alignment.new(0, 0, 1, 1) + rankingalgn.add(rankingsw) + # And an hbox for the fields and the buttons + hbox4 = Gtk.HBox(False, 0) + hbox4.pack_start(rankingalgn, True, True, 10) + # Add an hbox with 3 buttons + hbox3 = Gtk.HBox(False, 0) + btnCANCEL = GtkStockButton('close',"Close") + btnCANCEL.connect('clicked', lambda btn: self.hide()) + alignCANCEL = Gtk.Alignment.new(0, 0, 0, 0) + alignCANCEL.add(btnCANCEL) + btnBACK = GtkStockButton('back',"Back") + btnBACK.connect('clicked', back_clicked_cb) + btnNEXT = GtkStockButton('forward',"Next") + btnNEXT.connect('clicked', next_clicked_cb, edit) + # Populate + hbox3.pack_start(alignCANCEL, True, True, 0) + hbox3.pack_start(btnBACK, False, False, 2) + hbox3.pack_start(btnNEXT, False, False, 0) + vbox = Gtk.VBox(False, 0) + vbox.pack_start(top_label, False, False, 0) + vbox.pack_start(ov_hbox, False, False, 15) + vbox.pack_start(hbox4, True, True, 0) + vbox.pack_start(hbox3, False, False, 10) + self.add(vbox) + self.show_all() + + def ranking_edit(self, widget, path, text): + '''handles a change of a ranking field''' + treeiter = self.rankingmodel.get_iter(path) + div = self.rankingmodel.get_value(treeiter, 0) + self.rankingmodel[path][1] = text + self.rankings[div] = text + return + + def overall_edit(self, widget): + '''handles a change of the overall ranking field''' + idx = widget.get_active() + self.rankings['Overall'] = self.fieldslist[idx] + return + + def apply_to_divs(self, widget): + '''sets divs to the overall setting on button click''' + for div in self.rankings: + if div != 'Overall': + self.rankings[div] = str(self.rankings['Overall']) + for i in range(len(self.rankings)-1): + itr = self.rankingmodel.get_iter(i) + self.rankingmodel.set_value(itr, 1, self.rankings['Overall']) \ No newline at end of file diff --git a/fstimer/gui/editblocktimes.py b/fstimer/gui/editblocktimes.py index 4d42336..52eb4ea 100644 --- a/fstimer/gui/editblocktimes.py +++ b/fstimer/gui/editblocktimes.py @@ -17,47 +17,49 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window used for editing a block of times''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton -class EditBlockTimesWin(gtk.Window): +class EditBlockTimesWin(Gtk.Window): '''Handling of the window used for editing a block of times''' def __init__(self, parent, okclicked_cb): '''Builds and display the window for editing block of times''' - super(EditBlockTimesWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(EditBlockTimesWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.okclicked_cb = okclicked_cb - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.set_transient_for(parent) self.set_modal(True) self.set_title('fsTimer - Edit times') - self.set_position(gtk.WIN_POS_CENTER) + self.set_position(Gtk.WindowPosition.CENTER) self.set_border_width(20) self.connect('delete_event', lambda b, jnk: self.hide()) - label0 = gtk.Label('') + label0 = Gtk.Label(label='') label0.set_markup('WARNING: Changes to times cannot be automatically undone\nIf you change the times and forget the old values, they will be gone forever.') - label1 = gtk.Label('Time (h:mm:ss) to be added or subtracted from all selected times:') - self.radiobutton = gtk.RadioButton(None, "ADD") + label1 = Gtk.Label(label='Time (h:mm:ss) to be added or subtracted from all selected times:') + self.radiobutton = Gtk.RadioButton(group=None, label="ADD") self.radiobutton.set_active(True) - radiobutton2 = gtk.RadioButton(self.radiobutton, "SUBTRACT") - self.entrytime = gtk.Entry(max=7) + radiobutton2 = Gtk.RadioButton(group=self.radiobutton, label="SUBTRACT") + self.entrytime = Gtk.Entry() + self.entrytime.set_max_length(7) self.entrytime.set_text('0:00:00') - hbox1 = gtk.HBox(False, 10) + hbox1 = Gtk.HBox(False, 10) hbox1.pack_start(self.radiobutton, False, False, 0) hbox1.pack_start(radiobutton2, False, False, 0) hbox1.pack_start(self.entrytime, False, False, 0) - btnOK = gtk.Button(stock=gtk.STOCK_OK) + btnOK = GtkStockButton('ok',"OK") btnOK.connect('clicked', self.okclicked) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + btnCANCEL = GtkStockButton('close',"Cancel") btnCANCEL.connect('clicked', lambda b: self.hide()) - cancel_algn = gtk.Alignment(0, 0, 0, 0) + cancel_algn = Gtk.Alignment.new(0, 0, 0, 0) cancel_algn.add(btnCANCEL) - hbox2 = gtk.HBox(False, 10) + hbox2 = Gtk.HBox(False, 10) hbox2.pack_start(cancel_algn, True, True, 0) hbox2.pack_start(btnOK, False, False, 0) - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) vbox.pack_start(label0, False, False, 10) vbox.pack_start(label1, False, False, 10) vbox.pack_start(hbox1, False, False, 0) diff --git a/fstimer/gui/editt0.py b/fstimer/gui/editt0.py index 6eb9dc0..473bad0 100644 --- a/fstimer/gui/editt0.py +++ b/fstimer/gui/editt0.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,38 +17,42 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window used for editing t0, the race start time''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +import os +from fstimer.gui.util_classes import GtkStockButton -class EditT0Win(gtk.Window): +class EditT0Win(Gtk.Window): '''Handling of the window used for editing t0, the race start time''' def __init__(self, path, parent, t0, okclicked_cb): '''Builds and display the edit t0 window''' - super(EditT0Win, self).__init__(gtk.WINDOW_TOPLEVEL) + super(EditT0Win, self).__init__(Gtk.WindowType.TOPLEVEL) self.okclicked_cb = okclicked_cb - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) self.set_transient_for(parent) self.set_modal(True) self.connect('delete_event', lambda b, jnk: self.hide()) - label = gtk.Label("""This is the starting time in seconds.\ + label = Gtk.Label("""This is the starting time in seconds.\ \nAdd or subtract seconds from this number to adjust the start time by that many seconds.\ \nNote that this will NOT affect times that have already been marked, only future times.""") - self.t0box = gtk.Entry() + self.t0box = Gtk.Entry() self.t0box.set_text(str(t0)) - hbox = gtk.HBox(False, 8) - btnOK = gtk.Button(stock=gtk.STOCK_OK) + hbox = Gtk.HBox(False, 8) + btnOK = GtkStockButton('ok',"OK") btnOK.connect('clicked', self.okclicked) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + btnCANCEL = GtkStockButton('close',"Cancel") btnCANCEL.connect('clicked', lambda jnk: self.hide()) hbox.pack_start(btnOK, False, False, 0) hbox.pack_start(btnCANCEL, False, False, 0) - vbox = gtk.VBox(False, 8) - vbox.pack_start(label, False, False, 0) + hbox_lbl = Gtk.HBox(False, 0) + hbox_lbl.pack_start(label, False, False, 10) + vbox = Gtk.VBox(False, 8) + vbox.pack_start(hbox_lbl, False, False, 20) vbox.pack_start(self.t0box, False, False, 0) vbox.pack_start(hbox, False, False, 0) self.add(vbox) diff --git a/fstimer/gui/edittime.py b/fstimer/gui/edittime.py index 2d2fc9d..7adb19d 100644 --- a/fstimer/gui/edittime.py +++ b/fstimer/gui/edittime.py @@ -17,50 +17,53 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window dedicated to editing a single time entry''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton -class EditTimeWin(gtk.Window): +class EditTimeWin(Gtk.Window): '''Handling of the window dedicated to editing a single time entry''' def __init__(self, timewin, old_id, old_time, clickedok_cb): '''Builds and display the window for editing a single time''' - super(EditTimeWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(EditTimeWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.timewin = timewin self.clickedok_cb = clickedok_cb - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.set_transient_for(timewin) self.set_modal(True) self.set_title('fsTimer - Edit time') - self.set_position(gtk.WIN_POS_CENTER) + self.set_position(Gtk.WindowPosition.CENTER) self.set_border_width(20) self.connect('delete_event', lambda b, jnk: self.hide()) - label0 = gtk.Label('') + label0 = Gtk.Label(label='') label0.set_markup('WARNING: Changes to these values cannot be automatically undone\nIf you change the time and forget the old one, it will be gone forever.') - label1 = gtk.Label('ID:') - self.entryid = gtk.Entry(max=6) + label1 = Gtk.Label(label='ID:') + self.entryid = Gtk.Entry() + self.entryid.set_max_length(6) self.entryid.set_text(old_id) - hbox1 = gtk.HBox(False, 10) + hbox1 = Gtk.HBox(False, 10) hbox1.pack_start(label1, False, False, 0) hbox1.pack_start(self.entryid, False, False, 0) - label2 = gtk.Label('Time:') - self.entrytime = gtk.Entry(max=25) + label2 = Gtk.Label(label='Time:') + self.entrytime = Gtk.Entry() + self.entrytime.set_max_length(25) self.entrytime.set_text(old_time) - hbox2 = gtk.HBox(False, 10) + hbox2 = Gtk.HBox(False, 10) hbox2.pack_start(label2, False, False, 0) hbox2.pack_start(self.entrytime, False, False, 0) - btnOK = gtk.Button(stock=gtk.STOCK_OK) + btnOK = GtkStockButton('ok',"OK") btnOK.connect('clicked', self.winedittimeOK) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + btnCANCEL = GtkStockButton('close',"Cancel") btnCANCEL.connect('clicked', lambda b: self.hide()) - cancel_algn = gtk.Alignment(0, 0, 0, 0) + cancel_algn = Gtk.Alignment.new(0, 0, 0, 0) cancel_algn.add(btnCANCEL) - hbox3 = gtk.HBox(False, 10) + hbox3 = Gtk.HBox(False, 10) hbox3.pack_start(cancel_algn, True, True, 0) hbox3.pack_start(btnOK, False, False, 0) - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) vbox.pack_start(label0, False, False, 10) vbox.pack_start(hbox1, False, False, 0) vbox.pack_start(hbox2, False, False, 0) diff --git a/fstimer/gui/importprereg.py b/fstimer/gui/importprereg.py index 3e6902d..76ec848 100644 --- a/fstimer/gui/importprereg.py +++ b/fstimer/gui/importprereg.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,67 +17,72 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window dedicated to importation of pre-registration''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui import os, csv, json import datetime +from fstimer.gui.util_classes import GtkStockButton class ComboValueError(Exception): '''Exception launched when decoding reveals an invalid value for a combo field''' pass -class ImportPreRegWin(gtk.Window): +class ImportPreRegWin(Gtk.Window): '''Handling of the window dedicated to importation of pre-registration''' - def __init__(self, cwd, path, fields, fieldsdic): + def __init__(self, path, fields, fieldsdic): '''Builds and display the importation window''' - super(ImportPreRegWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(ImportPreRegWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.path = path - self.cwd = cwd self.fields = fields self.fieldsdic = fieldsdic - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_icon_from_file('fstimer/data/icon.png') - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/icon.png')) + self.set_icon_from_file(fname) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) self.connect('delete_event', lambda b, jnk: self.hide()) self.set_border_width(10) self.set_size_request(600, 400) # Start with some intro text. - label1 = gtk.Label('Select a pre-registration csv file to import.') + label1 = Gtk.Label(label='Select a pre-registration csv file to import.') #Continue to the load file. - btnFILE = gtk.Button(stock=gtk.STOCK_OPEN) + btnFILE = GtkStockButton('open',"Open") ## Textbuffer - textbuffer = gtk.TextBuffer() + textbuffer = Gtk.TextBuffer() try: textbuffer.create_tag("blue", foreground="blue") textbuffer.create_tag("red", foreground="red") except TypeError: pass - textview = gtk.TextView(textbuffer) + textview = Gtk.TextView() + textview.set_buffer(textbuffer) textview.set_editable(False) textview.set_cursor_visible(False) - textsw = gtk.ScrolledWindow() - textsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - textsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + textsw = Gtk.ScrolledWindow() + textsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + textsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) textsw.add(textview) - textalgn = gtk.Alignment(0, 0, 1, 1) + textalgn = Gtk.Alignment.new(0, 0, 1, 1) textalgn.add(textsw) - hbox2 = gtk.HBox(False, 5) + hbox2 = Gtk.HBox(False, 5) btnFILE.connect('clicked', self.select_preregistration, textbuffer) - btn_algn = gtk.Alignment(1, 0, 1, 0) + btn_algn = Gtk.Alignment.new(1, 0, 1, 0) hbox2.pack_start(btnFILE, False, False, 0) hbox2.pack_start(btn_algn, True, True, 0) ## buttons - btnOK = gtk.Button(stock=gtk.STOCK_OK) + btnOK = GtkStockButton('ok',"OK") btnOK.connect('clicked', lambda b: self.hide()) - cancel_algn = gtk.Alignment(0, 0, 1, 0) - hbox3 = gtk.HBox(False, 10) + cancel_algn = Gtk.Alignment.new(0, 0, 1, 0) + hbox3 = Gtk.HBox(False, 10) hbox3.pack_start(cancel_algn, True, True, 0) hbox3.pack_start(btnOK, False, False, 0) - vbox = gtk.VBox(False, 0) + vbox = Gtk.VBox(False, 0) vbox.pack_start(label1, False, False, 5) vbox.pack_start(hbox2, False, True, 5) vbox.pack_start(textalgn, True, True, 5) @@ -88,32 +93,32 @@ def __init__(self, cwd, path, fields, fieldsdic): def propose_advanced_import(self, csv_fields, textbuffer1): '''Propose advanced import mechanism where project fields can be build from the csv ones using python expressions''' - self.advancedwin = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.advancedwin.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.advancedwin = Gtk.Window(Gtk.WindowType.TOPLEVEL) + self.advancedwin.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.advancedwin.set_transient_for(self) self.advancedwin.set_modal(True) self.advancedwin.set_title('fsTimer - CSV import') - self.advancedwin.set_position(gtk.WIN_POS_CENTER) + self.advancedwin.set_position(Gtk.WindowPosition.CENTER) self.advancedwin.set_border_width(20) self.advancedwin.set_size_request(600, 400) self.advancedwin.connect('delete_event', lambda b, jnk_unused: self.advancedwin.hide()) # top label - toplabel = gtk.Label("For each field, specify the corresponding CSV column.\n") + toplabel = Gtk.Label("For each field, specify the corresponding CSV column.\n") # Treeview with 3 columns : field, combobox and free text - self.fieldview = gtk.TreeView() - self.fieldview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) + self.fieldview = Gtk.TreeView() + self.fieldview.set_grid_lines(Gtk.TreeViewGridLines.BOTH) # Associated model with 5 columns : the 4th one is a boolean indicating # whether the 3rd one (Advanced mapping) should be sensitive - self.fieldsmodel = gtk.ListStore(str, str, str, bool) + self.fieldsmodel = Gtk.ListStore(str, str, str, bool) for field in self.fields: self.fieldsmodel.append([field, field if field in csv_fields else '-- select --', '', False]) self.fieldview.set_model(self.fieldsmodel) # build first column (project field) - column = gtk.TreeViewColumn('Field', gtk.CellRendererText(), text=0) + column = Gtk.TreeViewColumn('Field', Gtk.CellRendererText(), text=0) self.fieldview.append_column(column) # vuild 2nd column (csv field to be used, as a combo box) - combo_renderer = gtk.CellRendererCombo() - liststore_csv_fields = gtk.ListStore(str) + combo_renderer = Gtk.CellRendererCombo() + liststore_csv_fields = Gtk.ListStore(str) liststore_csv_fields.append(['-- Leave empty --']) for field in csv_fields: liststore_csv_fields.append([field]) @@ -122,44 +127,45 @@ def propose_advanced_import(self, csv_fields, textbuffer1): combo_renderer.set_property("text-column", 0) combo_renderer.set_property("editable", True) combo_renderer.set_property("has-entry", False) - column = gtk.TreeViewColumn('CSV column', combo_renderer, text=1) + column = Gtk.TreeViewColumn('CSV column', combo_renderer, text=1) self.fieldview.append_column(column) # build the 3rd column (Advanced mapping) - advanced_renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn('Advanced mapping', advanced_renderer, text=2, sensitive=3, editable=3) + advanced_renderer = Gtk.CellRendererText() + column = Gtk.TreeViewColumn('Advanced mapping', advanced_renderer, text=2, sensitive=3, editable=3) self.fieldview.append_column(column) # handler for the combo changes combo_renderer.connect("edited", self.combo_changed) advanced_renderer.connect("edited", self.text_changed) # And put it in a scrolled window, in an alignment - fieldsw = gtk.ScrolledWindow() - fieldsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - fieldsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + fieldsw = Gtk.ScrolledWindow() + fieldsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + fieldsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) fieldsw.add(self.fieldview) - fieldalgn = gtk.Alignment(0, 0, 1, 1) + fieldalgn = Gtk.Alignment.new(0, 0, 1, 1) fieldalgn.add(fieldsw) # a text buffer for errors - textbuffer2 = gtk.TextBuffer() + textbuffer2 = Gtk.TextBuffer() try: textbuffer2.create_tag("red", foreground="red") textbuffer2.create_tag("blue", foreground="blue") except TypeError: pass - textview = gtk.TextView(textbuffer2) + textview = Gtk.TextView() + textview.set_buffer(textbuffer2) textview.set_editable(False) textview.set_cursor_visible(False) # hbox for the buttons - hbox = gtk.HBox(False, 0) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + hbox = Gtk.HBox(False, 0) + btnCANCEL = GtkStockButton('close',"Cancel") btnCANCEL.connect('clicked', self.advanced_import_cancel, textbuffer1) - btnOK = gtk.Button(stock=gtk.STOCK_OK) + btnOK = GtkStockButton('ok',"OK") btnOK.connect('clicked', self.advanced_import_ok, textbuffer1, textbuffer2) - alignOK = gtk.Alignment(1, 0, 0, 0) + alignOK = Gtk.Alignment.new(1, 0, 0, 0) alignOK.add(btnOK) hbox.pack_start(btnCANCEL, False, True, 0) hbox.pack_start(alignOK, True, True, 0) # populate - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) vbox.pack_start(toplabel, False, False, 0) vbox.pack_start(fieldalgn, True, True, 0) vbox.pack_start(textview, False, False, 0) @@ -173,27 +179,27 @@ def advanced_import_cancel(self, widget_unused, textbuffer): iter_end = textbuffer.get_end_iter() textbuffer.insert_with_tags_by_name(iter_end, 'Nothing done.', 'blue') - def combo_changed(self, widget_unused, path, text): + def combo_changed(self, widget_unused, gtkpath, text): '''Handles a change in the combo boxes' selections''' - self.fieldsmodel[path][1] = text + self.fieldsmodel[gtkpath][1] = text if text == '-- Advanced expression --': - if len(self.fieldsmodel[path][2]) == 0: - self.fieldsmodel[path][2] = '-- enter python expression --' - self.fieldsmodel[path][3] = True + if len(self.fieldsmodel[gtkpath][2]) == 0: + self.fieldsmodel[gtkpath][2] = '-- enter python expression --' + self.fieldsmodel[gtkpath][3] = True else: - self.fieldsmodel[path][3] = False + self.fieldsmodel[gtkpath][3] = False - def text_changed(self, widget_unused, path, text): + def text_changed(self, widget_unused, gtkpath, text): '''Handles a change in the advanced boxes''' - self.fieldsmodel[path][2] = text + self.fieldsmodel[gtkpath][2] = text def advanced_import_ok(self, jnk_unused, textbuffer1, textbuffer2): '''Handles click on OK button in the advanced interface''' textbuffer2.delete(textbuffer2.get_start_iter(), textbuffer2.get_end_iter()) self.fields_mapping = {} - for path in range(len(self.fieldsmodel)): - field = self.fieldsmodel[path][0] - csv_col = self.fieldsmodel[path][1] + for gtkpath in range(len(self.fieldsmodel)): + field = self.fieldsmodel[gtkpath][0] + csv_col = self.fieldsmodel[gtkpath][1] if csv_col == '-- select --': textbuffer2.insert_with_tags_by_name(textbuffer2.get_end_iter(), 'Nothing selected for field %s' % field, 'red') return @@ -201,9 +207,9 @@ def advanced_import_ok(self, jnk_unused, textbuffer1, textbuffer2): self.fields_mapping[field] = lambda reg, col=csv_col: '' elif csv_col == '-- Advanced expression --': try: - code = compile(self.fieldsmodel[path][2], '', 'eval') + code = compile(self.fieldsmodel[gtkpath][2], '', 'eval') self.fields_mapping[field] = lambda reg, code=code: eval(code) - except SyntaxError, e: + except SyntaxError as e: iter_end = textbuffer2.get_end_iter() textbuffer2.insert_with_tags_by_name(iter_end, 'Invalid syntax for expression of field %s: ' % field, 'red') textbuffer2.insert_with_tags_by_name(textbuffer2.get_end_iter(), str(e), 'blue') @@ -232,19 +238,19 @@ def build_fields_mapping(self, csv_fields, textbuffer): def select_preregistration(self, jnk_unused, textbuffer): '''Handle selection of a pre-reg file using a filechooser''' - chooser = gtk.FileChooserDialog(title='Select pre-registration csv', action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) - ffilter = gtk.FileFilter() + chooser = Gtk.FileChooserDialog(title='Select pre-registration csv', parent=self, action=Gtk.FileChooserAction.OPEN, buttons=('Cancel', Gtk.ResponseType.CANCEL, 'OK', Gtk.ResponseType.OK)) + ffilter = Gtk.FileFilter() ffilter.set_name('csv files') ffilter.add_pattern('*.csv') chooser.add_filter(ffilter) - chooser.set_current_folder(os.path.join(self.cwd, self.path, '')) + chooser.set_current_folder(self.path) response = chooser.run() - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: filename = chooser.get_filename() textbuffer.delete(textbuffer.get_start_iter(), textbuffer.get_end_iter()) textbuffer.set_text('Loading '+os.path.basename(filename)+'...\n') try: - fin = csv.DictReader(open(filename, 'r')) + fin = csv.DictReader(open(filename, 'r', encoding='utf-8')) self.csvreg = [] for row in fin: self.csvreg.append(row) @@ -279,6 +285,6 @@ def import_data(self, textbuffer): tmpdict[field] = value preregdata.append(tmpdict.copy()) row += 1 - with open(os.path.join(self.cwd, self.path, self.path+'_registration_prereg.json'), 'wb') as fout: + with open(os.path.join(self.path, os.path.basename(self.path)+'_registration_prereg.json'), 'w', encoding='utf-8') as fout: json.dump(preregdata, fout) - textbuffer.insert_with_tags_by_name(textbuffer.get_end_iter(), 'Success! Imported pre-registration saved to '+self.path+'_registration_prereg.json\nFinished!', 'blue') + textbuffer.insert_with_tags_by_name(textbuffer.get_end_iter(), 'Success! Imported pre-registration saved to '+os.path.basename(self.path)+'_registration_prereg.json\nFinished!', 'blue') diff --git a/fstimer/gui/intro.py b/fstimer/gui/intro.py index df32881..e701b86 100644 --- a/fstimer/gui/intro.py +++ b/fstimer/gui/intro.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,52 +17,57 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the introduction windows to select/create a project''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import os +from os.path import normpath, join, dirname, abspath import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton -class IntroWin(gtk.Window): +class IntroWin(Gtk.Window): '''Handles an introduction window to select/create a project''' def __init__(self, load_project_cb, create_project_cb): '''Builds and display the introduction window''' - super(IntroWin, self).__init__(gtk.WINDOW_TOPLEVEL) - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_icon_from_file('fstimer/data/icon.png') + super(IntroWin, self).__init__(Gtk.WindowType.TOPLEVEL) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + icon_fname = normpath(join(dirname(abspath(__file__)),'../data/icon.png')) + self.set_icon_from_file(icon_fname) self.set_title('fsTimer') - self.set_position(gtk.WIN_POS_CENTER) + self.set_position(Gtk.WindowPosition.CENTER) self.set_border_width(20) - self.connect('delete_event', gtk.main_quit) + self.connect('delete_event', Gtk.main_quit) # Create the vbox that will contain everything - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) self.add(vbox) # Main logo - logo = gtk.Image() - logo.set_from_file('fstimer/data/fstimer_logo.png') + logo = Gtk.Image() + logo.set_from_file(normpath(join(dirname(abspath(__file__)),'../data/fstimer_logo.png'))) # Welcome text - label0 = gtk.Label('') - label = gtk.Label('Select an existing project, or begin a new project.') + label0 = Gtk.Label(label='') + label = Gtk.Label('Select an existing project, or begin a new project.') # A combobox to select the project - combobox = gtk.combo_box_new_text() + combobox = Gtk.ComboBoxText() projectlist = [' -- Select an existing project --'] - projectlist.extend([i for i in os.listdir('.') if os.path.isdir(i) and os.path.exists(i+'/'+i+'.reg')]) #List the folders in pwd that contain a .reg registration file + rootdir = normpath(join(dirname(abspath(__file__)),'../../')) + projectlist.extend([i for i in os.listdir(rootdir) if os.path.isdir(join(rootdir,i)) and os.path.exists(join(rootdir,i+'/'+i+'.reg'))]) #List the folders in pwd that contain a .reg registration file projectlist.sort() for project in projectlist: combobox.append_text(project) combobox.set_active(0) #An hbox for the buttons. - hbox = gtk.HBox(False, 0) - btnNEW = gtk.Button(stock=gtk.STOCK_NEW) + hbox = Gtk.HBox(False, 0) + #And build the buttons + btnNEW = GtkStockButton('new','New') btnNEW.connect('clicked', create_project_cb) - btnOK = gtk.Button(stock=gtk.STOCK_OK) + btnOK = GtkStockButton('ok','OK') btnOK.connect('clicked', load_project_cb, combobox, projectlist) btnOK.set_sensitive(False) #Set combobox to lock btnOK, so we can't press OK until we have selected a project combobox.connect('changed', self.lock_btnOK, combobox, btnOK) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) - btnCANCEL.connect('clicked', gtk.main_quit) + btnCANCEL = GtkStockButton('close','Close') + btnCANCEL.connect('clicked', Gtk.main_quit) #Now fill the hbox. hbox.pack_start(btnCANCEL, True, True, 0) hbox.pack_start(btnNEW, True, True, 50) diff --git a/fstimer/gui/newproject.py b/fstimer/gui/newproject.py index 07fbd04..848ecc2 100644 --- a/fstimer/gui/newproject.py +++ b/fstimer/gui/newproject.py @@ -17,44 +17,58 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the new project windows''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import os, re +from os.path import normpath, join, dirname, abspath import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton -class NewProjectWin(gtk.Window): +class NewProjectWin(Gtk.Window): '''Handles the creation of a new project''' def __init__(self, set_projecttype_cb, parent): '''Creates new project window''' - super(NewProjectWin, self).__init__(gtk.WINDOW_TOPLEVEL) - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + super(NewProjectWin, self).__init__(Gtk.WindowType.TOPLEVEL) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.set_transient_for(parent) self.set_modal(True) self.set_title('fsTimer - New project') - self.set_position(gtk.WIN_POS_CENTER) + self.set_position(Gtk.WindowPosition.CENTER) self.set_border_width(20) self.connect('delete_event', lambda b, jnk_unused: self.hide()) # Now create the vbox. - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) self.add(vbox) # Now add the text. - label_0 = gtk.Label('Enter a name for the new project.\nOnly letters, numbers, and underscore.') + label_0 = Gtk.Label('Enter a name for the new project.\nOnly letters, numbers, and underscore.') # And an error, if needed.. - self.label_1 = gtk.Label() + self.label_1 = Gtk.Label() self.label_1.set_line_wrap(True) # And the text entry - self.entry = gtk.Entry(max=32) + self.entry = Gtk.Entry() + self.entry.set_max_length(32) + # Select a file to import, if desired + label_2 = Gtk.Label('Select a project to import settings from (optional)') + # A combobox to select the project + combobox = Gtk.ComboBoxText() + projectlist = ["-- No import --"] + rootdir = normpath(join(dirname(abspath(__file__)),'../../')) + projectlist.extend([i for i in os.listdir(rootdir) if os.path.isdir(join(rootdir,i)) and os.path.exists(join(rootdir,i+'/'+i+'.reg'))]) #List the folders in pwd that contain a .reg registration file + projectlist.sort() + for project in projectlist: + combobox.append_text(project) + combobox.set_active(0) # And an hbox with 2 buttons - hbox_1 = gtk.HBox(False, 0) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + hbox_1 = Gtk.HBox(False, 0) + btnCANCEL = GtkStockButton('close',"Close") btnCANCEL.connect('clicked', lambda btn: self.hide()) - alignCANCEL = gtk.Alignment(0, 0, 0, 0) + alignCANCEL = Gtk.Alignment.new(0, 0, 0, 0) alignCANCEL.add(btnCANCEL) - btnNEXT = gtk.Button(stock=gtk.STOCK_GO_FORWARD) - btnNEXT.connect('clicked', self.nextClicked, set_projecttype_cb) - alignNEXT = gtk.Alignment(1, 0, 1, 0) + btnNEXT = GtkStockButton('forward',"Next") + btnNEXT.connect('clicked', self.nextClicked, set_projecttype_cb, projectlist, combobox) + alignNEXT = Gtk.Alignment.new(1, 0, 1, 0) alignNEXT.add(btnNEXT) btnNEXT.set_sensitive(False) # And populate @@ -64,6 +78,8 @@ def __init__(self, set_projecttype_cb, parent): vbox.pack_start(label_0, False, False, 0) vbox.pack_start(self.entry, False, False, 0) vbox.pack_start(self.label_1, False, False, 0) + vbox.pack_start(label_2, False, False, 0) + vbox.pack_start(combobox, False, False, 0) vbox.pack_start(hbox_1, False, False, 0) self.show_all() @@ -74,15 +90,16 @@ def lock_btn_title(self, jnk_unused, btnNEXT): (not re.search('[^a-zA-Z0-9_]+', txt))) return - def nextClicked(self, jnk_unused, set_projecttype_cb): + def nextClicked(self, jnk_unused, set_projecttype_cb, projectlist, combobox): '''handles click on next by checking new project name and calling back fsTimer''' entry_text = str(self.entry.get_text()) - if os.path.exists(entry_text): + path = normpath(join(dirname(dirname(dirname(abspath(__file__)))), entry_text)) + if os.path.exists(path): # Add the error text. self.label_1.set_markup('Error! File or directory named "'+entry_text+'" already exists. Try again.') self.label_1.show() else: self.label_1.set_markup('') self.hide() - set_projecttype_cb(entry_text) + set_projecttype_cb(entry_text, projectlist, combobox) diff --git a/fstimer/gui/preregister.py b/fstimer/gui/preregister.py index 4b3a36c..85cd079 100644 --- a/fstimer/gui/preregister.py +++ b/fstimer/gui/preregister.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,56 +17,62 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window handling preregistration setup''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui import os +from fstimer.gui.util_classes import MsgDialog +from fstimer.gui.util_classes import GtkStockButton -class PreRegistrationWin(gtk.Window): +class PreRegistrationWin(Gtk.Window): '''Handling of the window handling preregistration setup''' - def __init__(self, cwd, path, set_registration_file_cb, handle_registration_cb): + def __init__(self, path, set_registration_file_cb, handle_registration_cb): '''Builds and display the window handling preregistration set the computers registration ID, and optionally choose a pre-registration json''' - super(PreRegistrationWin, self).__init__(gtk.WINDOW_TOPLEVEL) - self.cwd = cwd + super(PreRegistrationWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.path = path self.set_registration_file_cb = set_registration_file_cb - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_icon_from_file('fstimer/data/icon.png') - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/icon.png')) + self.set_icon_from_file(fname) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) self.connect('delete_event', lambda b, jnk: self.hide()) self.set_border_width(10) # Start with some intro text. - prereglabel1 = gtk.Label('Give a unique number to each computer used for registration.\nSelect a pre-registration file, if available.') + prereglabel1 = Gtk.Label('Give a unique number to each computer used for registration.\nSelect a pre-registration file, if available.') # Continue to the spinner - preregtable = gtk.Table(3, 2, False) + preregtable = Gtk.Table(3, 2, False) preregtable.set_row_spacings(5) preregtable.set_col_spacings(5) preregtable.set_border_width(10) - regid = gtk.Adjustment(value=1, lower=1, upper=99, step_incr=1) - regid_btn = gtk.SpinButton(regid, digits=0, climb_rate=0) + regid = Gtk.Adjustment(value=1, lower=1, upper=99, step_incr=1) + regid_btn = Gtk.SpinButton(digits=0, climb_rate=0) + regid_btn.set_adjustment(regid) preregtable.attach(regid_btn, 0, 1, 0, 1) - preregtable.attach(gtk.Label("This computer's registration number"), 1, 2, 0, 1) - preregbtnFILE = gtk.Button('Select pre-registration') + preregtable.attach(Gtk.Label(label="This computer's registration number"), 1, 2, 0, 1) + preregbtnFILE = Gtk.Button('Select pre-registration') preregbtnFILE.connect('clicked', self.file_selected) preregtable.attach(preregbtnFILE, 0, 1, 2, 3) - self.preregfilelabel = gtk.Label('') + self.preregfilelabel = Gtk.Label(label='') self.preregfilelabel.set_markup('No pre-registration selected.') preregtable.attach(self.preregfilelabel, 1, 2, 2, 3) ## buttons - prereghbox = gtk.HBox(True, 0) - preregbtnOK = gtk.Button(stock=gtk.STOCK_OK) + prereghbox = Gtk.HBox(True, 0) + preregbtnOK = GtkStockButton('ok',"OK") preregbtnOK.connect('clicked', self.preregister_ok_cb, regid_btn, handle_registration_cb) - preregbtnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + preregbtnCANCEL = GtkStockButton('close',"Close") preregbtnCANCEL.connect('clicked', lambda b: self.hide()) prereghbox.pack_start(preregbtnOK, False, False, 5) prereghbox.pack_start(preregbtnCANCEL, False, False, 5) #Vbox - preregvbox = gtk.VBox(False, 0) - preregbtnhalign = gtk.Alignment(1, 0, 0, 0) + preregvbox = Gtk.VBox(False, 0) + preregbtnhalign = Gtk.Alignment.new(1, 0, 0, 0) preregbtnhalign.add(prereghbox) preregvbox.pack_start(prereglabel1, False, False, 5) preregvbox.pack_start(preregtable, False, False, 5) @@ -76,14 +82,14 @@ def __init__(self, cwd, path, set_registration_file_cb, handle_registration_cb): def file_selected(self, jnk_unused): '''Handle selection of a pre-reg file using a filechooser.''' - chooser = gtk.FileChooserDialog(title='Select pre-registration file', action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) - ffilter = gtk.FileFilter() + chooser = Gtk.FileChooserDialog(title='Select pre-registration file', parent=self, action=Gtk.FileChooserAction.OPEN, buttons=('Cancel', Gtk.ResponseType.CANCEL, 'OK', Gtk.ResponseType.OK)) + ffilter = Gtk.FileFilter() ffilter.set_name('Registration files') ffilter.add_pattern('*_registration_*.json') chooser.add_filter(ffilter) - chooser.set_current_folder(os.path.join(self.cwd, self.path)) + chooser.set_current_folder(self.path) response = chooser.run() - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: filename = chooser.get_filename() try: self.set_registration_file_cb(filename) @@ -97,16 +103,14 @@ def preregister_ok_cb(self, jnk_unused, regid_btn, handle_registration_cb): '''If OK is pushed on the pre-register window.''' #First check if the file already exists regid = regid_btn.get_value_as_int() - filename = os.path.join(self.path, self.path+'_registration_'+str(regid)+'.json') + filename = os.path.join(self.path, os.path.basename(self.path)+'_registration_'+str(regid)+'.json') if os.path.exists(filename): #Raise a warning window - md = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT, - gtk.MESSAGE_WARNING, gtk.BUTTONS_OK_CANCEL, - "A file with this registration number already exists.\nIf you continue it will be overwritten!") + md = MsgDialog(self, 'warning', ['ok', 'cancel'], 'Proceed?', "A file with this registration number already exists.\nIf you continue it will be overwritten!") resp = md.run() md.destroy() #Check the result. - if resp == gtk.RESPONSE_CANCEL: + if resp == Gtk.ResponseType.CANCEL: #Do nothing. return #Else, continue on. diff --git a/fstimer/gui/pretime.py b/fstimer/gui/pretime.py index b178dec..03c091f 100644 --- a/fstimer/gui/pretime.py +++ b/fstimer/gui/pretime.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,61 +17,67 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window dedicated to selecting the timing dictionnary to be used''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui import os, json +from fstimer.gui.util_classes import GtkStockButton -class PreTimeWin(gtk.Window): +class PreTimeWin(Gtk.Window): '''Handling of the window dedicated to selecting the timing dictionnary to be used''' def __init__(self, path, timing, okclicked_cb): '''Builds and display the compilation error window''' - super(PreTimeWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(PreTimeWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.path = path self.timing = timing self.okclicked_cb = okclicked_cb - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_icon_from_file('fstimer/data/icon.png') - self.set_title('fsTimer - Project '+self.path) - self.set_position(gtk.WIN_POS_CENTER) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/icon.png')) + self.set_icon_from_file(fname) + self.set_title('fsTimer - Project '+os.path.basename(self.path)) + self.set_position(Gtk.WindowPosition.CENTER) self.connect('delete_event', lambda b, jnk: self.hide()) self.set_border_width(10) # Start with some intro text. - btnFILE = gtk.Button('Choose file') + btnFILE = Gtk.Button('Choose file') btnFILE.connect('clicked', self.choose_timingdict) - self.pretimefilelabel = gtk.Label('') + self.pretimefilelabel = Gtk.Label(label='') self.pretimefilelabel.set_markup('Select a timing dictionary.') - self.entry1 = gtk.Entry(max=6) + self.entry1 = Gtk.Entry() + self.entry1.set_max_length(6) self.entry1.set_text('0') - label2 = gtk.Label('Specify a "pass" ID, not assigned to any racer') - self.timebtncombobox = gtk.combo_box_new_text() + label2 = Gtk.Label('Specify a "pass" ID, not assigned to any racer') + self.timebtncombobox = Gtk.ComboBoxText() self.timebtnlist = [' ', '.', '/'] timebtndescr = ['Spacebar (" ")', 'Period (".")', 'Forward slash ("/")'] for descr in timebtndescr: self.timebtncombobox.append_text(descr) self.timebtncombobox.set_active(0) - label3 = gtk.Label('Specify the key for marking times. It must not be in any of the IDs.') - hbox3 = gtk.HBox(False, 10) + label3 = Gtk.Label(label='Specify the key for marking times. It must not be in any of the IDs.') + hbox3 = Gtk.HBox(False, 10) hbox3.pack_start(self.timebtncombobox, False, False, 8) hbox3.pack_start(label3, False, False, 8) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + btnCANCEL = GtkStockButton('close',"Close") btnCANCEL.connect('clicked', lambda b: self.hide()) - pretimebtnOK = gtk.Button(stock=gtk.STOCK_OK) + pretimebtnOK = GtkStockButton('ok',"OK") pretimebtnOK.connect('clicked', self.okclicked) - btmhbox = gtk.HBox(False, 8) + btmhbox = Gtk.HBox(False, 8) btmhbox.pack_start(pretimebtnOK, False, False, 8) btmhbox.pack_start(btnCANCEL, False, False, 8) - btmalign = gtk.Alignment(1, 0, 0, 0) + btmalign = Gtk.Alignment.new(1, 0, 0, 0) btmalign.add(btmhbox) - hbox = gtk.HBox(False, 10) + hbox = Gtk.HBox(False, 10) hbox.pack_start(btnFILE, False, False, 8) hbox.pack_start(self.pretimefilelabel, False, False, 8) - hbox2 = gtk.HBox(False, 10) + hbox2 = Gtk.HBox(False, 10) hbox2.pack_start(self.entry1, False, False, 8) hbox2.pack_start(label2, False, False, 8) - vbox = gtk.VBox(False, 10) + vbox = Gtk.VBox(False, 10) vbox.pack_start(hbox, False, False, 8) vbox.pack_start(hbox2, False, False, 8) vbox.pack_start(hbox3, False, False, 8) @@ -82,18 +88,18 @@ def __init__(self, path, timing, okclicked_cb): def choose_timingdict(self, jnk_unused): '''Handles click on Choose file button Converts the selected file into a defaultdict''' - chooser = gtk.FileChooserDialog(title='Choose timing dictionary', action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) - chooser.set_current_folder(os.path.join(os.getcwd(), self.path)) - ffilter = gtk.FileFilter() + chooser = Gtk.FileChooserDialog(title='Choose timing dictionary', parent=self, action=Gtk.FileChooserAction.OPEN, buttons=('Cancel', Gtk.ResponseType.CANCEL, 'OK', Gtk.ResponseType.OK)) + chooser.set_current_folder(self.path) + ffilter = Gtk.FileFilter() ffilter.set_name('Timing dictionaries') ffilter.add_pattern('*_timing_dict.json') chooser.add_filter(ffilter) response = chooser.run() - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: filename = chooser.get_filename() self.timing.clear() # reset try: - with open(filename, 'rb') as fin: + with open(filename, 'r', encoding='utf-8') as fin: a = json.load(fin) for reg in a.keys(): self.timing[reg].update(a[reg]) diff --git a/fstimer/gui/printfields.py b/fstimer/gui/printfields.py new file mode 100644 index 0000000..d4501de --- /dev/null +++ b/fstimer/gui/printfields.py @@ -0,0 +1,227 @@ +#fsTimer - free, open source software for race timing. +#Copyright 2012-14 Ben Letham + +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation, either version 3 of the License, or +#(at your option) any later version. + +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. + +#You should have received a copy of the GNU General Public License +#along with this program. If not, see . + +#The author/copyright holder can be contacted at bletham@gmail.com +'''Handling of the window dedicated to the definition of the print + fields''' + +import gi +import re +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton +from fstimer.gui.util_classes import MsgDialog + +class PrintFieldsWin(Gtk.Window): + '''Handling of the window dedicated to the definition of the field + to reset when registering several members of a family''' + + def __init__(self, fields, printfields, back_clicked_cb, next_clicked_cb, parent, edit): + '''Creates print fields window''' + super(PrintFieldsWin, self).__init__(Gtk.WindowType.TOPLEVEL) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + self.set_transient_for(parent) + self.set_modal(True) + self.set_size_request(600, 600) + self.set_title('fsTimer - Choose results fields') + self.set_position(Gtk.WindowPosition.CENTER) + self.set_border_width(20) + self.connect('delete_event', lambda b, jnk: self.hide()) + self.printfields = printfields + self.fields = fields + # Now create the vbox. + vbox = Gtk.VBox(False, 2) + self.add(vbox) + # Now add the text. + label1_0 = Gtk.Label("Choose the fields to show on the results printout.\nPress 'Forward' to continue with the default settings, or make edits below.\n") + label1_1 = Gtk.Label() + label1_1.set_markup('Registration fields') + vbox.pack_start(label1_0, False, False, 0) + vbox.pack_start(label1_1, False, False, 0) + reg_sw = Gtk.ScrolledWindow() + reg_sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + reg_sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + vbox_btn = Gtk.VBox(False, 2) + btnlist = [] + for field in self.fields: + btnlist.append(Gtk.CheckButton(field)) + if field in self.printfields: + btnlist[-1].set_active(True) + else: + btnlist[-1].set_active(False) + vbox_btn.pack_start(btnlist[-1], True, True, 0) + reg_sw.add_with_viewport(vbox_btn) + vbox.pack_start(reg_sw, True, True, 5) + label1_2 = Gtk.Label() + label1_2.set_markup('Computed results') + vbox.pack_start(label1_2, False, False, 0) + btn_time = Gtk.CheckButton('Time') + if 'Time' in self.printfields: + btn_time.set_active(True) + # Pace buttons + btn_pace = Gtk.CheckButton('Pace') + label_pace = Gtk.Label(' Distance:') + entry_pace = Gtk.Entry() + entry_pace.set_max_length(5) + entry_pace.set_width_chars(5) + # Fill it in + if 'Pace' in self.printfields: + btn_pace.set_active(True) + entry_pace.set_text(str(float(self.printfields['Pace'].split('/')[1]))) + hbox_pace = Gtk.HBox(False, 5) + hbox_pace.pack_start(btn_pace, False, False, 0) + hbox_pace.pack_start(label_pace, False, False, 2) + hbox_pace.pack_start(entry_pace, False, False, 2) + vbox.pack_start(btn_time, False, False, 0) + vbox.pack_start(hbox_pace, False, False, 0) + vbox.pack_start(Gtk.Label("\nCustom expressions ({Time} is in seconds):"), False, False, 0) + self.customview = Gtk.TreeView() + # Make the model, a liststore with columns str, bool, str + self.custommodel = Gtk.ListStore(str, str) + for field in self.printfields: + if field not in self.fields and field not in ['Time', 'Pace']: + self.custommodel.append((field, self.printfields[field])) + self.customview.set_model(self.custommodel) + selection = self.customview.get_selection() + # Add following columns to the treeview : + # field name | expression + name_renderer = Gtk.CellRendererText() + name_renderer.set_property("editable", True) + name_renderer.connect("edited", self.name_edit) + column = Gtk.TreeViewColumn('Name', name_renderer, text=0) + self.customview.append_column(column) + code_renderer = Gtk.CellRendererText() + code_renderer.set_property("editable", True) + code_renderer.connect("edited", self.code_edit) + column = Gtk.TreeViewColumn('Expression', code_renderer, text=1) + self.customview.append_column(column) + # Create scrolled window, in an alignment + customsw = Gtk.ScrolledWindow() + customsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + customsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + customsw.add(self.customview) + customalgn = Gtk.Alignment.new(0, 0, 1, 1) + customalgn.add(customsw) + # Now we put the buttons on the side. + vbox2 = Gtk.VBox(False, 10) + btnREMOVE = GtkStockButton('remove',"Remove") + btnREMOVE.connect('clicked', self.custom_remove, selection) + vbox2.pack_start(btnREMOVE, False, False, 0) + btnNEW = GtkStockButton('new',"New") + btnNEW.connect('clicked', self.custom_new, selection) + vbox2.pack_start(btnNEW, False, False, 0) + # And an hbox for the fields and the buttons + hbox4 = Gtk.HBox(False, 0) + hbox4.pack_start(customalgn, True, True, 10) + hbox4.pack_start(vbox2, False, False, 0) + vbox_comp = Gtk.VBox(False, 0) + vbox_comp.pack_start(hbox4, True, True, 0) + vbox.pack_start(vbox_comp, False, False, 0) + # And an hbox with 2 buttons + hbox = Gtk.HBox(False, 0) + btnCANCEL = GtkStockButton('close',"Close") + btnCANCEL.connect('clicked', lambda btn: self.hide()) + alignCANCEL = Gtk.Alignment.new(0, 0, 0, 0) + alignCANCEL.add(btnCANCEL) + btnBACK = GtkStockButton('back',"Back") + btnBACK.connect('clicked', back_clicked_cb, btnlist, btn_time, btn_pace, entry_pace, self.printfields) + btnNEXT = GtkStockButton('forward',"Next") + btnNEXT.connect('clicked', next_clicked_cb, btnlist, btn_time, btn_pace, entry_pace, self.printfields, edit) + ##And populate + hbox.pack_start(alignCANCEL, True, True, 0) + hbox.pack_start(btnBACK, False, False, 2) + hbox.pack_start(btnNEXT, False, False, 0) + vbox.pack_start(hbox, False, False, 8) + self.show_all() + + def custom_remove(self, jnk_unused, selection): + '''handles a click on REMOVE button''' + treeiter1 = selection.get_selected()[1] + if treeiter1: + row = self.custommodel.get_path(treeiter1)[0] + field = self.custommodel.get_value(treeiter1, 0) + self.custommodel.remove(treeiter1) + self.printfields.pop(field) + selection.select_path((row, )) + return + + def custom_new(self, jnk_unused, selection): + '''handles a click on NEW button''' + # create new name + name = 'Custom field' + i = 1 + while name in self.printfields: + name = 'Custom field%d' % i + i = i + 1 + # append new line with default content + self.printfields[name] = '{Time}' + self.custommodel.append((name, '{Time}')) + return + + def name_edit(self, widget, path, text): + '''handles a change of a ranking name''' + treeiter = self.custommodel.get_iter(path) + old_name = self.custommodel.get_value(treeiter, 0) + # Check if it was changed + if text != old_name: + if text in ['Time', 'Pace']: + md = MsgDialog(self, 'error', ['ok'], 'Error!', 'Names "Time" and "Pace" are reserved.') + md.run() + md.destroy() + elif text not in self.printfields and text not in self.fields: + self.custommodel[path][0] = text + self.printfields[text] = self.printfields[old_name] + self.printfields.pop(old_name) + else: + md = MsgDialog(self, 'error', ['ok'], 'Error!', 'Field name "%s" is already used!' % text) + md.run() + md.destroy() + return + + def code_edit(self, widget, path, text): + '''handles a change of the ranking code''' + # Validate the code + try: + # First make sure all of the variables are Time or a registration field + vars_ = re.findall("\{[^}]+\}", text) + for var in vars_: + name = var[1:-1] + if not (name in self.fields or name == 'Time'): + raise KeyError('Cannot find variable {}'.format(name)) + # Now check that the operations work, by plugging in a float for Time, and a string + # for everything else. + text_test = str(text) + for var in vars_: + if var == '{Time}': + text_test = text_test.replace(var, "3.14159") + if var == '{Age}': + text_test = text_test.replace(var, "20") + else: + # Use a string of a number so that int() works + # In the future we will have typed fields. + text_test = text_test.replace(var, "'1000000001'") + eval(text_test) + except Exception as e: + md = MsgDialog(self, 'error', ['ok'], 'Error!', 'Invalid code:\n\n{}\n\nSee documentation.'.format(e)) + md.run() + md.destroy() + return + treeiter = self.custommodel.get_iter(path) + name = self.custommodel.get_value(treeiter, 0) + self.custommodel[path][1] = text + self.printfields[name] = text + return \ No newline at end of file diff --git a/fstimer/gui/projecttype.py b/fstimer/gui/projecttype.py index 61d81fa..ebb4813 100644 --- a/fstimer/gui/projecttype.py +++ b/fstimer/gui/projecttype.py @@ -17,39 +17,41 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the new project windows''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +from fstimer.gui.util_classes import GtkStockButton -class ProjectTypeWin(gtk.Window): +class ProjectTypeWin(Gtk.Window): '''Handles setting project settings''' def __init__(self, project_types, projecttype, numlaps, back_clicked_cb, next_clicked_cb, parent): '''Creates project type window''' - super(ProjectTypeWin, self).__init__(gtk.WINDOW_TOPLEVEL) - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + super(ProjectTypeWin, self).__init__(Gtk.WindowType.TOPLEVEL) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.set_transient_for(parent) self.set_modal(True) self.set_title('fsTimer - Project type') - self.set_position(gtk.WIN_POS_CENTER) + self.set_position(Gtk.WindowPosition.CENTER) self.set_border_width(20) self.connect('delete_event', lambda b, jnk_unused: self.hide()) # Now create the vbox. - vbox = gtk.VBox(False, 50) + vbox = Gtk.VBox(False, 50) self.add(vbox) ##First is the project type - label_0 = gtk.Label('Select the race type.') + label_0 = Gtk.Label(label='Select the race type.') rbs = {} - rbs[0] = gtk.RadioButton(group=None, label='Standard.\t All runners begin at the same time.') - rbs[1] = gtk.RadioButton(group=rbs[0], label='Handicap.\t Some runners are assigned a handicap and start the race later.') + rbs[0] = Gtk.RadioButton(group=None, label='Standard.\t All runners begin at the same time.') + rbs[1] = Gtk.RadioButton(group=rbs[0], label='Handicap.\t Some runners are assigned a handicap and start the race later.') #Set the correct button active rbs[project_types.index(projecttype)].set_active(True) ##Second are other race settigns. - label_1 = gtk.Label('Additional race options:') - check_button = gtk.CheckButton(label='Lap timing - Check the box and specify the number of laps if more than one:') - numlapsadj = gtk.Adjustment(value=2, lower=2, upper=10, step_incr=1) - numlapsbtn = gtk.SpinButton(numlapsadj, digits=0, climb_rate=0) + label_1 = Gtk.Label(label='Additional race options:') + check_button = Gtk.CheckButton(label='Lap timing - Check the box and specify the number of laps if more than one:') + numlapsadj = Gtk.Adjustment(value=2, lower=2, upper=10, step_incr=1) + numlapsbtn = Gtk.SpinButton(digits=0, climb_rate=0) + numlapsbtn.set_adjustment(numlapsadj) #Activate the button as needed if numlaps > 1: check_button.set_active(True) @@ -58,29 +60,29 @@ def __init__(self, project_types, projecttype, numlaps, back_clicked_cb, next_cl check_button.set_active(False) numlapsadj.set_value(2) #Pop these in an hbox - hbox_0 = gtk.HBox(False, 0) + hbox_0 = Gtk.HBox(False, 0) hbox_0.pack_start(check_button, False, False, 8) hbox_0.pack_start(numlapsbtn, False, False, 8) # And an hbox with 2 buttons - hbox_1 = gtk.HBox(False, 0) - btnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + hbox_1 = Gtk.HBox(False, 0) + btnCANCEL = GtkStockButton('close',"Close") btnCANCEL.connect('clicked', lambda btn: self.hide()) - alignCANCEL = gtk.Alignment(0, 0, 0, 0) + alignCANCEL = Gtk.Alignment.new(0, 0, 0, 0) alignCANCEL.add(btnCANCEL) - btnBACK = gtk.Button(stock=gtk.STOCK_GO_BACK) + btnBACK = GtkStockButton('back',"Back") btnBACK.connect('clicked', back_clicked_cb) - btnNEXT = gtk.Button(stock=gtk.STOCK_GO_FORWARD) + btnNEXT = GtkStockButton('forward',"Next") btnNEXT.connect('clicked', next_clicked_cb, rbs, check_button, numlapsbtn) - alignNEXT = gtk.Alignment(1, 0, 1, 0) + alignNEXT = Gtk.Alignment.new(1, 0, 1, 0) alignNEXT.add(btnNEXT) - alignBACK = gtk.Alignment(1, 0, 1, 0) + alignBACK = Gtk.Alignment.new(1, 0, 1, 0) alignBACK.add(btnBACK) # And populate hbox_1.pack_start(alignCANCEL, True, True, 0) hbox_1.pack_start(alignBACK, False, False, 0) hbox_1.pack_start(alignNEXT, False, False, 0) - vbox1 = gtk.VBox(False, 10) - vbox2 = gtk.VBox(False, 10) + vbox1 = Gtk.VBox(False, 10) + vbox2 = Gtk.VBox(False, 10) vbox1.pack_start(label_0, False, False, 0) vbox1.pack_start(rbs[0], False, False, 0) vbox1.pack_start(rbs[1], False, False, 0) diff --git a/fstimer/gui/register.py b/fstimer/gui/register.py index 2b349a2..d690a8a 100644 --- a/fstimer/gui/register.py +++ b/fstimer/gui/register.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,128 +17,151 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the window dedicated to registration''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui +import os import re +from fstimer.gui.util_classes import MsgDialog +from fstimer.gui.util_classes import GtkStockButton -class RegistrationWin(gtk.Window): +class RegistrationWin(Gtk.Window): '''Handling of the window dedicated to registration''' - def __init__(self, path, fields, fieldsdic, prereg, clear_for_fam, projecttype, save_registration_cb): + def __init__(self, path, fields, fieldsdic, prereg, projecttype, save_registration_cb, parent_win=None, + autosave=True, save_label=''): '''Builds and display the registration window''' - super(RegistrationWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(RegistrationWin, self).__init__(Gtk.WindowType.TOPLEVEL) + if parent_win: + self.set_transient_for(parent_win) + self.set_modal(True) self.fields = fields self.fieldsdic = fieldsdic self.prereg = prereg - self.clear_for_fam = clear_for_fam + self.ids = set() self.projecttype = projecttype self.save_registration_cb = save_registration_cb + self.autosave = autosave self.editreg_win = None self.editregfields = None # First we define the registration model. # We will setup a liststore that is wrapped in a treemodelfilter # that is wrapped in a treemodelsort that is put in a treeview # that is put in a scrolled window. Eesh. - self.regmodel = gtk.ListStore(*[str for field in self.fields]) + self.regmodel = Gtk.ListStore(*[str for field in self.fields]) self.modelfilter = self.regmodel.filter_new() - self.modelfiltersorted = gtk.TreeModelSort(self.modelfilter) - self.treeview = gtk.TreeView() + self.modelfiltersorted = Gtk.TreeModelSort(self.modelfilter) + self.treeview = Gtk.TreeView() # Now we define each column in the treeview for (colid, field) in enumerate(fields): - column = gtk.TreeViewColumn(field, gtk.CellRendererText(), text=colid) + column = Gtk.TreeViewColumn(field, Gtk.CellRendererText(), text=colid) column.set_sort_column_id(colid) self.treeview.append_column(column) - self.lastnamecol = fields.index('Last name') # Now we populate the model with the pre-registration info, if any for reg in prereg: self.regmodel.append([reg[field] for field in fields]) + if reg['ID']: + self.ids.add(reg['ID']) # This is the string that we filter based on. self.searchstr = '' self.modelfilter.set_visible_func(self.visible_filter) self.treeview.set_model(self.modelfiltersorted) self.treeview.set_enable_search(False) # Now let us actually build the window - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_icon_from_file('fstimer/data/icon.png') - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) - self.connect('delete_event', lambda b, jnk: self.ok_clicked(jnk)) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/icon.png')) + self.set_icon_from_file(fname) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) + self.connect('delete_event', lambda b, jnk: self.close_clicked(jnk)) self.set_border_width(10) self.set_size_request(850, 450) #Now the filter entrybox - filterbox = gtk.HBox(False, 8) - filterbox.pack_start(gtk.Label('Filter by last name:'), False, False, 0) - self.filterentry = gtk.Entry(max=40) + filterbox = Gtk.HBox(False, 8) + filterbox.pack_start(Gtk.Label('Filter by ', True, True, 0), False, False, 0) + self.filter_combo = Gtk.ComboBoxText() + for field in self.fields: + self.filter_combo.append_text(field) + self.filter_combo.set_active(0) + filterbox.pack_start(self.filter_combo, False, False, 0) + filterbox.pack_start(Gtk.Label(':'), False, False, 0) + self.filterentry = Gtk.Entry() + self.filterentry.set_max_length(40) self.filterentry.connect('changed', self.filter_apply) - self.filterbtnCLEAR = gtk.Button(stock=gtk.STOCK_CLEAR) + self.filterbtnCLEAR = GtkStockButton('clear',"Clear") self.filterbtnCLEAR.connect('clicked', self.filter_clear) self.filterbtnCLEAR.set_sensitive(False) filterbox.pack_start(self.filterentry, False, False, 0) filterbox.pack_start(self.filterbtnCLEAR, False, False, 0) # Now the scrolled window that contains the treeview - regsw = gtk.ScrolledWindow() - regsw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - regsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + regsw = Gtk.ScrolledWindow() + regsw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + regsw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) regsw.add(self.treeview) # And a message that says if we have saved or not. - self.regstatus = gtk.Label('') + self.regstatus = Gtk.Label(label=save_label) # Some boxes for all the stuff on the left - regvbox1 = gtk.VBox(False, 8) + regvbox1 = Gtk.VBox(False, 8) regvbox1.pack_start(filterbox, False, False, 0) regvbox1.pack_start(regsw, True, True, 0) regvbox1.pack_start(self.regstatus, False, False, 0) - vbox1align = gtk.Alignment(0, 0, 1, 1) + vbox1align = Gtk.Alignment.new(0, 0, 1, 1) vbox1align.add(regvbox1) # And boxes/table for the buttons on the right - regtable = gtk.Table(2, 1, False) + regtable = Gtk.Table(2, 1, False) regtable.set_row_spacings(5) regtable.set_col_spacings(5) regtable.set_border_width(5) - btnEDIT = gtk.Button(stock=gtk.STOCK_EDIT) + btnEDIT = GtkStockButton('edit',"Edit") btnEDIT.connect('clicked', self.edit_clicked) - btnREMOVE = gtk.Button(stock=gtk.STOCK_REMOVE) + btnREMOVE = GtkStockButton('remove',"Remove") btnREMOVE.connect('clicked', self.rm_clicked) - btnFAM = gtk.Button('Add family') - btnFAM.connect('clicked', self.fam_clicked) - btnNEW = gtk.Button(stock=gtk.STOCK_NEW) + btnNEW = GtkStockButton('new',"New") btnNEW.connect('clicked', self.new_clicked) - btnSAVE = gtk.Button(stock=gtk.STOCK_SAVE) + btnSAVE = GtkStockButton('save',"Save") btnSAVE.connect('clicked', self.save_clicked) - btnOK = gtk.Button('Done') - btnOK.connect('clicked', self.ok_clicked) - vsubbox = gtk.VBox(False, 8) + btnOK = GtkStockButton('close',"Close") + btnOK.connect('clicked', self.close_clicked) + vsubbox = Gtk.VBox(False, 8) vsubbox.pack_start(btnSAVE, False, False, 0) - vsubbox.pack_start(btnOK, False, False, 0) - regvspacer = gtk.Alignment(1, 1, 0, 0) + regvspacer = Gtk.Alignment.new(1, 1, 0, 0) regvspacer.add(vsubbox) regtable.attach(regvspacer, 0, 1, 1, 2) - regvbox2 = gtk.VBox(False, 8) - regvbox2.pack_start(btnEDIT, False, False, 0) - regvbox2.pack_start(btnREMOVE, False, False, 0) - regvbox2.pack_start(btnFAM, False, False, 0) + regvbox2 = Gtk.VBox(False, 8) regvbox2.pack_start(btnNEW, False, False, 0) - regvbalign = gtk.Alignment(1, 0, 0, 0) + regvbox2.pack_start(btnEDIT, False, False, 0) + regvbox2.pack_start(btnREMOVE, False, False, 30) + regvbalign = Gtk.Alignment.new(1, 0, 0, 0) regvbalign.add(regvbox2) regtable.attach(regvbalign, 0, 1, 0, 1) #Now we pack everything together - reghbox = gtk.HBox(False, 8) + reghbox = Gtk.HBox(False, 8) reghbox.pack_start(vbox1align, True, True, 0) reghbox.pack_start(regtable, False, False, 0) - self.add(reghbox) + # Add in the close button on the bottom + vbox_all = Gtk.VBox(False, 0) + vbox_all.pack_start(reghbox, True, True, 0) + donespacer = Gtk.Alignment.new(0, 0, 0, 0) + donespacer.add(btnOK) + vbox_all.pack_start(donespacer, False, False, 5) + self.add(vbox_all) # And show. self.show_all() - def visible_filter(self, model, titer): + def visible_filter(self, model, titer, data): '''This is the filter function. - It checks if self.searchstr is contained in column self.lastnamecol, + It checks if self.searchstr is contained in column matching self.filter_combo case insensitive''' if self.searchstr: - if not model.get_value(titer, self.lastnamecol): + col_idx = self.filter_combo.get_active() + if not model.get_value(titer, col_idx): return False else: - return self.searchstr.lower() in model.get_value(titer, self.lastnamecol).lower() + return self.searchstr.lower() in model.get_value(titer, col_idx).lower() else: return True @@ -178,35 +201,27 @@ def rm_clicked(self, jnk_unused): treeiter = selection.get_selected()[1] # if nothing is selected, do nothing. if treeiter: - rmreg_dialog = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Are you sure you want to delete this entry?\nThis cannot be undone.') - rmreg_dialog.set_title('Woah!') - rmreg_dialog.set_default_response(gtk.RESPONSE_NO) + rmreg_dialog = MsgDialog(self, 'warning', ['yes', 'no'], 'Really delete?', 'Are you sure you want to delete this entry?\nThis cannot be undone.') + rmreg_dialog.set_default_response(Gtk.ResponseType.NO) response = rmreg_dialog.run() rmreg_dialog.destroy() - if response == gtk.RESPONSE_YES: + if response == Gtk.ResponseType.YES: + # Grab the current information. + current_info = {} + for (colid, field) in enumerate(self.fields): + current_info[field] = self.modelfiltersorted.get_value(treeiter, colid) + # Find where this is in self.prereg + preregiter = self.prereg.index(current_info) # converts the treeiter from sorted to filter to model, and remove - self.regmodel.remove(self.modelfilter.convert_iter_to_child_iter(self.modelfiltersorted.convert_iter_to_child_iter(None, treeiter))) + self.regmodel.remove(self.modelfilter.convert_iter_to_child_iter(self.modelfiltersorted.convert_iter_to_child_iter(treeiter))) + try: + self.ids.remove(current_info['ID']) + except: + pass + self.prereg.pop(preregiter) # The latest stuff has no longer been saved. self.regstatus.set_markup('') - def fam_clicked(self, jnk_unused): - '''Handles click on the 'add family' button on the registration window. - Constructs current_info the same as in self.edit_reg, - but passes None instead of treeiter''' - selection = self.treeview.get_selection() - treeiter = selection.get_selected()[1] - # if no selection, do nothing. - if treeiter: - # Grab the current information. - current_info = {} - for (colid, field) in enumerate(self.fields): - current_info[field] = self.modelfiltersorted.get_value(treeiter, colid) - # Drop some info - for field in self.clear_for_fam: - current_info[field] = '' - # Generate the window - self.edit_registration(None, None, current_info) - def new_clicked(self, jnk_unused): '''Handles click on the 'new' button on the registration window Creates the editreg window with a None treeiter and clear initial values.''' @@ -215,20 +230,26 @@ def new_clicked(self, jnk_unused): def save_clicked(self, jnk_unused): '''Handles click on the 'save' button on the registration window. We do a json dump of self.prereg''' - filename = self.save_registration_cb() - self.regstatus.set_markup('Registration saved to %s' % filename) + filename, success = self.save_registration_cb() + if success: + self.regstatus.set_markup('Registration saved to %s' % filename) + return True + else: + self.regstatus.set_markup('Registration NOT saved: %s' % filename) + return False - def ok_clicked(self, jnk_unused): - '''Handles click on the 'ok' button on the registration window. + def close_clicked(self, jnk_unused): + '''Handles click on the 'close' button on the registration window. Throws up a 'do you want to save' dialog, and close the window''' - okreg_dialog = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Do you want to save before finishing?\nUnsaved data will be lost.') - okreg_dialog.set_title('Save?') - okreg_dialog.set_default_response(gtk.RESPONSE_YES) + okreg_dialog = MsgDialog(self, 'question', ['yes', 'no'], 'Save?', 'Do you want to save before finishing?\nUnsaved data will be lost.') + okreg_dialog.set_default_response(Gtk.ResponseType.YES) response = okreg_dialog.run() okreg_dialog.destroy() - if response == gtk.RESPONSE_YES: + if response == Gtk.ResponseType.YES: # this will save - self.save_clicked(None) + save_res = self.save_clicked(None) + if not save_res: + return self.hide() # Clear the file setting from pre-reg, in case pre-reg is # re-run without selecting a file @@ -238,20 +259,20 @@ def edit_registration(self, treeiter, preregiter, current_info): '''handles creation/modification of a registration entry. Converts the treeiter from the treemodelsort to the liststore.''' if treeiter: - treeiter = self.modelfilter.convert_iter_to_child_iter(self.modelfiltersorted.convert_iter_to_child_iter(None, treeiter)) + treeiter = self.modelfilter.convert_iter_to_child_iter(self.modelfiltersorted.convert_iter_to_child_iter(treeiter)) # Define the window - self.editreg_win = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.editreg_win.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.editreg_win = Gtk.Window(Gtk.WindowType.TOPLEVEL) + self.editreg_win.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.editreg_win.set_title('Registration entry') self.editreg_win.set_transient_for(self) self.editreg_win.set_modal(True) - self.editreg_win.set_position(gtk.WIN_POS_CENTER) + self.editreg_win.set_position(Gtk.WindowPosition.CENTER) self.editreg_win.connect('delete_event', lambda b, jnk_unused: self.editreg_win.hide()) self.editreg_win.set_border_width(10) #An hbox for the buttons - editreghbox = gtk.HBox(False, 8) - editregbtnOK = gtk.Button(stock=gtk.STOCK_OK) - editregbtnCANCEL = gtk.Button(stock=gtk.STOCK_CANCEL) + editreghbox = Gtk.HBox(False, 8) + editregbtnOK = GtkStockButton('ok',"OK") + editregbtnCANCEL = GtkStockButton('close',"Cancel") editregbtnCANCEL.connect('clicked', lambda b: self.editreg_win.hide()) editreghbox.pack_start(editregbtnOK, False, False, 5) editreghbox.pack_start(editregbtnCANCEL, False, False, 5) @@ -261,12 +282,13 @@ def edit_registration(self, treeiter, preregiter, current_info): # Determine which type of entry is appropriate, create it and fill it. # Entrybox if self.fieldsdic[field]['type'] == 'entrybox': - self.editregfields[field] = gtk.Entry(max=self.fieldsdic[field]['max']) + self.editregfields[field] = Gtk.Entry() + self.editregfields[field].set_max_length(self.fieldsdic[field]['max']) if current_info: self.editregfields[field].set_text(current_info[field]) # Combobox elif self.fieldsdic[field]['type'] == 'combobox': - self.editregfields[field] = gtk.combo_box_new_text() + self.editregfields[field] = Gtk.ComboBoxText() self.editregfields[field].append_text('') for val in self.fieldsdic[field]['options']: self.editregfields[field].append_text(val) @@ -279,27 +301,30 @@ def edit_registration(self, treeiter, preregiter, current_info): else: self.editregfields[field].set_active(0) # Set up the vbox - editregvbox = gtk.VBox(False, 8) + editregvbox = Gtk.VBox(False, 8) # We will make a smaller hbox for each of the fields. hboxes = {} for field in self.fields: - hboxes[field] = gtk.HBox(False, 15) - hboxes[field].pack_start(gtk.Label(field+':'), False, False, 0) #Pack the label + hboxes[field] = Gtk.HBox(False, 15) + hboxes[field].pack_start(Gtk.Label(field+':', True, True, 0), False, False, 0) #Pack the label hboxes[field].pack_start(self.editregfields[field], False, False, 0) #Pack the button/entry/.. if self.projecttype == 'handicap' and field == 'Handicap': - label = gtk.Label('hh:mm:ss') - hboxes[field].pack_start(label, False, False, 0) + label_hd = Gtk.Label(label='hh:mm:ss') + hboxes[field].pack_start(label_hd, False, False, 0) + if field == 'ID': + label_id = Gtk.Label('Must be unique') + hboxes[field].pack_start(label_id, False, False, 0) editregvbox.pack_start(hboxes[field], False, False, 0) #Pack this hbox into the big vbox. #Pack and show if self.projecttype == 'handicap': - editregbtnOK.connect('clicked', self.validate_entry, treeiter, preregiter, label) + editregbtnOK.connect('clicked', self.validate_entry, treeiter, preregiter, label_hd, label_id) else: - editregbtnOK.connect('clicked', self.validate_entry, treeiter, preregiter, None) + editregbtnOK.connect('clicked', self.validate_entry, treeiter, preregiter, None, label_id) editregvbox.pack_start(editreghbox, False, False, 5) self.editreg_win.add(editregvbox) self.editreg_win.show_all() - def validate_entry(self, jnk_unused, treeiter, preregiter, label): + def validate_entry(self, jnk_unused, treeiter, preregiter, label_hd, label_id): '''Handles a click on the 'ok' button of the entry edition window. Reads out the input information, and writes the changes to the treemodel''' #First check if we have entered a handicap, and if so, make sure it is valid @@ -310,7 +335,7 @@ def validate_entry(self, jnk_unused, treeiter, preregiter, label): timePattern = r'((?P-?\d+) day(s)?, )?((?P\d+):)?'r'(?P\d+):(?P\d+)' re.match(timePattern, sduration).groupdict(0) except AttributeError: - label.set_markup('hh:mm:ss') + label_hd.set_markup('hh:mm:ss') return # If that was OK, we go through each field and grab the new value. new_vals = {} @@ -325,17 +350,33 @@ def validate_entry(self, jnk_unused, treeiter, preregiter, label): new_vals[field] = '' else: new_vals[field] = self.fieldsdic[field]['options'][indx-1] + # Make sure we don't have a duplicate ID, unless we are editing and leave it the same. + if treeiter and new_vals['ID'] == self.prereg[preregiter]['ID']: + pass # No need for an ID check, it was already done. + elif new_vals['ID'] in self.ids: + label_id.set_markup('{} has already been used'.format(new_vals['ID'])) + return # Now we replace or append in the treemodel and in prereg if treeiter: + # Remove the old ID from the id store, and add the new value + if self.prereg[preregiter]['ID']: + self.ids.remove(self.prereg[preregiter]['ID']) + # Update the tree and prereg for (colid, field) in enumerate(self.fields): self.regmodel.set_value(treeiter, colid, new_vals[field]) self.prereg[preregiter] = new_vals else: self.regmodel.append([new_vals[field] for field in self.fields]) self.prereg.append(new_vals) + # Add the new ID to the id store + if new_vals['ID']: + self.ids.add(new_vals['ID']) # The saved status is unsaved self.regstatus.set_markup('') - # Filter results by this last name - self.filterentry.set_text(new_vals['Last name']) + # Filter results by the current filter field, for this value + self.filterentry.set_text(new_vals[self.filter_combo.get_active_text()]) + # Save + if self.autosave: + self.save_clicked(None) # we're done self.editreg_win.hide() diff --git a/fstimer/gui/root.py b/fstimer/gui/root.py index 89f850b..0730e76 100644 --- a/fstimer/gui/root.py +++ b/fstimer/gui/root.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,67 +17,73 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handles the root window of the application''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import fstimer.gui -import webbrowser +import webbrowser, os +from fstimer.gui.util_classes import GtkStockButton +from fstimer.gui.util_classes import MenuItemIcon -class RootWin(gtk.Window): +class RootWin(Gtk.Window): '''Handles the root window of the application''' def __init__(self, path, show_about_cb, importprereg_cb, - prereg_cb, compreg_cb, pretime_cb): + prereg_cb, compreg_cb, pretime_cb, edit_cb): '''Creates the root window with choices for the tasks''' - super(RootWin, self).__init__(gtk.WINDOW_TOPLEVEL) - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) - self.set_icon_from_file('fstimer/data/icon.png') - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) - self.connect('delete_event', gtk.main_quit) + super(RootWin, self).__init__(Gtk.WindowType.TOPLEVEL) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/icon.png')) + self.set_icon_from_file(fname) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) + self.connect('delete_event', Gtk.main_quit) self.set_border_width(0) # Generate the menubar - mb = gtk.MenuBar() - helpmenu = gtk.Menu() - helpm = gtk.MenuItem('Help') + mb = Gtk.MenuBar() + helpmenu = Gtk.Menu() + helpm = Gtk.MenuItem('Menu') helpm.set_submenu(helpmenu) - menuhelp = gtk.ImageMenuItem(gtk.STOCK_HELP) - menuhelp.connect('activate', lambda jnk: webbrowser.open('documentation/documentation_sec2.htm')) + menuedit = MenuItemIcon('edit', 'Edit project settings', edit_cb, True) + helpmenu.append(menuedit) + menuhelp = MenuItemIcon('help', 'Documentation', lambda x: webbrowser.open_new('http://fstimer.org/documentation/documentation_sec2.htm')) helpmenu.append(menuhelp) - menuabout = gtk.ImageMenuItem(gtk.STOCK_ABOUT) - menuabout.connect('activate', show_about_cb) + menuabout = MenuItemIcon('about', 'About', show_about_cb, self) helpmenu.append(menuabout) mb.append(helpm) ### Frame - rootframe = gtk.Frame(label='al') - rootframe_label = gtk.Label('') - rootframe_label.set_markup('fsTimer - ' + path + '') + rootframe = Gtk.Frame(label='al') + rootframe_label = Gtk.Label(label='') + rootframe_label.set_markup('fsTimer - ' + os.path.basename(path) + '') rootframe.set_label_widget(rootframe_label) rootframe.set_border_width(20) #And now fill the frame with a table - roottable = gtk.Table(4, 2, False) + roottable = Gtk.Table(4, 2, False) roottable.set_row_spacings(20) roottable.set_col_spacings(20) roottable.set_border_width(10) #And internal buttons - rootbtnPREREG = gtk.Button('Preregister') + rootbtnPREREG = Gtk.Button('Import') rootbtnPREREG.connect('clicked', importprereg_cb) - rootlabelPREREG = gtk.Label('') + rootlabelPREREG = Gtk.Label(label='') rootlabelPREREG.set_alignment(0, 0.5) - rootlabelPREREG.set_markup('Prepare pre-registration file.') - rootbtnREG = gtk.Button('Register') + rootlabelPREREG.set_markup('Import registration info from spreadsheet.') + rootbtnREG = Gtk.Button('Register') rootbtnREG.connect('clicked', prereg_cb) - rootlabelREG = gtk.Label('') + rootlabelREG = Gtk.Label(label='') rootlabelREG.set_alignment(0, 0.5) rootlabelREG.set_markup('Register racer information and assign ID numbers.') - rootbtnCOMP = gtk.Button('Compile') + rootbtnCOMP = Gtk.Button('Compile') rootbtnCOMP.connect('clicked', compreg_cb) - rootlabelCOMP = gtk.Label('') + rootlabelCOMP = Gtk.Label(label='') rootlabelCOMP.set_alignment(0, 0.5) - rootlabelCOMP.set_markup('Compile registrations from multiple computers.') - rootbtnTIME = gtk.Button('Time') + rootlabelCOMP.set_markup('Compile registration file(s)') + rootbtnTIME = Gtk.Button('Time') rootbtnTIME.connect('clicked', pretime_cb) - rootlabelTIME = gtk.Label('') + rootlabelTIME = Gtk.Label(label='') rootlabelTIME.set_alignment(0, 0.5) rootlabelTIME.set_markup('Record race times on the day of the race.') roottable.attach(rootbtnPREREG, 0, 1, 0, 1) @@ -90,13 +96,13 @@ def __init__(self, path, show_about_cb, importprereg_cb, roottable.attach(rootlabelTIME, 1, 2, 3, 4) rootframe.add(roottable) ### Buttons - roothbox = gtk.HBox(True, 0) - rootbtnQUIT = gtk.Button(stock=gtk.STOCK_QUIT) - rootbtnQUIT.connect('clicked', gtk.main_quit) + roothbox = Gtk.HBox(True, 0) + rootbtnQUIT = GtkStockButton('close',"Quit") + rootbtnQUIT.connect('clicked', Gtk.main_quit) roothbox.pack_start(rootbtnQUIT, False, False, 5) #Vbox - rootvbox = gtk.VBox(False, 0) - btnhalign = gtk.Alignment(1, 0, 0, 0) + rootvbox = Gtk.VBox(False, 0) + btnhalign = Gtk.Alignment.new(1, 0, 0, 0) btnhalign.add(roothbox) rootvbox.pack_start(mb, False, False, 0) rootvbox.pack_start(rootframe, True, True, 0) diff --git a/fstimer/gui/timing.py b/fstimer/gui/timing.py index d487f27..dc3c61b 100644 --- a/fstimer/gui/timing.py +++ b/fstimer/gui/timing.py @@ -1,5 +1,5 @@ #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -17,20 +17,23 @@ #The author/copyright holder can be contacted at bletham@gmail.com '''Handling of the timing window''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib, Gdk import fstimer.gui import fstimer.gui.editt0 import fstimer.gui.edittime import fstimer.gui.editblocktimes +from fstimer.gui.register import RegistrationWin import datetime import time import os import re import json -import pango +from gi.repository import Pango from collections import defaultdict, Counter +from fstimer.gui.util_classes import MsgDialog +from fstimer.gui.util_classes import GtkStockButton class MergeError(Exception): '''Exception used in case of merging error''' @@ -41,25 +44,45 @@ def time_format(t): milli = int((t - int(t)) * 10) hours, rem = divmod(int(t), 3600) minutes, seconds = divmod(rem, 60) - days, hours = divmod(hours, 24) - s = '%02d:%02d:%02d.%01d' % (hours, minutes, seconds, milli) - if days > 0: - s = '%s day%s, ' % (days, 's' if days > 1 else '') + s + if hours > 0: + s = '%d:%02d:%02d.%01d' % (hours, minutes, seconds, milli) + else: + s = '%d:%02d.%01d' % (minutes, seconds, milli) return s def time_parse(dt): '''converts string time to datetime.timedelta''' - d = re.match(r'((?P\d+) days, )?((?P\d+):)?(?P\d+):(?P\d+)(\.(?P\d+))?', dt).groupdict(0) - d['milliseconds'] = int(d['milliseconds'])*100 + if dt and dt[0] == '-': + return datetime.timedelta(0) #we don't allow negative times + d = re.match(r'((?P\d+):)?(?P\d+):(?P\d+)(\.(?P\d+))?', dt).groupdict(0) + d['milliseconds'] = int(d['milliseconds'])*100 # they are actually centiseconds in the string return datetime.timedelta(**dict(((key, int(value)) for key, value in d.items()))) -class TimingWin(gtk.Window): +def time_diff(t1, t2): + '''takes the diff of two string times and returns it as a time, rectified to 0. t1-t2.''' + delta_t = time_parse(t1) - time_parse(t2) + if delta_t < datetime.timedelta(0): + return '0:00.0' + else: + return time_format(delta_t.total_seconds()) + +def time_sum(t1, t2): + '''takes the sum of two string times and returns it as a time, t1+t2.''' + timesum = time_parse(t1) + time_parse(t2) + return time_format(timesum.total_seconds()) + +class TimingWin(Gtk.Window): '''Handling of the timing window''' - def __init__(self, path, parent, timebtn, rawtimes, timing, print_cb, projecttype, numlaps): + def __init__(self, path, parent, timebtn, rawtimes, timing, print_cb, projecttype, numlaps, + fields, fieldsdic, write_timing_cb): '''Builds and display the compilation error window''' - super(TimingWin, self).__init__(gtk.WINDOW_TOPLEVEL) + super(TimingWin, self).__init__(Gtk.WindowType.TOPLEVEL) self.path = path + self.projecttype = projecttype + self.fields = fields + self.fieldsdic = fieldsdic + self.write_timing_cb = write_timing_cb self.timebtn = timebtn self.rawtimes = rawtimes self.timing = timing @@ -67,47 +90,48 @@ def __init__(self, path, parent, timebtn, rawtimes, timing, print_cb, projecttyp self.wineditblocktime = None self.winedittime = None self.t0win = None - self.modify_bg(gtk.STATE_NORMAL, fstimer.gui.bgcolor) + self.modify_bg(Gtk.StateType.NORMAL, fstimer.gui.bgcolor) self.set_transient_for(parent) self.set_modal(True) - self.set_title('fsTimer - ' + path) - self.set_position(gtk.WIN_POS_CENTER) + self.set_title('fsTimer - ' + os.path.basename(path)) + self.set_position(Gtk.WindowPosition.CENTER) self.connect('delete_event', lambda b, jnk: self.done_timing(b)) self.set_border_width(10) self.set_size_request(450, 450) # We will put the timing info in a liststore in a scrolledwindow - self.timemodel = gtk.ListStore(str, str) + self.timemodel = Gtk.ListStore(str, str) # We will put the liststore in a treeview - self.timeview = gtk.TreeView() - column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0) + self.timeview = Gtk.TreeView() + column = Gtk.TreeViewColumn('ID', Gtk.CellRendererText(), text=0) self.timeview.append_column(column) - column = gtk.TreeViewColumn('Time', gtk.CellRendererText(), text=1) + column = Gtk.TreeViewColumn('Time', Gtk.CellRendererText(), text=1) self.timeview.append_column(column) #An extra column if it is a handicap race if projecttype == 'handicap': - renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn('Corrected Time', renderer) + renderer = Gtk.CellRendererText() + column = Gtk.TreeViewColumn('Corrected Time', renderer) column.set_cell_data_func(renderer, self.print_corrected_time) self.timeview.append_column(column) #Another extra column if it is a lap race if self.numlaps > 1: - renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn('Completed laps', renderer) + renderer = Gtk.CellRendererText() + column = Gtk.TreeViewColumn('Completed laps', renderer) column.set_cell_data_func(renderer, self.print_completed_laps) self.timeview.append_column(column) self.timeview.set_model(self.timemodel) self.timeview.connect('size-allocate', self.scroll_times) treeselection = self.timeview.get_selection() # make it multiple selecting - treeselection.set_mode(gtk.SELECTION_MULTIPLE) + treeselection.set_mode(Gtk.SelectionMode.MULTIPLE) # And put it in a scrolled window, in an alignment - self.timesw = gtk.ScrolledWindow() - self.timesw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - self.timesw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.timesw = Gtk.ScrolledWindow() + self.timesw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + self.timesw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.timesw.add(self.timeview) - timealgn = gtk.Alignment(0, 0, 1, 1) + timealgn = Gtk.Alignment.new(0, 0, 1, 1) timealgn.add(self.timesw) - self.entrybox = gtk.Entry(max=40) + self.entrybox = Gtk.Entry() + self.entrybox.set_max_length(40) self.offset = 0 #this is len(times) - len(ids) self.entrybox.connect('activate', self.record_time) self.entrybox.connect('changed', self.check_for_newtime) @@ -115,20 +139,21 @@ def __init__(self, path, parent, timebtn, rawtimes, timing, print_cb, projecttyp self.timestr = re.sub(' +', '_', time.ctime()).replace(':', '') #we save with the current time in the filename so no chance of being overwritten accidentally # Now lets go on to boxes - tophbox = gtk.HBox() + tophbox = Gtk.HBox() # our default t0, and the stuff on top for setting/edit t0 self.t0 = 0. - btn_t0 = gtk.Button('Start!') + btn_t0 = Gtk.Button('Start!') btn_t0.connect('clicked', self.set_t0) # time display - self.clocklabel = gtk.Label() - self.clocklabel.modify_font(pango.FontDescription("sans 20")) + self.clocklabel = Gtk.Label() + self.clocklabel.modify_font(Pango.FontDescription("sans 20")) self.clocklabel.set_markup(time_format(0)) tophbox.pack_start(btn_t0, False, False, 10) tophbox.pack_start(self.clocklabel, False, False, 10) - timevbox1 = gtk.VBox(False, 8) + timevbox1 = Gtk.VBox(False, 8) timevbox1.pack_start(tophbox, False, False, 0) timevbox1.pack_start(timealgn, True, True, 0) + timevbox1.pack_start(Gtk.Label('Select box below in order to mark times:'), False, False, 0) timevbox1.pack_start(self.entrybox, False, False, 0) # we will keep track of how many racers are still out. self.racers_reg = [] @@ -137,91 +162,97 @@ def __init__(self, path, parent, timebtn, rawtimes, timing, print_cb, projecttyp self.racers_total = len(self.racers_reg[0]) self.racers_in = [0] * self.numlaps self.lapcounter = defaultdict(int) - self.racerslabel = gtk.Label() + self.racerslabel = Gtk.Label() self.update_racers_label() timevbox1.pack_start(self.racerslabel, False, False, 0) - vbox1align = gtk.Alignment(0, 0, 1, 1) + vbox1align = Gtk.Alignment.new(0, 0, 1, 1) vbox1align.add(timevbox1) # buttons on the right side #First an options button that will actually be a menu - options_menu = gtk.Menu() - menu_editt0 = gtk.MenuItem('Edit starting time') + options_menu = Gtk.Menu() + menu_editreg = Gtk.MenuItem('Edit registration data') + menu_editreg.connect_object("activate", self.edit_reg, None) + menu_editreg.show() + options_menu.append(menu_editreg) + menu_resett0 = Gtk.MenuItem('Restart clock') + menu_resett0.connect_object("activate", self.restart_t0, None) + menu_resett0.show() + options_menu.append(menu_resett0) + menu_editt0 = Gtk.MenuItem('Edit starting time') menu_editt0.connect_object("activate", self.edit_t0, None) menu_editt0.show() options_menu.append(menu_editt0) - menu_savecsv = gtk.MenuItem('Save results to CSV') + menu_savecsv = Gtk.MenuItem('Save results to CSV') menu_savecsv.connect_object("activate", print_cb, None, True) #True is to print csv menu_savecsv.show() options_menu.append(menu_savecsv) - menu_resume = gtk.MenuItem('Load saved timing session') + menu_resume = Gtk.MenuItem('Load saved timing session') menu_resume.connect_object("activate", self.resume_times, None, False) #False is for not merging menu_resume.show() options_menu.append(menu_resume) - menu_merge = gtk.MenuItem('Merge in saved IDs or times') + menu_merge = Gtk.MenuItem('Merge in saved IDs or times') menu_merge.connect_object("activate", self.resume_times, None, True) #True is for merging menu_merge.show() options_menu.append(menu_merge) - btnOPTIONS = gtk.Button('Options') + btnOPTIONS = Gtk.Button('Options') btnOPTIONS.connect_object("event", self.options_btn, options_menu) - options_align = gtk.Alignment(1, 0.1, 1, 0) + options_align = Gtk.Alignment.new(1, 0.1, 1, 0) options_align.add(btnOPTIONS) #Then the block of editing buttons - btnDROPID = gtk.Button('Drop ID') + btnDROPID = Gtk.Button('Drop ID') btnDROPID.connect('clicked', self.timing_rm_ID) - btnDROPTIME = gtk.Button('Drop time') + btnDROPTIME = Gtk.Button('Drop time') btnDROPTIME.connect('clicked', self.timing_rm_time) - btnEDIT = gtk.Button(stock=gtk.STOCK_EDIT) + btnEDIT = GtkStockButton('edit',"Edit") btnEDIT.connect('clicked', self.edit_time) - edit_vbox = gtk.VBox(True, 8) + edit_vbox = Gtk.VBox(True, 8) edit_vbox.pack_start(btnDROPID, False, False, 0) edit_vbox.pack_start(btnDROPTIME, False, False, 0) edit_vbox.pack_start(btnEDIT, False, False, 0) - edit_align = gtk.Alignment(1, 0, 1, 0) + edit_align = Gtk.Alignment.new(1, 0, 1, 0) edit_align.add(edit_vbox) #Then the print and save buttons - btnPRINT = gtk.Button(stock=gtk.STOCK_PRINT) + btnPRINT = Gtk.Button('Printouts') btnPRINT.connect('clicked', print_cb, False) - btnSAVE = gtk.Button(stock=gtk.STOCK_SAVE) + btnSAVE = GtkStockButton('save',"Save") btnSAVE.connect('clicked', self.save_times) - save_vbox = gtk.VBox(True, 8) + save_vbox = Gtk.VBox(True, 8) save_vbox.pack_start(btnPRINT, False, False, 0) save_vbox.pack_start(btnSAVE, False, False, 0) - save_align = gtk.Alignment(1, 1, 1, 0) + save_align = Gtk.Alignment.new(1, 1, 1, 0) save_align.add(save_vbox) #And finally the finish button - btnOK = gtk.Button('Done') + btnOK = GtkStockButton('close',"Close") btnOK.connect('clicked', self.done_timing) - done_align = gtk.Alignment(1, 0.7, 1, 0) + done_align = Gtk.Alignment.new(1, 0.7, 1, 0) done_align.add(btnOK) - vsubbox = gtk.VBox(True, 0) + vsubbox = Gtk.VBox(True, 0) vsubbox.pack_start(options_align, True, True, 0) vsubbox.pack_start(edit_align, True, True, 0) vsubbox.pack_start(save_align, True, True, 0) vsubbox.pack_start(done_align, True, True, 0) - vspacer = gtk.Alignment(1, 1, 0, 0) + vspacer = Gtk.Alignment.new(1, 1, 0, 0) vspacer.add(vsubbox) - timehbox = gtk.HBox(False, 8) + timehbox = Gtk.HBox(False, 8) timehbox.pack_start(vbox1align, True, True, 0) timehbox.pack_start(vspacer, False, False, 0) self.add(timehbox) self.show_all() - def print_corrected_time(self, column, renderer, model, itr): + def print_corrected_time(self, column, renderer, model, itr, data): '''computes a handicap corrected time from en entry in the timing model''' bibid, st = model.get(itr, 0, 1) if st and self.timing[bibid]['Handicap']: - t = time_parse(st) try: - th = time_parse(self.timing[bibid]['Handicap']) - nt = t - th - renderer.set_property('text', str(nt)[:-5]) + nt = time_diff(st, self.timing[bibid]['Handicap']) + renderer.set_property('text', nt) except AttributeError: #Handicap is present but is not formatted correctly. renderer.set_property('text', '') else: renderer.set_property('text', '') - def print_completed_laps(self, column, renderer, model, itr): + def print_completed_laps(self, column, renderer, model, itr, data): '''computes number of laps completed by this (registered) racer''' bibid, st = model.get(itr, 0, 1) if bibid: @@ -256,12 +287,23 @@ def update_clock(self): # keep updating return True - def set_t0(self, jnk_unused): + def set_t0(self, btn): '''Handles click on Start button Sets t0 to the current time''' self.t0 = time.time() - gtk.timeout_add(100, self.update_clock) #update clock every 100ms + GLib.timeout_add(100, self.update_clock) #update clock every 100ms + btn.set_sensitive(False) + def restart_t0(self, jnk_unused): + '''Handles click on restart clock button''' + restart_t0_dialog = MsgDialog(self, 'warning', ['yes', 'no'], 'Are you sure?', + 'Are you sure you want to restart the race clock?\nThis cannot be undone.') + restart_t0_dialog.set_default_response(Gtk.ResponseType.NO) + response = restart_t0_dialog.run() + restart_t0_dialog.destroy() + if response == Gtk.ResponseType.YES: + self.t0 = time.time() + def edit_t0(self, jnk_unused): '''Handles click on Edit button for the t0 value. Loads up a window and query the new t0''' @@ -274,12 +316,35 @@ def ok_editt0(self, t0): def options_btn(self, menu, event): '''Handles opening the menu on click of the options button''' - if event.type == gtk.gdk.BUTTON_PRESS: - menu.popup(None, None, None, event.button, event.time) + if event.type == Gdk.EventType.BUTTON_PRESS: + menu.popup(parent_menu_shell=None,parent_menu_item=None, func=None, data=None, button=event.get_button()[1], activate_time=event.get_time()) return True else: return False + def edit_reg(self, jnk_unused): + filename = os.path.join(self.path, os.path.basename(self.path)+'_registration_compiled.json') + with open(filename, 'r', encoding='utf-8') as fin: + self.reg_file = json.load(fin) + regwin = RegistrationWin( + self.path, self.fields, self.fieldsdic, self.reg_file, self.projecttype, self.save_reg, self, False, + 'Loaded '+filename) + + def save_reg(self): + # Re-create the timing dictionary + timedict = defaultdict(lambda: defaultdict(str)) + for reg in self.reg_file: + # Any registration without an ID is left out of the timing dictionary + if reg['ID']: + # have we already added this ID to the timing dictionary? + if reg['ID'] in timedict.keys(): + return 'ID {} NOT UNIQUE'.format(reg['ID']), False + else: + timedict[reg['ID']] = reg + # Write the files and set the new timedict + filename = self.write_timing_cb(self.reg_file, timedict) + return filename, True + def edit_time(self, jnk_unused): '''Handles click on Edit button for a time Chooses which edit time window to open, @@ -304,6 +369,11 @@ def edit_time(self, jnk_unused): def editsingletimedone(self, treeiter, new_id, new_time): '''Handled result of the editing of a given time''' row = self.timemodel.get_path(treeiter)[0] + if not re.match('^[0-9:.]*$', new_time): + md = MsgDialog(self, 'error', ['ok'], 'Error!', 'Time is not valid format.') + md.run() + md.destroy() + return if row < self.offset: if new_id: # we are putting an ID in a slot that we hadn't reached yet @@ -383,30 +453,22 @@ def editsingletimedone(self, treeiter, new_id, new_time): def editblocktimedone(self, pathlist, operation, timestr): '''Handled result of the editing of a block of times Goes through every time in pathlist and do the requested operation''' - for path in pathlist: + for gtkpath in pathlist: # Figure out which row this is, and which treeiter - treeiter = self.timemodel.get_iter(path) - row = path[0] + treeiter = self.timemodel.get_iter(gtkpath) + row = gtkpath[0] # Now figure out the new time. First get the old time as a string old_time_str = self.timemodel.get_value(treeiter, 1) try: - # Now we convert it to timedelta - old_time = time_parse(old_time_str) - # time adjustment - adj_time = time_parse(timestr) - # Combine the timedeltas to get the new time if operation == 'ADD': - new_time = str(old_time + adj_time)[:-5] + new_time = time_sum(old_time_str, timestr) elif operation == 'SUBTRACT': - if old_time > adj_time: - new_time = str(old_time - adj_time)[:-5] - else: - new_time = '0:00:00.0' #We don't allow negative times. + new_time = time_diff(old_time_str, timestr) # Save them, and write out to the timemodel self.rawtimes['times'][row] = str(new_time) self.timemodel.set_value(treeiter, 1, str(new_time)) except AttributeError: - # This will happen for instance if the path has a blank time + # This will happen for instance if the gtkpath has a blank time pass self.wineditblocktime.hide() @@ -429,16 +491,11 @@ def timing_rm_ID(self, jnk_unused): if ididx >= 0: # Otherwise, there is no ID here so there is nothing to do. # Ask if we are sure. - rmID_dialog = gtk.MessageDialog(self, - gtk.DIALOG_MODAL, - gtk.MESSAGE_QUESTION, - gtk.BUTTONS_YES_NO, - 'Are you sure you want to drop this ID and shift all later IDs down earlier in the list?\nThis cannot be undone.') - rmID_dialog.set_title('Woah!') - rmID_dialog.set_default_response(gtk.RESPONSE_NO) + rmID_dialog = MsgDialog(self, 'warning', ['yes', 'no'], 'Are you sure?', 'Are you sure you want to drop this ID and shift all later IDs down earlier in the list?\nThis cannot be undone.') + rmID_dialog.set_default_response(Gtk.ResponseType.NO) response = rmID_dialog.run() rmID_dialog.destroy() - if response == gtk.RESPONSE_YES: + if response == Gtk.ResponseType.YES: # Make the shift in self.rawtimes and self.offset self.rawtimes['ids'].pop(ididx) self.offset += 1 @@ -478,16 +535,11 @@ def timing_rm_time(self, jnk_unused): if timeidx >= 0: # Otherwise, there is no time here so there is nothing to do. # Ask if we are sure. - rmtime_dialog = gtk.MessageDialog(self, - gtk.DIALOG_MODAL, - gtk.MESSAGE_QUESTION, - gtk.BUTTONS_YES_NO, - 'Are you sure you want to drop this time and shift all later times down earlier in the list?\nThis cannot be undone.') - rmtime_dialog.set_title('Woah!') - rmtime_dialog.set_default_response(gtk.RESPONSE_NO) + rmtime_dialog = MsgDialog(self, 'warning', ['yes', 'no'], 'Are you sure?', 'Are you sure you want to drop this time and shift all later times down earlier in the list?\nThis cannot be undone.') + rmtime_dialog.set_default_response(Gtk.ResponseType.NO) response = rmtime_dialog.run() rmtime_dialog.destroy() - if response == gtk.RESPONSE_YES: + if response == Gtk.ResponseType.YES: # Make the shift in self.rawtimes and self.offset self.rawtimes['times'].pop(timeidx) self.offset -= 1 @@ -510,17 +562,17 @@ def timing_rm_time(self, jnk_unused): def resume_times(self, jnk_unused, isMerge): '''Handles click on Resume button''' - chooser = gtk.FileChooserDialog(title='Choose timing results to resume', action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) - chooser.set_current_folder(os.path.join(os.getcwd(), self.path)) - ffilter = gtk.FileFilter() + chooser = Gtk.FileChooserDialog(title='Choose timing results to resume', parent=self, action=Gtk.FileChooserAction.OPEN, buttons=('Cancel', Gtk.ResponseType.CANCEL, 'OK', Gtk.ResponseType.OK)) + chooser.set_current_folder(self.path) + ffilter = Gtk.FileFilter() ffilter.set_name('Timing results') ffilter.add_pattern('*_times.json') chooser.add_filter(ffilter) response = chooser.run() - if response == gtk.RESPONSE_OK: + if response == Gtk.ResponseType.OK: filename = chooser.get_filename() try: - with open(filename, 'rb') as fin: + with open(filename, 'r', encoding='utf-8') as fin: saveresults = json.load(fin) newrawtimes = saveresults['rawtimes'] if isMerge: @@ -543,7 +595,7 @@ def resume_times(self, jnk_unused, isMerge): self.rawtimes['times'] = newrawtimes['times'] #self.timestr = saveresults['timestr'] #We will _not_ overwrite when resuming. self.t0 = saveresults['t0'] - gtk.timeout_add(100, self.update_clock) #start the stopwatch + GLib.timeout_add(100, self.update_clock) #start the stopwatch # Recompute how many racers have checked in self.racers_in = [0] * self.numlaps for ID in self.rawtimes['ids']: @@ -562,11 +614,8 @@ def resume_times(self, jnk_unused, isMerge): adj_ids = list(self.rawtimes['ids']) for entry in zip(adj_ids, adj_times): self.timemodel.append(list(entry)) - except (IOError, ValueError, TypeError, MergeError), e: - error_dialog = gtk.MessageDialog(self, gtk.DIALOG_MODAL, - gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, - 'ERROR: Failed to %s : %s.' % ('merge' if isMerge else 'result', e)) - error_dialog.set_title('Oops...') + except (IOError, ValueError, TypeError, MergeError) as e: + error_dialog = MsgDialog(self, 'error', ['ok'], 'Oops...', 'ERROR: Failed to %s : %s.' % ('merge' if isMerge else 'resume', e)) response = error_dialog.run() error_dialog.destroy() chooser.destroy() @@ -578,31 +627,23 @@ def save_times(self, jnk_unused): saveresults['rawtimes'] = self.rawtimes saveresults['timestr'] = self.timestr saveresults['t0'] = self.t0 - with open(os.path.join(self.path, self.path+'_'+self.timestr+'_times.json'), 'wb') as fout: + with open(os.path.join(self.path, os.path.basename(self.path)+'_'+self.timestr+'_times.json'), 'w', encoding='utf-8') as fout: json.dump(saveresults, fout) - md = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, "Times saved!") + md = MsgDialog(self, 'information', ['ok'], 'Saved!', 'Times saved!') md.run() md.destroy() def done_timing(self, source): '''Handles click on the Done button - Gives two dialogs before closing.''' - if str(type(source)) == "": - oktime_dialog1 = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Are you sure you want to leave?') - oktime_dialog1.set_title('Really done?') - response1 = oktime_dialog1.run() - oktime_dialog1.destroy() - else: - # in case of delete_event the window closes regardless. - response1 = gtk.RESPONSE_YES - if response1 == gtk.RESPONSE_YES: - oktime_dialog2 = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, 'Do you want to save before finishing?\nUnsaved data will be lost.') - oktime_dialog2.set_title('Save?') - response2 = oktime_dialog2.run() - oktime_dialog2.destroy() - if response2 == gtk.RESPONSE_YES: - self.save_times(None) - self.hide() + Gives a dialog before closing.''' + oktime_dialog2 = MsgDialog(self, 'question', ['yes', 'no', 'cancel'], 'Save?', 'Do you want to save before finishing?\nUnsaved data will be lost.') + response2 = oktime_dialog2.run() + oktime_dialog2.destroy() + if response2 == Gtk.ResponseType.CANCEL: + return + elif response2 == Gtk.ResponseType.YES: + self.save_times(None) + self.hide() def update_racers(self, ID): '''Updates racers_reg and racers_in after arrival of user ID''' @@ -647,7 +688,7 @@ def record_time(self, jnk_unused): def new_blank_time(self): '''Record a new time''' - t = str(datetime.timedelta(milliseconds=int(1000*(time.time()-self.t0))))[:-5] + t = time_format(time.time()-self.t0) self.rawtimes['times'].insert(0, t) #we prepend to rawtimes, just as we prepend to timemodel if self.offset >= 0: # No IDs in the buffer, so just prepend it to the liststore. diff --git a/fstimer/gui/util_classes.py b/fstimer/gui/util_classes.py new file mode 100644 index 0000000..9b14a38 --- /dev/null +++ b/fstimer/gui/util_classes.py @@ -0,0 +1,123 @@ +#fsTimer - free, open source software for race timing. +#Copyright 2012-14 Ben Letham + +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation, either version 3 of the License, or +#(at your option) any later version. + +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +#GNU General Public License for more details. + +#You should have received a copy of the GNU General Public License +#along with this program. If not, see . + +#The author/copyright holder can be contacted at bletham@gmail.com +'''Convenience classes for the PyGTK -> PyGObject transition''' + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +import os + +icon_path = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../data/adwaita_icons')) + +icon_files = {'new': 'actions/document-new.png', + 'close': 'actions/window-close.png', + 'ok': 'emblems/emblem-default.png', + 'remove': 'actions/list-remove.png', + 'add': 'actions/list-add.png', + 'up': 'actions/go-up.png', + 'down': 'actions/go-down.png', + 'edit': 'apps/accessories-text-editor.png', + 'copy': 'actions/edit-copy.png', + 'back': 'actions/go-previous.png', + 'forward': 'actions/go-next.png', + 'open': 'status/folder-open.png', + 'clear': 'actions/edit-clear.png', + 'save': 'actions/document-save.png', + 'information': 'status/dialog-information.png', + 'error': 'status/dialog-error.png', + 'question': 'status/dialog-question.png', + 'warning': 'status/dialog-warning.png', + 'help': 'actions/help-faq.png', + 'about': 'actions/help-about.png', + } + +dialog_buttons = {'ok': ('ok', 'OK', Gtk.ResponseType.OK), + 'cancel': ('close', 'Cancel', Gtk.ResponseType.CANCEL), + 'yes': ('ok', 'Yes', Gtk.ResponseType.YES), + 'no': ('close', 'No', Gtk.ResponseType.NO), + } + + +class GtkStockButton(Gtk.Button): + + def __init__(self, icon_name, label_text): + #Init a regular Gtk.Button + Gtk.Button.__init__(self) + #Add the icon and the label. + fname = os.path.join(icon_path, icon_files[icon_name]) + btnIcon = Gtk.Image.new_from_file(fname) + btnLabel = Gtk.Label(label_text+' ') + #pack 'em into an HBox + btnHbox = Gtk.HBox(False, 0) + btnHbox.pack_start(btnIcon, False, False, 0) + btnHbox.pack_start(btnLabel, True, True, 0) + self.add(btnHbox) + return + + +class MsgDialog(Gtk.Dialog): + + def __init__(self, parent, icon_name, buttons, title, text): + super(MsgDialog, self).__init__(title, parent, Gtk.DialogFlags.MODAL) + btn_list = [] + for i, btn_name in enumerate(buttons): + icon, label, signal = dialog_buttons[btn_name] + btn = GtkStockButton(icon, label) + btn.connect('clicked', self.click_response, signal) + btn_list.append(btn) + self.set_border_width(5) + self.set_default_size(400, 50) + #Load in the icon + fname = os.path.join(icon_path, icon_files[icon_name]) + msg_icon = Gtk.Image.new_from_file(fname) + label = Gtk.Label(label=text) + #And pack + hbox = Gtk.HBox(False, 0) + hbox.pack_start(msg_icon, False, False,10) + hbox.pack_start(label, False, False,10) + vbox = Gtk.VBox(False, 0) + vbox.pack_start(hbox, True, True, 15) + # Pack in all of the buttons + hbox_btns = Gtk.HBox(False, 0) + align = Gtk.Alignment.new(1, 0, 1, 0) + hbox_btns.pack_start(align, True, True, 0) + for btn in btn_list: + hbox_btns.pack_start(btn, False, False, 5) + vbox.pack_start(hbox_btns, False, False, 0) + self.get_content_area().add(vbox) + self.show_all() + + def click_response(self, w, signal): + self.response(signal) + + +class MenuItemIcon(Gtk.MenuItem): + + def __init__(self, icon_name, text, cb, *args): + super(MenuItemIcon, self).__init__() + hbox = Gtk.HBox(False, 0) + fname = os.path.join(icon_path, icon_files[icon_name]) + msg_icon = Gtk.Image.new_from_file(fname) + hbox.pack_start(msg_icon, False, False, 0) + hbox.pack_start(Gtk.Label(label=text), False, False, 5) + hbox.pack_start(Gtk.Alignment.new(1, 0, 1, 0), True, True, 0) + self.add(hbox) + self.connect('activate', cb, *args) \ No newline at end of file diff --git a/fstimer/printcsv.py b/fstimer/printcsv.py index 8ecf0f2..3b65c74 100644 --- a/fstimer/printcsv.py +++ b/fstimer/printcsv.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #fsTimer - free, open source software for race timing. #Copyright 2012-14 Ben Letham @@ -43,7 +43,7 @@ def file_extension(self): def scratch_table_header(self): '''Returns the header of the printout for scratch results''' - return ','.join(self.fields) + '\n' + return 'Place,' + ','.join(self.fields) + '\n' def cat_table_header(self, category): '''Returns the header of the printout for results by category. @@ -51,54 +51,21 @@ def cat_table_header(self, category): @param category: name of the category handled by the table''' return category + '\n' + self.scratch_table_header() - def common_entry(self, bibid, timing_data, runner_data): + def common_entry(self, row): '''Returns the common part of the printout of the entry - of a given runner for scratch or by category results - @type bibid: string - @param bibid: the bibid of the runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - data = [str(timing_data), - runner_data['First name'] + ' '+ runner_data['Last name'], - bibid, - runner_data['Gender'], - str(runner_data['Age'])] - for field in self.fields[6:]: - data.append(runner_data[field]) - return ','.join(data) + '\n' + of a given runner for scratch or by category results''' + return ','.join(row) + '\n' - def scratch_entry(self, bibid, timing_data, runner_data): + def scratch_entry(self, row): '''Returns the printout of the entry of a given runner - in the scratch results - @type bibid: string - @param bibid: the bibid of the runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - result = str(self.place) + ',' + self.common_entry(bibid, timing_data, runner_data) + in the scratch results''' + result = str(self.place) + ',' + self.common_entry(row) self.place += 1 return result - def cat_entry(self, bibid, category, timing_data, runner_data): + def cat_entry(self, category, row): '''Returns the printout of the entry of a given runner - in the divisional results - @type bibid: string - @param bibid: the bibid of the runner - @type category: string - @param category: name of the category for this runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - result = str(self.cat_place[category]) + ',' + self.common_entry(bibid, timing_data, runner_data) + in the divisional results''' + result = str(self.cat_place[category]) + ',' + self.common_entry(row) self.cat_place[category] += 1 return result diff --git a/fstimer/printcsvlaps.py b/fstimer/printcsvlaps.py index 7a2eee7..0ee8870 100644 --- a/fstimer/printcsvlaps.py +++ b/fstimer/printcsvlaps.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #fsTimer - free, open source software for race timing. #Copyright 2012-14 Ben Letham @@ -34,30 +34,20 @@ def __init__(self, fields, categories): @param categories: existing categories''' super(CSVPrinterLaps, self).__init__(fields, categories) - def common_entry(self, bibid, timing_data, runner_data): + def common_entry(self, row): '''Returns the common part of the printout of the entry - of a given runner for scratch or by category results - @type bibid: string - @param bibid: the bibid of the runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - # first line, with total time and first lap - data = [str(timing_data[0]), - '1 - ' + str(timing_data[1]), - runner_data['First name'] + ' '+ runner_data['Last name'], - bibid, - runner_data['Gender'], - str(runner_data['Age'])] - for field in self.fields[7:]: - data.append(runner_data[field]) - entry = ','.join(data) + '\n' - # others lines, with other lap times - for i in range(2, len(timing_data)): - data = ['', '', str(i) + ' - ' + str(timing_data[i]), '', '', '', ''] - data.extend(['']*(len(self.fields)-7)) - entry += ','.join(data) + '\n' - return entry + of a given runner for scratch or by category results''' + # first line, as before + row_print = list(row) + if 'Lap Times' in self.fields: + idx_lap = self.fields.index('Lap Times') + lap_times = row[idx_lap] + row_print[idx_lap] = lap_times[0] + entry = ','.join(row_print)+'\n' + if 'Lap Times' in self.fields: + for i in range(1, len(lap_times)): + entry += ',' # for Place + row_print = ['' for j in range(len(row))] + row_print[idx_lap] = str(lap_times[i]) + entry += ','.join(row_print) + '\n' + return entry \ No newline at end of file diff --git a/fstimer/printer.py b/fstimer/printer.py index 40c8f2d..5d387d3 100644 --- a/fstimer/printer.py +++ b/fstimer/printer.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #fsTimer - free, open source software for race timing. #Copyright 2012-14 Ben Letham @@ -66,20 +66,12 @@ def cat_table_footer(self, category): @param category: name of the category handled by the table''' return '' - def scratch_entry(self, bibid, timing_data, runner_data): + def scratch_entry(self, row): '''Returns the printout of the entry of a given runner - in the scratch results - @type bibid: string - @param bibid: the bibid of the runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' + in the scratch results''' return '' - def cat_entry(self, bibid, category, timing_data, runner_data): + def cat_entry(self, row): '''Returns the printout of the entry of a given runner in the results by category @type bibid: string diff --git a/fstimer/printhtml.py b/fstimer/printhtml.py index 474972b..852aeb4 100644 --- a/fstimer/printhtml.py +++ b/fstimer/printhtml.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #fsTimer - free, open source software for race timing. #Copyright 2012-14 Ben Letham @@ -75,11 +75,12 @@ def header(self): def footer(self): '''Returns the footer of the printout''' - return '' + return '' def scratch_table_header(self): '''Returns the header of the printout for scratch results''' header = '\n' + header += '\n' for field in self.fields: header += '\n' header += '\n' @@ -102,54 +103,21 @@ def cat_table_footer(self, category): @param category: name of the category handled by the table''' return self.scratch_table_footer() - def common_entry(self, bibid, timing_data, runner_data): + def common_entry(self, row): '''Returns the common part of the printout of the entry - of a given runner for scratch or by category results - @type bibid: string - @param bibid: the bibid of the runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - data = [str(timing_data), - runner_data['First name'] + ' '+ runner_data['Last name'], - bibid, - runner_data['Gender'], - str(runner_data['Age'])] - for field in self.fields[6:]: - data.append(runner_data[field]) - return '\n' - - def scratch_entry(self, bibid, timing_data, runner_data): + of a given runner for scratch or by category results''' + return '\n' + + def scratch_entry(self, row): '''Returns the printout of the entry of a given runner - in the scratch results - @type bibid: string - @param bibid: the bibid of the runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - result = '\n' - # others lines, with other lap times - for i in range(2, len(timing_data)): - data = ['', '', str(i) + ' - ' + str(timing_data[i]), '', '', '', ''] - data.extend(['']*(len(self.fields)-7)) - entry += '\n' - return entry + @type bibid: string''' + # first line, as before + row_print = list(row) + if 'Lap Times' in self.fields: + idx_lap = self.fields.index('Lap Times') + lap_times = row[idx_lap] # Take from str back to list + row_print[idx_lap] = lap_times[0] + entry = '\n' + if 'Lap Times' in self.fields: + for i in range(1, len(lap_times)): + entry += '\n' + return entry \ No newline at end of file diff --git a/fstimer/timer.py b/fstimer/timer.py index 197c49b..d3817dd 100644 --- a/fstimer/timer.py +++ b/fstimer/timer.py @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #fsTimer - free, open source software for race timing. -#Copyright 2012-14 Ben Letham +#Copyright 2012-15 Ben Letham #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by @@ -20,16 +20,18 @@ '''Main class of the fsTimer package''' -import pygtk -pygtk.require('2.0') -import gtk +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk import os, json, csv, re, datetime +from os.path import normpath, join, dirname, abspath, basename import fstimer.gui.intro import fstimer.gui.newproject import fstimer.gui.projecttype import fstimer.gui.definefields -import fstimer.gui.definefamilyreset import fstimer.gui.definedivisions +import fstimer.gui.printfields +import fstimer.gui.definerankings import fstimer.gui.root import fstimer.gui.about import fstimer.gui.importprereg @@ -43,7 +45,9 @@ import fstimer.printcsvlaps import fstimer.printhtml import fstimer.printhtmllaps +from fstimer.gui.timing import time_diff, time_parse, time_format from collections import defaultdict +from fstimer.gui.util_classes import MsgDialog class PyTimer(object): @@ -56,13 +60,14 @@ def __init__(self): def load_project(self, jnk_unused, combobox, projectlist): '''Loads the registration settings of a project, and go back to rootwin''' - self.path = projectlist[combobox.get_active()] - with open(os.path.join(self.path, self.path+'.reg'), 'rb') as fin: + projectname = projectlist[combobox.get_active()] + self.path = normpath(join(dirname(dirname(abspath(__file__))), projectname)) + #self.path is now _absolute_, it is not project name. + with open(join(self.path, projectname + '.reg'), 'r', encoding='utf-8') as fin: regdata = json.load(fin) #Assign all of the project settings self.fields = regdata['fields'] self.fieldsdic = regdata['fieldsdic'] - self.clear_for_fam = regdata['clear_for_fam'] self.divisions = regdata['divisions'] try: self.projecttype = regdata['projecttype'] @@ -73,6 +78,23 @@ def load_project(self, jnk_unused, combobox, projectlist): self.numlaps = regdata['numlaps'] except KeyError: self.numlaps = 1 + try: + self.rankings = regdata['rankings'] + except KeyError: + # old project, with no rankings, rankings is thus default one + self.rankings = {'Overall': 'Time'} + for div in self.divisions: + self.rankings[div[0]] = 'Time' + try: + self.printfields = regdata['printfields'] + except KeyError: + # fill with default + self.printfields = {'Time': '{time}'} + for field in ['ID', 'Age', 'Gender']: + self.printfields[field] = '{' + field + '}' + if 'Name' not in self.fields: + self.printfields['Name'] = "{First name} + ' ' + {Last name}" + #Move on to the main window self.introwin.hide() self.rootwin = fstimer.gui.root.RootWin(self.path, @@ -80,28 +102,49 @@ def load_project(self, jnk_unused, combobox, projectlist): self.import_prereg, self.handle_preregistration, self.compreg_window, - self.gen_pretimewin) + self.gen_pretimewin, + self.define_divisions) def create_project(self, jnk_unused): + #And load the new project window + self.newprojectwin = fstimer.gui.newproject.NewProjectWin(self.set_projecttype, + self.introwin) + + def set_projecttype(self, projectname, projectlist, combobox): '''creates a new project''' self.project_types = ['standard', 'handicap'] #Options for project types - #First load in the default project settings - with open('fstimer/data/fstimer_default_project.reg', 'rb') as fin: + #First load in the project settings + indx = combobox.get_active() + if indx == 0: + # Default settings + fname = os.path.abspath( + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'data/fstimer_default_project.reg')) + else: + importname = projectlist[indx] + importpath = normpath(join(dirname(dirname(abspath(__file__))), importname)) + fname = os.path.join(importpath, importname+'.reg') + with open(fname, 'r', encoding='utf-8') as fin: regdata = json.load(fin) #Assign all of the project settings self.fields = regdata['fields'] self.fieldsdic = regdata['fieldsdic'] - self.clear_for_fam = regdata['clear_for_fam'] self.divisions = regdata['divisions'] self.projecttype = regdata['projecttype'] self.numlaps = regdata['numlaps'] - #And load the new project window - self.newprojectwin = fstimer.gui.newproject.NewProjectWin(self.set_projecttype, - self.introwin) - - def set_projecttype(self, path): - '''Handles setting project type''' - self.path = path + try: + self.rankings = regdata['rankings'] + except KeyError: + # old project, with no rankings, rankings is thus default one + self.rankings = {'Overall': 'Time'} + for div in self.divisions: + self.rankings[div[0]] = 'Time' + try: + self.printfields = regdata['printfields'] + except KeyError: + self.printfields = {} + self.path = normpath(join(dirname(dirname(abspath(__file__))), projectname)) self.projecttypewin = fstimer.gui.projecttype.ProjectTypeWin(self.project_types, self.projecttype, self.numlaps, @@ -113,7 +156,7 @@ def define_fields(self, jnk_unused, rbs, check_button, numlapsbtn): '''Handled the definition of fields when creating a new project''' self.projecttypewin.hide() #First take care of the race settings from the previous window - for b, btn in rbs.iteritems(): + for b, btn in rbs.items(): if btn.get_active(): self.projecttype = self.project_types[b] break @@ -126,11 +169,10 @@ def define_fields(self, jnk_unused, rbs, check_button, numlapsbtn): if 'Handicap' not in self.fields: self.fields.append('Handicap') self.fieldsdic['Handicap'] = {'type':'entrybox', 'max':20} - self.clear_for_fam.append('Handicap') #And now generate the window. self.definefieldswin = fstimer.gui.definefields.DefineFieldsWin \ (self.fields, self.fieldsdic, self.projecttype, self.back_to_projecttype, - self.define_family_reset, self.introwin) + self.define_divisions, self.introwin) def back_to_projecttype(self, jnk_unused): '''Goes back to new project window''' @@ -141,73 +183,182 @@ def back_to_new_project(self, jnk_unused): '''Goes back to new project window''' self.projecttypewin.hide() self.newprojectwin.show_all() - - def define_family_reset(self, jnk_unused): - '''Goes to family reset window''' - self.definefieldswin.hide() - self.familyresetwin = fstimer.gui.definefamilyreset.FamilyResetWin \ - (self.fields, self.clear_for_fam, self.back_to_define_fields, - self.define_divisions, self.introwin) - - def back_to_define_fields(self, jnk_unused): - '''Goes back to define fields window from the family reset one''' - self.familyresetwin.hide() - self.definefieldswin.show_all() - - def define_divisions(self, jnk_unused, btnlist): + + def define_divisions(self, jnk_unused, edit=False): '''Defines default divisions and launched the division edition window''' - self.clear_for_fam = [] - for (field, btn) in zip(self.fields, btnlist): - if btn.get_active(): - self.clear_for_fam.append(field) - self.familyresetwin.hide() + # First edit the current divisions to use only available fields + divs = [] + for div, fields_dict in self.divisions: + keep_div = True + for field in fields_dict: + if field not in self.fields: + keep_div = False + break + if keep_div: + divs.append([div, fields_dict]) + # replace + self.divisions = list(divs) + # continue + if edit: + parent_win = self.rootwin + else: + self.definefieldswin.hide() + parent_win = self.introwin self.divisionswin = fstimer.gui.definedivisions.DivisionsWin \ - (self.fields, self.fieldsdic, self.divisions, self.back_to_family_reset, self.store_new_project, self.introwin) + (self.fields, self.fieldsdic, self.divisions, self.back_to_fields, self.print_fields, parent_win, edit) - def back_to_family_reset(self, jnk_unused): + def back_to_fields(self, jnk_unused): '''Goes back to family reset window, from the division edition one''' self.divisionswin.hide() - self.familyresetwin.show_all() + self.definefieldswin.show_all() + + def print_fields(self, jnk_unused, edit): + '''Launch print fields window''' + # First filter the current self.printfields to only include ones that use fields + # from self.fields. + bad_fields = [] + for field, text in self.printfields.items(): + if not (field in self.fields or field in ['Time', 'Pace']): + vars_ = re.findall("\{[^}]+\}", text) + for var in vars_: + name = var[1:-1] + if not (name in self.fields or name == 'Time'): + bad_fields.append(field) + break + for field in bad_fields: + self.printfields.pop(field) + # Now launch the window + parent_win = self.rootwin if edit else self.introwin + self.divisionswin.hide() + self.printfieldswin = fstimer.gui.printfields.PrintFieldsWin( + self.fields, self.printfields, self.back_to_divisions, self.define_rankings, parent_win, edit) + + def back_to_divisions(self, jnk_unused, btnlist, btn_time, btn_pace, entry_pace, printfields_m): + '''Goes back to define fields window from the print fields''' + res = self.set_printfields(btnlist, btn_time, btn_pace, entry_pace, printfields_m) + if not res: + return + #else + self.printfieldswin.hide() + self.divisionswin.show_all() + + def set_printfields(self, btnlist, btn_time, btn_pace, entry_pace, printfields_m): + '''Update self.printfields''' + # First check it is valid. + if btn_pace.get_active(): + try: + d = float(entry_pace.get_text()) + except ValueError: + md = MsgDialog(self.printfieldswin, 'error', ['ok'], 'Error!', 'Distance must be a number.') + md.run() + md.destroy() + return False + # It will be valid. Let's continue. + self.printfields = {} # re-set + # Start with the registration fields + for field, btn in zip(self.fields, btnlist): + if btn.get_active(): + self.printfields[field] = '{' + field + '}' + # Then timing button and pace button + if btn_time.get_active(): + self.printfields['Time'] = '{Time}' + if btn_pace.get_active(): + self.printfields['Pace'] = '{Time}/' + entry_pace.get_text() + # Finally custom fields + for field in printfields_m: + if field not in self.fields and field not in ['Time', 'Pace']: + self.printfields[field] = str(printfields_m[field]) + if len(self.printfields) == 0: + md = MsgDialog(self.printfieldswin, 'error', ['ok'], 'Error!', 'Must include at least one field.') + md.run() + md.destroy() + return False + return True + + def define_rankings(self, jnk_unused, btnlist, btn_time, btn_pace, entry_pace, printfields_m, edit): + '''Goes to the define rankings window''' + # Store away the printfields information. + res = self.set_printfields(btnlist, btn_time, btn_pace, entry_pace, printfields_m) + if not res: + return + # else move on. + # Edit the current self.rankings to make sure its keys match the divisions in div. + old_divs = list(self.rankings.keys()) + old_divs.remove('Overall') + for div, descr in self.divisions: + if div not in self.rankings: + self.rankings[div] = self.rankings['Overall'] + else: + old_divs.remove(div) + # Get rid of any removed divs + for div in old_divs: + self.rankings.pop(div) + # Also change any rankings that are set to fields that no longer exist + # First for overall + if self.rankings['Overall'] not in self.printfields: + # Set to the first item by default then + self.rankings['Overall'] = list(self.printfields.keys())[0] + # Now check the others + for ranking in self.rankings: + if self.rankings[ranking] not in self.printfields: + self.rankings[ranking] = self.rankings['Overall'] + # Now we're ready. + parent_win = self.rootwin if edit else self.introwin + self.printfieldswin.hide() + self.rankingswin = fstimer.gui.definerankings.RankingsWin( + self.rankings, self.divisions, self.printfields, self.back_to_printfields, self.store_new_project, parent_win, edit) + + def back_to_printfields(self, jnk_unused): + '''Goes back to define fields window from the family reset one''' + self.rankingswin.hide() + self.printfieldswin.show_all() - def store_new_project(self, jnk_unused): + def store_new_project(self, jnk_unused, edit): '''Stores a new project to file and goes to root window''' - os.system('mkdir '+self.path) + if not edit: + os.system('mkdir '+ self.path) regdata = {} regdata['projecttype'] = self.projecttype regdata['numlaps'] = self.numlaps regdata['fields'] = self.fields regdata['fieldsdic'] = self.fieldsdic - regdata['clear_for_fam'] = self.clear_for_fam + regdata['printfields'] = self.printfields regdata['divisions'] = self.divisions - with open(os.path.join(self.path, self.path+'.reg'), 'wb') as fout: + regdata['rankings'] = self.rankings + with open(join(self.path, basename(self.path)+'.reg'), 'w', encoding='utf-8') as fout: json.dump(regdata, fout) - md = gtk.MessageDialog(self.divisionswin, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, 'Project '+self.path+' successfully created!') + if edit: + md = MsgDialog(self.rankingswin, 'information', ['ok'], 'Edited!', 'Project '+basename(self.path)+' successfully edited!') + else: + md = MsgDialog(self.rankingswin, 'information', ['ok'], 'Created!', 'Project '+basename(self.path)+' successfully created!') md.run() md.destroy() - self.divisionswin.hide() - self.introwin.hide() - self.rootwin = fstimer.gui.root.RootWin(self.path, - self.show_about, - self.import_prereg, - self.handle_preregistration, - self.compreg_window, - self.gen_pretimewin) - - def show_about(self, jnk_unused): + self.rankingswin.hide() + if not edit: + self.introwin.hide() + self.rootwin = fstimer.gui.root.RootWin(self.path, + self.show_about, + self.import_prereg, + self.handle_preregistration, + self.compreg_window, + self.gen_pretimewin, + self.define_divisions) + + def show_about(self, jnk_unused, parent): '''Displays the about window''' - fstimer.gui.about.AboutWin() + fstimer.gui.about.AboutWin(parent) def import_prereg(self, jnk_unused): '''import pre-registration from a csv''' - self.importpreregwin = fstimer.gui.importprereg.ImportPreRegWin(os.getcwd(), self.path, self.fields, self.fieldsdic) + self.importpreregwin = fstimer.gui.importprereg.ImportPreRegWin(self.path, self.fields, self.fieldsdic) def handle_preregistration(self, jnk_unused): '''handles preregistration''' - self.preregistrationwin = fstimer.gui.preregister.PreRegistrationWin(os.getcwd(), self.path, self.set_registration_file, self.handle_registration) + self.preregistrationwin = fstimer.gui.preregister.PreRegistrationWin(self.path, self.set_registration_file, self.handle_registration) def set_registration_file(self, filename): '''set a preregistration file''' - with open(filename, 'rb') as fin: + with open(filename, 'r', encoding='utf-8') as fin: self.prereg = json.load(fin) def handle_registration(self, regid): @@ -216,14 +367,14 @@ def handle_registration(self, regid): self.regid = regid if not hasattr(self, 'prereg'): self.prereg = [] #No pre-registration was selected - self.registrationwin = fstimer.gui.register.RegistrationWin(self.path, self.fields, self.fieldsdic, self.prereg, self.clear_for_fam, self.projecttype, self.save_registration) + self.registrationwin = fstimer.gui.register.RegistrationWin(self.path, self.fields, self.fieldsdic, self.prereg, self.projecttype, self.save_registration) def save_registration(self): '''saves registration''' - filename = os.path.join(self.path, self.path+'_registration_'+str(self.regid)+'.json') - with open(filename, 'wb') as fout: + filename = os.path.join(self.path, basename(self.path)+'_registration_'+str(self.regid)+'.json') + with open(filename, 'w', encoding='utf-8') as fout: json.dump(self.prereg, fout) - return filename + return filename, True def compreg_window(self, jnk_unused): '''Merges registration files and create the timing dictionary.''' @@ -244,11 +395,12 @@ def merge_compreg(self, regfilelist): # each registration is a list of dictionaries self.regmerge = [] for fname in regfilelist: - with open(fname, 'rb') as fin: + with open(fname, 'r', encoding='utf-8') as fin: reglist = json.load(fin) self.regmerge.extend(reglist) # Now remove trivial dups - self.reg_nodups0 = [dict(tupleized) for tupleized in set(tuple(item.items()) for item in self.regmerge)] + self.reg_nodups0 = [dict(tupleized) for tupleized in set( + tuple((field, item[field]) for field in self.fields) for item in self.regmerge)] # Get rid of entries that differ only by the ID. That is, items that were in the pre-reg and had no changes except an ID was assigned in one reg file. # we'll do this in O(n^2) time:-( self.reg_nodups = [] @@ -257,15 +409,16 @@ def merge_compreg(self, regfilelist): self.reg_nodups.append(reg) else: # make sure there isn't an entry with everything else the same, but an ID - dupcheck = 0 + dupcheck = False for i in range(len(self.reg_nodups0)): dicttmp = self.reg_nodups0[i].copy() if dicttmp['ID']: #lets make sure we aren't a dup of this one dicttmp['ID'] = '' if reg == dicttmp: - dupcheck = 1 - if dupcheck == 0: + dupcheck = True + break + if not dupcheck: self.reg_nodups.append(reg) # Now form the Timing dictionary, and check for errors. self.compilewin.setLabel(1, 'Checking for errors...') @@ -301,16 +454,16 @@ def compreg_noerrors(self, errs=False): else: self.compilewin.setLabel(1, 'Checking for errors... no errors found!') #Now save things - with open(os.path.join(self.path, self.path+'_registration_compiled.json'), 'wb') as fout: + with open(join(self.path, basename(self.path)+'_registration_compiled.json'), 'w', encoding='utf-8') as fout: json.dump(self.reg_nodups, fout) - with open(os.path.join(self.path, self.path+'_timing_dict.json'), 'wb') as fout: + with open(join(self.path, basename(self.path)+'_timing_dict.json'), 'w', encoding='utf-8') as fout: json.dump(self.timedict, fout) - regfn = os.path.join(self.path, self.path + '_registration_compiled.json') - timefn = os.path.join(self.path, self.path + '_timing_dict.json') + regfn = join(self.path, basename(self.path) + '_registration_compiled.json') + timefn = join(self.path, basename(self.path) + '_timing_dict.json') self.compilewin.setLabel(2, 'Successfully wrote files:\n' + \ regfn + '\n' + timefn + '') #And write the compiled registration to csv - with open(os.path.join(self.path, self.path+'_registration.csv'), 'wb') as fout: + with open(join(self.path, basename(self.path)+'_registration.csv'), 'w', encoding='utf-8') as fout: dict_writer = csv.DictWriter(fout, self.fields) dict_writer.writer.writerow(self.fields) dict_writer.writerows(self.reg_nodups) @@ -329,10 +482,23 @@ def gen_timewin(self, passid, timebtn): # We will store 'raw' data, lists of times and IDs. self.rawtimes = {'times':[], 'ids':[]} # create Timing window - self.timewin = fstimer.gui.timing.TimingWin(self.path, self.rootwin, timebtn, self.rawtimes, self.timing, self.print_times, self.projecttype, self.numlaps) + self.timewin = fstimer.gui.timing.TimingWin(self.path, self.rootwin, timebtn, self.rawtimes, self.timing, self.print_times, self.projecttype, self.numlaps, self.fields, self.fieldsdic, self.write_updated_timing) + + def write_updated_timing(self, reg, timedict): + filename = os.path.join(self.path, os.path.basename(self.path)+'_registration_compiled.json') + with open(filename, 'w', encoding='utf-8') as fout: + json.dump(reg, fout) + with open(join(self.path, basename(self.path)+'_timing_dict.json'), 'w', encoding='utf-8') as fout: + json.dump(timedict, fout) + with open(join(self.path, basename(self.path)+'_registration.csv'), 'w', encoding='utf-8') as fout: + dict_writer = csv.DictWriter(fout, self.fields) + dict_writer.writer.writerow(self.fields) + dict_writer.writerows(reg) + self.timing = timedict + return filename def print_times(self, jnk_unused, use_csv): - '''print times to a file''' + '''print times to files''' # choose the right Printer Class if use_csv: if self.numlaps > 1: @@ -344,57 +510,91 @@ def print_times(self, jnk_unused, use_csv): printer_class = fstimer.printhtmllaps.HTMLPrinterLaps else: printer_class = fstimer.printhtml.HTMLPrinter - # Figure out what the columns will be - other_fields = set([field for div in self.divisions for field in div[1] - if field not in ['Age', 'Gender']]) - fields = ['Place', 'Time'] - if self.numlaps > 1: - fields.append('Lap Times') - fields.extend(['Name', 'Bib ID', 'Gender', 'Age']) - fields.extend(list(other_fields)) + # Figure out what the columns will be. + cols = [] + # Prefer first time, and then pace, if they are in the printfields + for field in ['Time', 'Pace']: + if field in self.printfields: + cols.append(field) + if self.numlaps > 1 and 'Time' in self.printfields: + cols.append('Lap Times') + # Then add in the calculated fields + for field in self.printfields: + if not field in ['Time', 'Pace'] and not field in self.fields: + cols.append(field) # A computed field + # Finally registration fields + for field in self.fields: + if field in self.printfields: + cols.append(field) + # Prepare functions for computing each column + col_fns = [] + for col in cols: + if col == 'Lap Times': + text = 'lap_time' + else: + text = self.printfields[col] + # Sub {Time} + text = text.replace('{Time}', 'time_parse(time).total_seconds()') + # Age + text = text.replace('{Age}', "int(userdata['Age'])") + # ID + text = text.replace('{ID}', "tag") + # And the other registration fields + for field in self.fields: + if field not in ['Age', 'ID']: + text = text.replace('{' + field + '}', "userdata['{}']".format(field)) + col_fns.append(text) + # Get the list of different things we will rank by + ranking_keys = set(self.rankings.values()) # instantiate the printer - printer = printer_class(fields, [div[0] for div in self.divisions]) - # first build all results into strings + printer = printer_class(cols, [div[0] for div in self.divisions]) + # Build the results scratchresults = printer.scratch_table_header() divresults = {div[0]:'\n'+printer.cat_table_header(div[0]) for div in self.divisions} - for (tag, time) in self.get_sorted_results(): - scratchresults += printer.scratch_entry(tag, time, self.timing[tag]) - mydivs = self.get_division(self.timing[tag]) - for div in mydivs: - divresults[div] += printer.cat_entry(tag, div, time, self.timing[tag]) + # Do the ranking for each ranking key + ranked_results = {} + for ranking_key in ranking_keys: + rank_indx = cols.index(ranking_key) + ranked_results = self.get_sorted_results(rank_indx, cols, col_fns) + for tag, row in ranked_results: + # Add this to the appropriate results + if self.rankings['Overall'] == ranking_key: + scratchresults += printer.scratch_entry(row) + mydivs = self.get_divisions(tag) + for div in mydivs: + if self.rankings[div] == ranking_key: + divresults[div] += printer.cat_entry(div, row) scratchresults += printer.scratch_table_footer() for div in divresults: divresults[div] += printer.cat_table_footer(div) # now save to files scratch_file = os.path.join(self.path, - '_'.join([self.path, - self.timewin.timestr, - 'alltimes.' + printer.file_extension()])) + '_'.join([basename(self.path), + self.timewin.timestr, + 'alltimes.' + printer.file_extension()])) with open(scratch_file, 'w') as scratch_out: scratch_out.write(printer.header()) scratch_out.write(scratchresults) scratch_out.write(printer.footer()) div_file = os.path.join(self.path, - '_'.join([self.path, - self.timewin.timestr, - 'divtimes.' + printer.file_extension()])) + '_'.join([basename(self.path), + self.timewin.timestr, + 'divtimes.' + printer.file_extension()])) with open(div_file, 'w') as div_out: div_out.write(printer.header()) for div in self.divisions: div_out.write(divresults[div[0]]) div_out.write(printer.footer()) # display user dialog that all was successful - md = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, - gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, - "Results saved to " + printer.file_extension() + "!") + md = MsgDialog(self.timewin, 'information', ['ok'], 'Success!', "Results saved to " + printer.file_extension() + "!") md.run() md.destroy() - def get_division(self, timingEntry): + def get_divisions(self, tag): '''Get the divisions for a given timing entry''' try: - age = int(timingEntry['Age']) + age = int(self.timing[tag]['Age']) except ValueError: age = '' mydivs = [] @@ -406,7 +606,7 @@ def get_division(self, timingEntry): if not age or age < div[1]['Age'][0] or age > div[1]['Age'][1]: break else: - if timingEntry[field] != div[1][field]: + if self.timing[tag][field] != div[1][field]: break else: mydivs.append(div[0]) @@ -429,7 +629,7 @@ def get_sync_times_and_ids(self): adj_times = list(self.rawtimes['times']) return adj_ids, adj_times - def get_sorted_results(self): + def get_sorted_results(self, rank_indx, cols, col_fns): '''returns a sorted list of (id, result) items. The content of result depends on the race type''' # get raw times @@ -441,37 +641,103 @@ def get_sorted_results(self): for tag, time in timeslist: if tag and time and tag != self.passid: try: - new_timeslist.append((tag, str(fstimer.gui.timing.time_parse(time) - fstimer.gui.timing.time_parse(self.timing[tag]['Handicap']))[:-5])) + new_timeslist.append((tag, time_diff(time,self.timing[tag]['Handicap']))) except AttributeError: - #Either time or Handicap couldn't be converted to timedelta. It will be dropped. - pass + #Either time or Handicap couldn't be converted to timedelta. + new_timeslist.append((tag, '_')) #else: We just drop entries with blank tag, blank time, or the pass ID timeslist = list(new_timeslist) #replace else: #Drop times that are blank or have the passid timeslist = [(tag, time) for tag, time in timeslist if tag and time and tag != self.passid] - # sort by time - timeslist = sorted(timeslist, key=lambda entry: entry[1]) - # single lap case - if self.numlaps == 1: - return timeslist - else: + # Compute lap times, if a lap race + if self.numlaps > 1: # multi laps - groups times by tag # Each value of laptimesdic is a list, sorted in order from # fastest time (1st lap) to longest time (last lap). laptimesdic = defaultdict(list) - for (tag, time) in timeslist: + for (tag, time) in sorted(timeslist, key=lambda x:x[1]): laptimesdic[tag].append(time) # compute the lap times. - laptimesdic2 = defaultdict(list) + lap_times = {} + total_times = {} for tag in laptimesdic: # First put the total race time if len(laptimesdic[tag]) == self.numlaps: - laptimesdic2[tag] = [laptimesdic[tag][-1]] + total_times[tag] = laptimesdic[tag][-1] else: - laptimesdic2[tag] = ['<>'] - # And now the first lap - laptimesdic2[tag].append(laptimesdic[tag][0]) + total_times[tag] = '_' + # And the first lap + lap_times[tag] = ['1 - ' + laptimesdic[tag][0]] # And now the subsequent laps - laptimesdic2[tag].extend([str(fstimer.gui.timing.time_parse(laptimesdic[tag][ii+1]) - fstimer.gui.timing.time_parse(laptimesdic[tag][ii]))[:-5] for ii in range(len(laptimesdic[tag])-1)]) - return sorted(laptimesdic2.items(), key=lambda entry: entry[1][0]) + for ii in range(len(laptimesdic[tag])-1): + try: + lap_times[tag].append(str(ii+2) + ' - ' + time_diff(laptimesdic[tag][ii+1],laptimesdic[tag][ii])) + except AttributeError: + lap_times[tag].append(str(ii+2) + ' - _') + # Now correct timeslist to have the new total times + timeslist = list(total_times.items()) + else: + lap_times = defaultdict(int) + # Compute each results row + result_rows = [] + for tag, time in timeslist: + row = [] + lap_time = lap_times[tag] + userdata = self.timing[tag] + for i, col_fn in enumerate(col_fns): + try: + row.append((eval(col_fn))) + except (SyntaxError, TypeError, AttributeError, ValueError): + if cols[i] == 'ID': + row.append(tag) + else: + row.append(None) + result_rows.append((tag, row)) + # sort by column rank_indx. + # Try sorting as float, but if that doesn't work, use string. + try: + # Define a sorter that will handle the Nones + def floatsort(x): + if x[1][rank_indx] is None: + return 1e20 + else: + return x[1][rank_indx] + result_rows = sorted(result_rows, key=floatsort) + except TypeError: + def stringsort(x): + if x[1][rank_indx] is None: + return '' + else: + return x[1][rank_indx] + result_rows = sorted(result_rows, key=stringsort) + # remove duplicate entries: If a tag has multiple entries, keep only the most highly ranked. + # Also replace total times and pace times with formatted times, stringify everything but Lap Times, + # and replace Nones. + indx_format_time = [] + for field in ['Time', 'Pace']: + if field in cols: + indx_format_time.append(cols.index(field)) + taglist = set() + result_rows_dedup = [] + for tag, row in result_rows: + if tag in taglist: + pass # drop it + else: + taglist.add(tag) + row_new = [] + for i, val in enumerate(row): + if val is None: + if i == rank_indx: + val = '_' + else: + val = '' + elif cols[i] in ['Time', 'Pace']: + val = time_format(val) + elif cols[i] == 'Lap Times': + pass # Leave it as is + else: + val = str(val) + row_new.append(val) + result_rows_dedup.append((tag, row_new)) + return result_rows_dedup diff --git a/fstimer_demo/fstimer_demo.reg b/fstimer_demo/fstimer_demo.reg index 157fc1e..df03a6c 100644 --- a/fstimer_demo/fstimer_demo.reg +++ b/fstimer_demo/fstimer_demo.reg @@ -1 +1 @@ -{"fields": ["Last name", "First name", "ID", "Age", "Gender", "Address", "Email", "Telephone", "Contact for future races", "How did you hear about race"], "clear_for_fam": ["First name", "ID", "Age", "Gender"], "fieldsdic": {"How did you hear about race": {"max": 40, "type": "entrybox"}, "Last name": {"max": 30, "type": "entrybox"}, "Gender": {"type": "combobox", "options": ["male", "female"]}, "Age": {"max": 3, "type": "entrybox"}, "Telephone": {"max": 20, "type": "entrybox"}, "Email": {"max": 40, "type": "entrybox"}, "First name": {"max": 30, "type": "entrybox"}, "Address": {"max": 90, "type": "entrybox"}, "Contact for future races": {"type": "combobox", "options": ["yes", "no"]}, "ID": {"max": 6, "type": "entrybox"}}, "divisions": [["Female, ages 10 and under", {"Gender": "female", "Age": [0, 10]}], ["Male, ages 10 and under", {"Gender": "male", "Age": [0, 10]}], ["Female, ages 10-14", {"Gender": "female", "Age": [10, 14]}], ["Male, ages 10-14", {"Gender": "male", "Age": [10, 14]}], ["Female, ages 15-19", {"Gender": "female", "Age": [15, 19]}], ["Male, ages 15-19", {"Gender": "male", "Age": [15, 19]}], ["Female, ages 20-24", {"Gender": "female", "Age": [20, 24]}], ["Male, ages 20-24", {"Gender": "male", "Age": [20, 24]}], ["Female, ages 25-29", {"Gender": "female", "Age": [25, 29]}], ["Male, ages 25-29", {"Gender": "male", "Age": [25, 29]}], ["Female, ages 30-34", {"Gender": "female", "Age": [30, 34]}], ["Male, ages 30-34", {"Gender": "male", "Age": [30, 34]}], ["Female, ages 35-39", {"Gender": "female", "Age": [35, 39]}], ["Male, ages 35-39", {"Gender": "male", "Age": [35, 39]}], ["Female, ages 40-44", {"Gender": "female", "Age": [40, 44]}], ["Male, ages 40-44", {"Gender": "male", "Age": [40, 44]}], ["Female, ages 45-49", {"Gender": "female", "Age": [45, 49]}], ["Male, ages 45-49", {"Gender": "male", "Age": [45, 49]}], ["Female, ages 50-54", {"Gender": "female", "Age": [50, 54]}], ["Male, ages 50-54", {"Gender": "male", "Age": [50, 54]}], ["Female, ages 55-59", {"Gender": "female", "Age": [55, 59]}], ["Male, ages 55-59", {"Gender": "male", "Age": [55, 59]}], ["Female, ages 60-64", {"Gender": "female", "Age": [60, 64]}], ["Male, ages 60-64", {"Gender": "male", "Age": [60, 64]}], ["Female, ages 65-69", {"Gender": "female", "Age": [65, 69]}], ["Male, ages 65-69", {"Gender": "male", "Age": [65, 69]}], ["Female, ages 70-74", {"Gender": "female", "Age": [70, 74]}], ["Male, ages 70-74", {"Gender": "male", "Age": [70, 74]}], ["Female, ages 80 and up", {"Gender": "female", "Age": [80, 120]}], ["Male, ages 80 and up", {"Gender": "male", "Age": [80, 120]}]], "projecttype": "standard", "numlaps": 1} \ No newline at end of file +{"projecttype": "standard", "fieldsdic": {"Last name": {"type": "entrybox", "max": 30}, "ID": {"type": "entrybox", "max": 6}, "First name": {"type": "entrybox", "max": 30}, "Age": {"type": "entrybox", "max": 3}, "Gender": {"options": ["male", "female"], "type": "combobox"}, "Email": {"type": "entrybox", "max": 40}}, "rankings": {"Female, ages 20-49": "Time", "Male, ages 19 and under": "Time", "Female, ages 19 and under": "Time", "All males": "Time", "Overall": "Time", "Male, ages 20-49": "Time", "50 and up": "Time", "All females": "Time"}, "numlaps": 1, "divisions": [["All females", {"Gender": "female"}], ["All males", {"Gender": "male"}], ["Female, ages 19 and under", {"Age": [0, 19], "Gender": "female"}], ["Male, ages 19 and under", {"Age": [0, 19], "Gender": "male"}], ["Female, ages 20-49", {"Age": [20, 49], "Gender": "female"}], ["Male, ages 20-49", {"Age": [20, 49], "Gender": "male"}], ["50 and up", {"Age": [50, 120]}]], "fields": ["Last name", "First name", "ID", "Age", "Gender", "Email"], "printfields": {"ID": "{ID}", "Name": "{First name} + ' ' + {Last name}", "Age": "{Age}", "Gender": "{Gender}", "Time": "{Time}"}} \ No newline at end of file diff --git a/fstimer_demo/fstimer_demo_registration_prereg.json b/fstimer_demo/fstimer_demo_registration_prereg.json index ff0707e..6a22a89 100644 --- a/fstimer_demo/fstimer_demo_registration_prereg.json +++ b/fstimer_demo/fstimer_demo_registration_prereg.json @@ -1 +1 @@ -[{"How did you hear about race": "", "Last name": "Baker", "Gender": "female", "Age": "75", "Telephone": "606-5803", "ID": "", "First name": "Kelsey", "Address": "207 Chickpeas Ln. Weymouth MA", "Contact for future races": "no", "Email": "whooshes@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Talley", "Gender": "male", "Age": "81", "Telephone": "706-3076", "ID": "", "First name": "Kent", "Address": "", "Contact for future races": "no", "Email": "repository@gmail.com"}, {"How did you hear about race": "", "Last name": "Parker", "Gender": "female", "Age": "32", "Telephone": "655-3383", "ID": "", "First name": "Tiffany", "Address": "", "Contact for future races": "yes", "Email": "wholesalers@gmail.com"}, {"How did you hear about race": "school", "Last name": "Bender", "Gender": "male", "Age": "16", "Telephone": "652-5998", "ID": "", "First name": "Matthew", "Address": "75 Sleepiest Way Braintree MA", "Contact for future races": "yes", "Email": "parmesans@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Richmond", "Gender": "male", "Age": "85", "Telephone": "942-1850", "ID": "", "First name": "Mathew", "Address": "565 Mnemonics Way Weymouth MA", "Contact for future races": "yes", "Email": "superintending@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Harper", "Gender": "female", "Age": "20", "Telephone": "814-4767", "ID": "", "First name": "Opal", "Address": "424 Imus Ln. Braintree MA", "Contact for future races": "no", "Email": "recursively@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Long", "Gender": "male", "Age": "42", "Telephone": "218-1353", "ID": "", "First name": "Jeremy", "Address": "805 Modestys Pkwy. Hanover MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Gillespie", "Gender": "female", "Age": "82", "Telephone": "729-8951", "ID": "", "First name": "Marta", "Address": "406 Downstairs Way Braintree MA", "Contact for future races": "yes", "Email": "musicians@hotmail.com"}, {"How did you hear about race": "", "Last name": "Cooper", "Gender": "female", "Age": "56", "Telephone": "174-3543", "ID": "", "First name": "Beatriz", "Address": "133 Riffs St. Quincy MA", "Contact for future races": "no", "Email": "reapply@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Vincent", "Gender": "male", "Age": "27", "Telephone": "631-5961", "ID": "", "First name": "", "Address": "70 Napkins St. Weymouth MA", "Contact for future races": "no", "Email": "veterinaries@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Dodson", "Gender": "male", "Age": "47", "Telephone": "501-6323", "ID": "", "First name": "Sidney", "Address": "671 Theoretically St. Weymouth MA", "Contact for future races": "no", "Email": "poised@gmail.com"}, {"How did you hear about race": "church", "Last name": "Deleon", "Gender": "female", "Age": "3", "Telephone": "540-6532", "ID": "", "First name": "Beulah", "Address": "276 Hearings Dr. Weymouth MA", "Contact for future races": "no", "Email": "crick@gmail.com"}, {"How did you hear about race": "", "Last name": "Mueller", "Gender": "male", "Age": "62", "Telephone": "787-4487", "ID": "", "First name": "Felix", "Address": "174 Rhododendrons St. Norwell MA", "Contact for future races": "yes", "Email": "dicker@gmail.com"}, {"How did you hear about race": "", "Last name": "Mendez", "Gender": "male", "Age": "86", "Telephone": "923-6096", "ID": "", "First name": "Brian", "Address": "322 Comfort Way Norwell MA", "Contact for future races": "yes", "Email": "remoteness@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mccullough", "Gender": "female", "Age": "24", "Telephone": "902-7407", "ID": "", "First name": "Jeannette", "Address": "768 Monkeying Ln. Hingham MA", "Contact for future races": "yes", "Email": "samovars@gmail.com"}, {"How did you hear about race": "school", "Last name": "Gilbert", "Gender": "male", "Age": "27", "Telephone": "955-4133", "ID": "", "First name": "Milton", "Address": "951 Manchesters St. Hingham MA", "Contact for future races": "yes", "Email": "sleek@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Ellison", "Gender": "female", "Age": "49", "Telephone": "618-8692", "ID": "", "First name": "Abby", "Address": "485 Gazebo Way Hingham MA", "Contact for future races": "yes", "Email": "maos@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Kelly", "Gender": "female", "Age": "96", "Telephone": "188-5883", "ID": "", "First name": "Lynn", "Address": "568 Twinned Dr. Norwell MA", "Contact for future races": "yes", "Email": "footlockers@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Herring", "Gender": "male", "Age": "88", "Telephone": "194-6545", "ID": "", "First name": "Daryl", "Address": "287 Blondie Pkwy. Braintree MA", "Contact for future races": "yes", "Email": "loosenesss@gmail.com"}, {"How did you hear about race": "", "Last name": "Holt", "Gender": "male", "Age": "76", "Telephone": "955-4392", "ID": "", "First name": "Kelly", "Address": "958 Ybs Pkwy. Norwell MA", "Contact for future races": "no", "Email": "hogsheads@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Le", "Gender": "male", "Age": "20", "Telephone": "131-4933", "ID": "", "First name": "Chester", "Address": "942 Telephone Ln. Norwell MA", "Contact for future races": "no", "Email": "moonshots@gmail.com"}, {"How did you hear about race": "", "Last name": "Stuart", "Gender": "female", "Age": "15", "Telephone": "738-4543", "ID": "", "First name": "Delia", "Address": "510 Overtaken St. Hingham MA", "Contact for future races": "yes", "Email": "stalemates@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Watson", "Gender": "male", "Age": "73", "Telephone": "397-1738", "ID": "", "First name": "Herman", "Address": "582 Submissions Way Weymouth MA", "Contact for future races": "yes", "Email": "soothsayers@hotmail.com"}, {"How did you hear about race": "", "Last name": "Farley", "Gender": "male", "Age": "75", "Telephone": "585-5612", "ID": "", "First name": "Daniel", "Address": "17 Admit St. Hanover MA", "Contact for future races": "no", "Email": "heisted@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Mitchell", "Gender": "male", "Age": "65", "Telephone": "348-5830", "ID": "", "First name": "Douglas", "Address": "934 Yakima Ln. Weymouth MA", "Contact for future races": "yes", "Email": "confessions@hotmail.com"}, {"How did you hear about race": "", "Last name": "Long", "Gender": "female", "Age": "74", "Telephone": "645-4132", "ID": "", "First name": "Gwendolyn", "Address": "171 Polygamys Pkwy. Norwell MA", "Contact for future races": "no", "Email": "overseers@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Compton", "Gender": "female", "Age": "46", "Telephone": "345-8884", "ID": "", "First name": "Sondra", "Address": "322 Valparaiso Ln. Hingham MA", "Contact for future races": "no", "Email": "enigma@hotmail.com"}, {"How did you hear about race": "", "Last name": "Chang", "Gender": "female", "Age": "12", "Telephone": "182-7644", "ID": "", "First name": "Gwen", "Address": "421 Andreas St. Braintree MA", "Contact for future races": "yes", "Email": "acquirable@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Stevenson", "Gender": "male", "Age": "82", "Telephone": "709-5370", "ID": "", "First name": "Dwayne", "Address": "667 Haggling Dr. Hingham MA", "Contact for future races": "no", "Email": "microfiche@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Dyer", "Gender": "male", "Age": "22", "Telephone": "893-3344", "ID": "", "First name": "Nicholas", "Address": "547 Abstrusenesss St. Quincy MA", "Contact for future races": "no", "Email": "ingrates@hotmail.com"}, {"How did you hear about race": "", "Last name": "Schroeder", "Gender": "male", "Age": "53", "Telephone": "", "ID": "", "First name": "Lawrence", "Address": "816 Fragrances St. Norwell MA", "Contact for future races": "yes", "Email": "grinders@gmail.com"}, {"How did you hear about race": "", "Last name": "Gibbs", "Gender": "male", "Age": "90", "Telephone": "741-1051", "ID": "", "First name": "Edward", "Address": "143 Belgiums St. Norwell MA", "Contact for future races": "yes", "Email": "indictment@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Johns", "Gender": "male", "Age": "57", "Telephone": "486-3339", "ID": "", "First name": "Juan", "Address": "638 Cooping Ln. Weymouth MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "a friend", "Last name": "Mcconnell", "Gender": "female", "Age": "49", "Telephone": "695-4871", "ID": "", "First name": "Olga", "Address": "348 Gossiped Way Weymouth MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "a friend", "Last name": "Clark", "Gender": "male", "Age": "0", "Telephone": "", "ID": "", "First name": "Freddie", "Address": "", "Contact for future races": "yes", "Email": "obduracys@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Knapp", "Gender": "female", "Age": "87", "Telephone": "461-4215", "ID": "", "First name": "Ronda", "Address": "", "Contact for future races": "yes", "Email": "bizet@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Middleton", "Gender": "female", "Age": "98", "Telephone": "945-7343", "ID": "", "First name": "Edwina", "Address": "776 Burner Dr. Braintree MA", "Contact for future races": "no", "Email": "donated@gmail.com"}, {"How did you hear about race": "school", "Last name": "Bowen", "Gender": "female", "Age": "42", "Telephone": "716-7912", "ID": "", "First name": "Beverly", "Address": "81 Roosts St. Hingham MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "came last year", "Last name": "Bentley", "Gender": "female", "Age": "76", "Telephone": "155-7951", "ID": "", "First name": "Anna", "Address": "681 Hear Way Norwell MA", "Contact for future races": "yes", "Email": "handbags@gmail.com"}, {"How did you hear about race": "school", "Last name": "Hunt", "Gender": "female", "Age": "71", "Telephone": "163-9110", "ID": "", "First name": "Mamie", "Address": "994 Insolvencys St. Weymouth MA", "Contact for future races": "yes", "Email": "phonying@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Benjamin", "Gender": "female", "Age": "37", "Telephone": "743-3024", "ID": "", "First name": "Nannie", "Address": "512 Evian St. Hingham MA", "Contact for future races": "", "Email": "synced@gmail.com"}, {"How did you hear about race": "church", "Last name": "", "Gender": "female", "Age": "14", "Telephone": "967-7274", "ID": "", "First name": "Dianne", "Address": "816 Nonscheduled Way Quincy MA", "Contact for future races": "yes", "Email": "replies@hotmail.com"}, {"How did you hear about race": "", "Last name": "Gill", "Gender": "female", "Age": "43", "Telephone": "113-2052", "ID": "", "First name": "Greta", "Address": "701 Weakfish Dr. Braintree MA", "Contact for future races": "yes", "Email": "cheetos@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Ingram", "Gender": "male", "Age": "27", "Telephone": "566-5723", "ID": "", "First name": "Elmer", "Address": "509 Dalliances Ln. Hanover MA", "Contact for future races": "yes", "Email": "hearses@gmail.com"}, {"How did you hear about race": "church", "Last name": "Boyer", "Gender": "female", "Age": "54", "Telephone": "545-9627", "ID": "", "First name": "Marisa", "Address": "868 Mortified St. Weymouth MA", "Contact for future races": "no", "Email": "trail@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Nicholson", "Gender": "male", "Age": "90", "Telephone": "427-1635", "ID": "", "First name": "Sidney", "Address": "30 Bikinis Dr. Hanover MA", "Contact for future races": "no", "Email": "handy@gmail.com"}, {"How did you hear about race": "church", "Last name": "Chase", "Gender": "female", "Age": "27", "Telephone": "489-7862", "ID": "", "First name": "Elise", "Address": "695 Neurology St. Hingham MA", "Contact for future races": "yes", "Email": "oswald@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Huber", "Gender": "male", "Age": "86", "Telephone": "843-2775", "ID": "", "First name": "", "Address": "137 Obnoxious Pkwy. Norwell MA", "Contact for future races": "yes", "Email": "foundering@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hansen", "Gender": "female", "Age": "60", "Telephone": "281-1082", "ID": "", "First name": "Willie", "Address": "851 Gorillas St. Weymouth MA", "Contact for future races": "no", "Email": "reebok@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Pruitt", "Gender": "male", "Age": "45", "Telephone": "228-9597", "ID": "", "First name": "Henry", "Address": "105 Acacias St. Weymouth MA", "Contact for future races": "yes", "Email": "compressors@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Leblanc", "Gender": "female", "Age": "66", "Telephone": "647-4490", "ID": "", "First name": "Alexandra", "Address": "753 Transitions Ln. Quincy MA", "Contact for future races": "", "Email": "grimnesss@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Savage", "Gender": "male", "Age": "9", "Telephone": "265-8787", "ID": "", "First name": "Mark", "Address": "723 Administrators St. Quincy MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Tate", "Gender": "male", "Age": "71", "Telephone": "701-4214", "ID": "", "First name": "Darryl", "Address": "692 Dislocation Dr. Norwell MA", "Contact for future races": "no", "Email": "thundercloud@gmail.com"}, {"How did you hear about race": "", "Last name": "Randall", "Gender": "male", "Age": "72", "Telephone": "345-4984", "ID": "", "First name": "Lawrence", "Address": "83 Muds Pkwy. Hanover MA", "Contact for future races": "no", "Email": "prefers@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Green", "Gender": "female", "Age": "3", "Telephone": "181-9150", "ID": "", "First name": "Tommie", "Address": "851 Trading Way Weymouth MA", "Contact for future races": "yes", "Email": "mutineers@gmail.com"}, {"How did you hear about race": "church", "Last name": "Guerra", "Gender": "female", "Age": "13", "Telephone": "214-1454", "ID": "", "First name": "Sasha", "Address": "333 Parlay St. Hingham MA", "Contact for future races": "yes", "Email": "dills@gmail.com"}, {"How did you hear about race": "", "Last name": "Mason", "Gender": "male", "Age": "73", "Telephone": "246-8265", "ID": "", "First name": "Julio", "Address": "", "Contact for future races": "yes", "Email": "overlooking@hotmail.com"}, {"How did you hear about race": "", "Last name": "Anderson", "Gender": "male", "Age": "11", "Telephone": "306-7246", "ID": "", "First name": "Raymond", "Address": "994 Flammability St. Norwell MA", "Contact for future races": "yes", "Email": "payments@hotmail.com"}, {"How did you hear about race": "", "Last name": "Duncan", "Gender": "female", "Age": "94", "Telephone": "227-6264", "ID": "", "First name": "Megan", "Address": "900 Mordants Dr. Hanover MA", "Contact for future races": "yes", "Email": "venally@hotmail.com"}, {"How did you hear about race": "church", "Last name": "England", "Gender": "female", "Age": "55", "Telephone": "", "ID": "", "First name": "Hattie", "Address": "346 Rankles Pkwy. Braintree MA", "Contact for future races": "yes", "Email": "noreen@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Avila", "Gender": "", "Age": "60", "Telephone": "643-2993", "ID": "", "First name": "", "Address": "325 Danton St. Hanover MA", "Contact for future races": "yes", "Email": "tightwad@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Mendez", "Gender": "male", "Age": "3", "Telephone": "518-3467", "ID": "", "First name": "Ryan", "Address": "624 Filibusters Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "skinheads@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Harris", "Gender": "male", "Age": "4", "Telephone": "487-9176", "ID": "", "First name": "Leon", "Address": "", "Contact for future races": "yes", "Email": "fruitfulnesss@hotmail.com"}, {"How did you hear about race": "", "Last name": "Sellers", "Gender": "female", "Age": "29", "Telephone": "396-1064", "ID": "", "First name": "Anastasia", "Address": "239 Parachuting Way Braintree MA", "Contact for future races": "yes", "Email": "sandalwood@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Potts", "Gender": "male", "Age": "87", "Telephone": "493-3691", "ID": "", "First name": "Jamie", "Address": "634 Wallows St. Hanover MA", "Contact for future races": "yes", "Email": "clinicians@gmail.com"}, {"How did you hear about race": "", "Last name": "Turner", "Gender": "male", "Age": "73", "Telephone": "303-4743", "ID": "", "First name": "Terrance", "Address": "577 Invalidate St. Norwell MA", "Contact for future races": "yes", "Email": "fitzpatrick@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Bradley", "Gender": "female", "Age": "12", "Telephone": "221-2386", "ID": "", "First name": "Gretchen", "Address": "46 Gazeboes Way Norwell MA", "Contact for future races": "yes", "Email": "morocco@hotmail.com"}, {"How did you hear about race": "", "Last name": "Ortiz", "Gender": "female", "Age": "51", "Telephone": "174-4356", "ID": "", "First name": "Sherri", "Address": "20 Rightfulness Way Weymouth MA", "Contact for future races": "no", "Email": "stark@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Harding", "Gender": "female", "Age": "62", "Telephone": "461-5698", "ID": "", "First name": "Flossie", "Address": "851 Congratulations St. Quincy MA", "Contact for future races": "yes", "Email": "quintuples@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Britt", "Gender": "male", "Age": "77", "Telephone": "630-6387", "ID": "", "First name": "Wayne", "Address": "221 Simulated Ln. Braintree MA", "Contact for future races": "yes", "Email": "manicures@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Kim", "Gender": "male", "Age": "18", "Telephone": "141-3315", "ID": "", "First name": "Mario", "Address": "834 Junkies St. Norwell MA", "Contact for future races": "no", "Email": "probables@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Conway", "Gender": "female", "Age": "48", "Telephone": "347-3465", "ID": "", "First name": "Latisha", "Address": "138 Bewilders Ln. Quincy MA", "Contact for future races": "yes", "Email": "sequins@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mullins", "Gender": "female", "Age": "44", "Telephone": "688-4691", "ID": "", "First name": "", "Address": "281 Atoms Dr. Norwell MA", "Contact for future races": "no", "Email": "undemanding@hotmail.com"}, {"How did you hear about race": "", "Last name": "Crane", "Gender": "male", "Age": "40", "Telephone": "818-8162", "ID": "", "First name": "Jay", "Address": "297 Mist St. Hanover MA", "Contact for future races": "no", "Email": "sours@gmail.com"}, {"How did you hear about race": "", "Last name": "Waters", "Gender": "male", "Age": "56", "Telephone": "655-6302", "ID": "", "First name": "Ernest", "Address": "937 Chopras St. Weymouth MA", "Contact for future races": "no", "Email": "heeps@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mcneil", "Gender": "female", "Age": "42", "Telephone": "583-1943", "ID": "", "First name": "Louisa", "Address": "689 Abusively Dr. Weymouth MA", "Contact for future races": "no", "Email": "thrilled@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Moody", "Gender": "male", "Age": "34", "Telephone": "502-7359", "ID": "", "First name": "Jeremy", "Address": "411 Georgia Ln. Braintree MA", "Contact for future races": "yes", "Email": "admittance@gmail.com"}, {"How did you hear about race": "school", "Last name": "Owen", "Gender": "female", "Age": "61", "Telephone": "479-7530", "ID": "", "First name": "Bertie", "Address": "752 Tightropes Dr. Norwell MA", "Contact for future races": "yes", "Email": "pliability@gmail.com"}, {"How did you hear about race": "", "Last name": "Bray", "Gender": "female", "Age": "35", "Telephone": "420-6232", "ID": "", "First name": "Rhonda", "Address": "78 Egrets St. Hanover MA", "Contact for future races": "no", "Email": "remunerations@gmail.com"}, {"How did you hear about race": "church", "Last name": "Poole", "Gender": "female", "Age": "98", "Telephone": "894-4320", "ID": "", "First name": "Mollie", "Address": "867 Confirmatory Dr. Norwell MA", "Contact for future races": "yes", "Email": "sheratons@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Lucas", "Gender": "female", "Age": "68", "Telephone": "307-8529", "ID": "", "First name": "Lara", "Address": "746 Murray Ln. Hanover MA", "Contact for future races": "no", "Email": "emusic@gmail.com"}, {"How did you hear about race": "school", "Last name": "Perez", "Gender": "female", "Age": "83", "Telephone": "873-8027", "ID": "", "First name": "Coleen", "Address": "835 Gawains St. Hingham MA", "Contact for future races": "yes", "Email": "kristies@hotmail.com"}, {"How did you hear about race": "", "Last name": "Fleming", "Gender": "female", "Age": "87", "Telephone": "148-6064", "ID": "", "First name": "Pearlie", "Address": "711 Baxters St. Hingham MA", "Contact for future races": "yes", "Email": "queens@hotmail.com"}, {"How did you hear about race": "", "Last name": "Vega", "Gender": "male", "Age": "81", "Telephone": "785-1588", "ID": "", "First name": "Chad", "Address": "984 Infantries St. Quincy MA", "Contact for future races": "no", "Email": "accretion@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Landry", "Gender": "female", "Age": "80", "Telephone": "673-2306", "ID": "", "First name": "Beatrice", "Address": "107 Neutrinos St. Quincy MA", "Contact for future races": "yes", "Email": "conceptualizations@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Glover", "Gender": "female", "Age": "58", "Telephone": "380-9885", "ID": "", "First name": "Twila", "Address": "976 Subterfuges Ln. Quincy MA", "Contact for future races": "no", "Email": "gait@gmail.com"}, {"How did you hear about race": "school", "Last name": "Johnson", "Gender": "male", "Age": "41", "Telephone": "578-7692", "ID": "", "First name": "Leroy", "Address": "938 Comprehensions Dr. Hingham MA", "Contact for future races": "yes", "Email": "shush@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Crawford", "Gender": "female", "Age": "85", "Telephone": "598-3444", "ID": "", "First name": "Mildred", "Address": "", "Contact for future races": "yes", "Email": "widowers@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Roman", "Gender": "female", "Age": "10", "Telephone": "124-5788", "ID": "", "First name": "", "Address": "480 Daughters Ln. Braintree MA", "Contact for future races": "no", "Email": "overrunning@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Zimmerman", "Gender": "male", "Age": "9", "Telephone": "832-3652", "ID": "", "First name": "Alfred", "Address": "594 Eileens Dr. Weymouth MA", "Contact for future races": "no", "Email": "libeling@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Hardin", "Gender": "female", "Age": "66", "Telephone": "126-2759", "ID": "", "First name": "Janet", "Address": "535 Chrysalides St. Quincy MA", "Contact for future races": "yes", "Email": "nona@gmail.com"}, {"How did you hear about race": "", "Last name": "Slater", "Gender": "female", "Age": "52", "Telephone": "287-3392", "ID": "", "First name": "Stacy", "Address": "238 Alford Way Hingham MA", "Contact for future races": "yes", "Email": "staler@hotmail.com"}, {"How did you hear about race": "", "Last name": "Simon", "Gender": "male", "Age": "20", "Telephone": "916-5228", "ID": "", "First name": "Arnold", "Address": "455 Nansen Way Weymouth MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Wilkinson", "Gender": "female", "Age": "77", "Telephone": "285-5317", "ID": "", "First name": "Guadalupe", "Address": "961 Removers Pkwy. Braintree MA", "Contact for future races": "no", "Email": "festivitys@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Guthrie", "Gender": "male", "Age": "60", "Telephone": "914-3125", "ID": "", "First name": "Steve", "Address": "241 Masticating Dr. Norwell MA", "Contact for future races": "", "Email": "bumbled@gmail.com"}, {"How did you hear about race": "", "Last name": "Holman", "Gender": "male", "Age": "21", "Telephone": "209-7014", "ID": "", "First name": "Norman", "Address": "230 Restatements St. Quincy MA", "Contact for future races": "yes", "Email": "remounts@hotmail.com"}, {"How did you hear about race": "", "Last name": "Lee", "Gender": "female", "Age": "31", "Telephone": "899-8117", "ID": "", "First name": "Earlene", "Address": "429 Abducts Way Hanover MA", "Contact for future races": "no", "Email": "fathead@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Albert", "Gender": "male", "Age": "73", "Telephone": "227-9859", "ID": "", "First name": "Jason", "Address": "802 Unimpeachable St. Braintree MA", "Contact for future races": "yes", "Email": "affinitys@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Marks", "Gender": "female", "Age": "73", "Telephone": "288-2772", "ID": "", "First name": "", "Address": "886 Taos Way Weymouth MA", "Contact for future races": "yes", "Email": "misogynistic@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Maddox", "Gender": "male", "Age": "25", "Telephone": "340-4356", "ID": "", "First name": "Marshall", "Address": "387 Diwalis St. Hingham MA", "Contact for future races": "yes", "Email": "exchange@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Patterson", "Gender": "male", "Age": "94", "Telephone": "647-5048", "ID": "", "First name": "Earl", "Address": "216 Massasoit Pkwy. Norwell MA", "Contact for future races": "no", "Email": "yvettes@gmail.com"}, {"How did you hear about race": "school", "Last name": "Velez", "Gender": "male", "Age": "40", "Telephone": "686-1369", "ID": "", "First name": "Tyrone", "Address": "672 Hermans St. Braintree MA", "Contact for future races": "", "Email": "returned@hotmail.com"}, {"How did you hear about race": "", "Last name": "Koch", "Gender": "male", "Age": "85", "Telephone": "528-7632", "ID": "", "First name": "Howard", "Address": "742 Sudra Dr. Braintree MA", "Contact for future races": "no", "Email": "renaming@gmail.com"}, {"How did you hear about race": "church", "Last name": "Hinton", "Gender": "male", "Age": "47", "Telephone": "868-4307", "ID": "", "First name": "Rick", "Address": "482 Luridness Dr. Quincy MA", "Contact for future races": "no", "Email": "mess@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Booth", "Gender": "male", "Age": "57", "Telephone": "123-7113", "ID": "", "First name": "Jordan", "Address": "34 Zingers Pkwy. Hingham MA", "Contact for future races": "no", "Email": "paulette@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Foreman", "Gender": "male", "Age": "21", "Telephone": "201-1344", "ID": "", "First name": "Adam", "Address": "883 Rockets Ln. Hanover MA", "Contact for future races": "no", "Email": "allergists@gmail.com"}, {"How did you hear about race": "church", "Last name": "Harrison", "Gender": "female", "Age": "91", "Telephone": "106-8177", "ID": "", "First name": "Elena", "Address": "959 Domiciles St. Quincy MA", "Contact for future races": "yes", "Email": "mesmerisms@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mcconnell", "Gender": "male", "Age": "0", "Telephone": "760-8310", "ID": "", "First name": "Floyd", "Address": "754 Positioned St. Hingham MA", "Contact for future races": "no", "Email": "havens@gmail.com"}, {"How did you hear about race": "", "Last name": "Haley", "Gender": "female", "Age": "57", "Telephone": "673-6712", "ID": "", "First name": "Rhoda", "Address": "274 Indexs Way Norwell MA", "Contact for future races": "yes", "Email": "bossier@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Gomez", "Gender": "male", "Age": "13", "Telephone": "971-5809", "ID": "", "First name": "Gene", "Address": "879 Mummies Dr. Hanover MA", "Contact for future races": "yes", "Email": "leavening@gmail.com"}, {"How did you hear about race": "", "Last name": "Garner", "Gender": "female", "Age": "87", "Telephone": "979-2518", "ID": "", "First name": "Jan", "Address": "953 Anterooms Pkwy. Braintree MA", "Contact for future races": "yes", "Email": "goaltender@gmail.com"}, {"How did you hear about race": "school", "Last name": "English", "Gender": "male", "Age": "93", "Telephone": "528-1497", "ID": "", "First name": "Darryl", "Address": "0 Liquored Dr. Weymouth MA", "Contact for future races": "yes", "Email": "neckerchiefs@hotmail.com"}, {"How did you hear about race": "", "Last name": "Wheeler", "Gender": "female", "Age": "31", "Telephone": "643-5418", "ID": "", "First name": "Marion", "Address": "612 Stools Dr. Hingham MA", "Contact for future races": "no", "Email": "disguising@hotmail.com"}, {"How did you hear about race": "", "Last name": "Goff", "Gender": "male", "Age": "36", "Telephone": "391-9990", "ID": "", "First name": "Eric", "Address": "163 Gazelles St. Weymouth MA", "Contact for future races": "yes", "Email": "speeder@gmail.com"}, {"How did you hear about race": "church", "Last name": "Fox", "Gender": "male", "Age": "20", "Telephone": "419-5058", "ID": "", "First name": "George", "Address": "586 Martinets St. Weymouth MA", "Contact for future races": "yes", "Email": "rovers@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Reese", "Gender": "male", "Age": "79", "Telephone": "155-6575", "ID": "", "First name": "John", "Address": "492 Eurasia St. Quincy MA", "Contact for future races": "no", "Email": "easts@gmail.com"}, {"How did you hear about race": "", "Last name": "Pruitt", "Gender": "female", "Age": "96", "Telephone": "471-6701", "ID": "", "First name": "Violet", "Address": "", "Contact for future races": "yes", "Email": "grumpiness@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Bean", "Gender": "female", "Age": "60", "Telephone": "668-1141", "ID": "", "First name": "Eugenia", "Address": "29 Remedying Dr. Weymouth MA", "Contact for future races": "no", "Email": "shoo@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Kent", "Gender": "male", "Age": "39", "Telephone": "846-1209", "ID": "", "First name": "Alfredo", "Address": "176 Polygons Ln. Weymouth MA", "Contact for future races": "yes", "Email": "esqs@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Ware", "Gender": "male", "Age": "96", "Telephone": "301-7879", "ID": "", "First name": "", "Address": "540 Zoroastrians Dr. Quincy MA", "Contact for future races": "yes", "Email": "referential@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Stevenson", "Gender": "male", "Age": "18", "Telephone": "423-1069", "ID": "", "First name": "Timothy", "Address": "194 Buyers Ln. Norwell MA", "Contact for future races": "yes", "Email": "humbly@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Glass", "Gender": "female", "Age": "24", "Telephone": "872-9825", "ID": "", "First name": "Lesley", "Address": "903 Faxing St. Quincy MA", "Contact for future races": "yes", "Email": "hampering@hotmail.com"}, {"How did you hear about race": "", "Last name": "Singleton", "Gender": "", "Age": "95", "Telephone": "373-1622", "ID": "", "First name": "Gabriel", "Address": "384 Sidestepped St. Hingham MA", "Contact for future races": "yes", "Email": "nws@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Roach", "Gender": "female", "Age": "92", "Telephone": "218-8031", "ID": "", "First name": "Lily", "Address": "594 Christs St. Quincy MA", "Contact for future races": "no", "Email": "alleyways@gmail.com"}, {"How did you hear about race": "church", "Last name": "Berger", "Gender": "female", "Age": "11", "Telephone": "290-9202", "ID": "", "First name": "", "Address": "82 Congas Ln. Norwell MA", "Contact for future races": "yes", "Email": "biotechnologys@gmail.com"}, {"How did you hear about race": "", "Last name": "Brennan", "Gender": "female", "Age": "60", "Telephone": "234-6357", "ID": "", "First name": "Tessa", "Address": "290 Georgias St. Norwell MA", "Contact for future races": "yes", "Email": "pollutions@hotmail.com"}, {"How did you hear about race": "", "Last name": "Rowland", "Gender": "female", "Age": "97", "Telephone": "393-2371", "ID": "", "First name": "Sophie", "Address": "627 Vodka St. Quincy MA", "Contact for future races": "no", "Email": "communions@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Hicks", "Gender": "female", "Age": "54", "Telephone": "615-5414", "ID": "", "First name": "Earnestine", "Address": "34 Teals Dr. Hingham MA", "Contact for future races": "no", "Email": "unspoken@gmail.com"}, {"How did you hear about race": "", "Last name": "Graham", "Gender": "male", "Age": "25", "Telephone": "695-4692", "ID": "", "First name": "Ryan", "Address": "493 Aphoristic St. Norwell MA", "Contact for future races": "yes", "Email": "randell@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Turner", "Gender": "male", "Age": "68", "Telephone": "519-3334", "ID": "", "First name": "Harold", "Address": "798 Sticklebacks Pkwy. Norwell MA", "Contact for future races": "yes", "Email": "cara@gmail.com"}, {"How did you hear about race": "", "Last name": "Morgan", "Gender": "female", "Age": "0", "Telephone": "539-5074", "ID": "", "First name": "Odessa", "Address": "992 Pursed St. Quincy MA", "Contact for future races": "", "Email": "idahos@gmail.com"}, {"How did you hear about race": "", "Last name": "Madden", "Gender": "female", "Age": "45", "Telephone": "602-2043", "ID": "", "First name": "Edna", "Address": "935 Ermines St. Quincy MA", "Contact for future races": "yes", "Email": "sprawl@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Valenzuela", "Gender": "male", "Age": "13", "Telephone": "695-8498", "ID": "", "First name": "Lee", "Address": "14 Grinds Way Hingham MA", "Contact for future races": "", "Email": "banged@hotmail.com"}, {"How did you hear about race": "", "Last name": "Dean", "Gender": "female", "Age": "23", "Telephone": "806-4665", "ID": "", "First name": "Rosetta", "Address": "197 Reinterpretation Ln. Hingham MA", "Contact for future races": "yes", "Email": "gibberishs@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Perry", "Gender": "male", "Age": "19", "Telephone": "467-2607", "ID": "", "First name": "Seth", "Address": "791 Tocqueville St. Braintree MA", "Contact for future races": "no", "Email": "plagiarists@hotmail.com"}, {"How did you hear about race": "", "Last name": "Doyle", "Gender": "female", "Age": "37", "Telephone": "485-5702", "ID": "", "First name": "Rebecca", "Address": "845 Sensed Dr. Norwell MA", "Contact for future races": "yes", "Email": "inflammation@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Gillespie", "Gender": "male", "Age": "69", "Telephone": "136-2684", "ID": "", "First name": "Marion", "Address": "629 Flatboats Dr. Norwell MA", "Contact for future races": "yes", "Email": "corrosions@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Herrera", "Gender": "", "Age": "0", "Telephone": "244-1861", "ID": "", "First name": "Geraldine", "Address": "569 Undemonstrative Ln. Norwell MA", "Contact for future races": "no", "Email": "helvetius@gmail.com"}, {"How did you hear about race": "", "Last name": "Justice", "Gender": "female", "Age": "82", "Telephone": "489-9917", "ID": "", "First name": "Miranda", "Address": "952 Elopements Dr. Hanover MA", "Contact for future races": "yes", "Email": "doughy@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Sandoval", "Gender": "male", "Age": "48", "Telephone": "559-9690", "ID": "", "First name": "Edwin", "Address": "310 Eighty Ln. Weymouth MA", "Contact for future races": "yes", "Email": "docket@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mcknight", "Gender": "female", "Age": "28", "Telephone": "364-7931", "ID": "", "First name": "Johanna", "Address": "", "Contact for future races": "yes", "Email": "overrating@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Jordan", "Gender": "female", "Age": "34", "Telephone": "912-5991", "ID": "", "First name": "Beatrice", "Address": "950 Decedents Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "rod@gmail.com"}, {"How did you hear about race": "", "Last name": "Ayers", "Gender": "female", "Age": "2", "Telephone": "119-1913", "ID": "", "First name": "Donna", "Address": "391 Enforces Dr. Weymouth MA", "Contact for future races": "no", "Email": "specialists@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Fields", "Gender": "female", "Age": "84", "Telephone": "222-3253", "ID": "", "First name": "", "Address": "782 Paley Dr. Braintree MA", "Contact for future races": "no", "Email": "tycoons@gmail.com"}, {"How did you hear about race": "church", "Last name": "Summers", "Gender": "male", "Age": "65", "Telephone": "343-3967", "ID": "", "First name": "Warren", "Address": "313 Superior St. Hingham MA", "Contact for future races": "yes", "Email": "terabytes@gmail.com"}, {"How did you hear about race": "school", "Last name": "Kelley", "Gender": "female", "Age": "44", "Telephone": "931-3425", "ID": "", "First name": "Jewell", "Address": "176 Seaweed Pkwy. Quincy MA", "Contact for future races": "no", "Email": "rocknes@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Dean", "Gender": "female", "Age": "3", "Telephone": "329-2796", "ID": "", "First name": "Laurel", "Address": "637 Geographically St. Braintree MA", "Contact for future races": "yes", "Email": "livens@gmail.com"}, {"How did you hear about race": "church", "Last name": "Phillips", "Gender": "male", "Age": "45", "Telephone": "294-3239", "ID": "", "First name": "Brad", "Address": "627 Sams St. Hanover MA", "Contact for future races": "yes", "Email": "incubators@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mccarthy", "Gender": "male", "Age": "11", "Telephone": "450-9414", "ID": "", "First name": "Juan", "Address": "654 Officemax Way Quincy MA", "Contact for future races": "no", "Email": "gospels@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Warren", "Gender": "female", "Age": "99", "Telephone": "732-9102", "ID": "", "First name": "Ellen", "Address": "785 Livias St. Norwell MA", "Contact for future races": "yes", "Email": "cockades@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hernandez", "Gender": "male", "Age": "36", "Telephone": "815-2321", "ID": "", "First name": "Peter", "Address": "886 Sightings St. Norwell MA", "Contact for future races": "", "Email": "aurae@hotmail.com"}, {"How did you hear about race": "", "Last name": "Cooley", "Gender": "", "Age": "4", "Telephone": "508-6042", "ID": "", "First name": "Neil", "Address": "828 Loopy Ln. Norwell MA", "Contact for future races": "yes", "Email": "postage@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mendez", "Gender": "male", "Age": "56", "Telephone": "344-3585", "ID": "", "First name": "Floyd", "Address": "39 Conked Pkwy. Braintree MA", "Contact for future races": "yes", "Email": "translucent@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Morse", "Gender": "female", "Age": "20", "Telephone": "719-9245", "ID": "", "First name": "Lacey", "Address": "384 Uncontrollably Dr. Weymouth MA", "Contact for future races": "no", "Email": "cinders@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Anthony", "Gender": "female", "Age": "61", "Telephone": "496-1937", "ID": "", "First name": "Liliana", "Address": "759 Tracts Way Quincy MA", "Contact for future races": "no", "Email": "venice@hotmail.com"}, {"How did you hear about race": "", "Last name": "Walton", "Gender": "female", "Age": "99", "Telephone": "570-6066", "ID": "", "First name": "Malinda", "Address": "272 Sellerss Dr. Weymouth MA", "Contact for future races": "no", "Email": "fulcrum@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Valentine", "Gender": "female", "Age": "49", "Telephone": "449-4101", "ID": "", "First name": "Beverly", "Address": "364 Paderewski Pkwy. Quincy MA", "Contact for future races": "yes", "Email": "bruisers@gmail.com"}, {"How did you hear about race": "church", "Last name": "Kelly", "Gender": "female", "Age": "92", "Telephone": "159-9716", "ID": "", "First name": "Ilene", "Address": "293 Anorexics St. Braintree MA", "Contact for future races": "yes", "Email": "celebrity@hotmail.com"}, {"How did you hear about race": "", "Last name": "Lester", "Gender": "female", "Age": "36", "Telephone": "446-5943", "ID": "", "First name": "Cristina", "Address": "61 Possessive St. Quincy MA", "Contact for future races": "yes", "Email": "burmeses@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Vargas", "Gender": "female", "Age": "93", "Telephone": "245-2019", "ID": "", "First name": "Coleen", "Address": "952 Trussed Ln. Braintree MA", "Contact for future races": "yes", "Email": "semiprofessionals@gmail.com"}, {"How did you hear about race": "school", "Last name": "Wyatt", "Gender": "male", "Age": "60", "Telephone": "658-3130", "ID": "", "First name": "Tony", "Address": "917 Novelists Ln. Norwell MA", "Contact for future races": "yes", "Email": "falsehoods@gmail.com"}, {"How did you hear about race": "", "Last name": "Hood", "Gender": "female", "Age": "47", "Telephone": "", "ID": "", "First name": "Ella", "Address": "183 Excretes Ln. Quincy MA", "Contact for future races": "no", "Email": "toiled@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Pratt", "Gender": "female", "Age": "90", "Telephone": "241-6323", "ID": "", "First name": "Mayra", "Address": "503 Charbroil Pkwy. Quincy MA", "Contact for future races": "no", "Email": "priestliest@gmail.com"}, {"How did you hear about race": "", "Last name": "Burgess", "Gender": "", "Age": "73", "Telephone": "419-4203", "ID": "", "First name": "Rhonda", "Address": "620 Blasphemes St. Braintree MA", "Contact for future races": "yes", "Email": "whose@gmail.com"}, {"How did you hear about race": "church", "Last name": "Haney", "Gender": "male", "Age": "40", "Telephone": "", "ID": "", "First name": "Freddie", "Address": "447 Dins Dr. Hanover MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Callahan", "Gender": "female", "Age": "82", "Telephone": "916-5257", "ID": "", "First name": "Flossie", "Address": "328 Baleens St. Hingham MA", "Contact for future races": "yes", "Email": "ricotta@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mcintosh", "Gender": "male", "Age": "80", "Telephone": "649-3820", "ID": "", "First name": "Cory", "Address": "97 Wrinkles Ln. Quincy MA", "Contact for future races": "yes", "Email": "rumbled@hotmail.com"}, {"How did you hear about race": "", "Last name": "Leach", "Gender": "female", "Age": "33", "Telephone": "623-9346", "ID": "", "First name": "", "Address": "442 Exclusive Way Quincy MA", "Contact for future races": "no", "Email": "luminarys@gmail.com"}, {"How did you hear about race": "", "Last name": "Gonzalez", "Gender": "male", "Age": "81", "Telephone": "436-9425", "ID": "", "First name": "Andy", "Address": "612 Paars Ln. Hanover MA", "Contact for future races": "yes", "Email": "amalgam@gmail.com"}, {"How did you hear about race": "school", "Last name": "Valentine", "Gender": "female", "Age": "67", "Telephone": "216-8093", "ID": "", "First name": "Lily", "Address": "612 Fastens St. Hanover MA", "Contact for future races": "no", "Email": "manfully@gmail.com"}, {"How did you hear about race": "", "Last name": "Barker", "Gender": "female", "Age": "83", "Telephone": "703-7667", "ID": "", "First name": "Casey", "Address": "532 Gaiters St. Hanover MA", "Contact for future races": "no", "Email": "pelvic@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Eaton", "Gender": "male", "Age": "61", "Telephone": "840-5875", "ID": "", "First name": "Benjamin", "Address": "468 Hootch St. Norwell MA", "Contact for future races": "", "Email": "mazarin@gmail.com"}, {"How did you hear about race": "", "Last name": "Marquez", "Gender": "female", "Age": "26", "Telephone": "642-5361", "ID": "", "First name": "Beth", "Address": "626 Graphites St. Quincy MA", "Contact for future races": "yes", "Email": "dooms@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Clemons", "Gender": "female", "Age": "35", "Telephone": "487-5368", "ID": "", "First name": "Eula", "Address": "352 Counterattacks Ln. Quincy MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Berg", "Gender": "male", "Age": "84", "Telephone": "173-6303", "ID": "", "First name": "Charlie", "Address": "446 Badmouths St. Braintree MA", "Contact for future races": "yes", "Email": "unsubstantial@gmail.com"}, {"How did you hear about race": "church", "Last name": "Mcknight", "Gender": "female", "Age": "17", "Telephone": "798-8481", "ID": "", "First name": "Hester", "Address": "265 Swimmings St. Hingham MA", "Contact for future races": "no", "Email": "maples@gmail.com"}, {"How did you hear about race": "", "Last name": "Obrien", "Gender": "male", "Age": "91", "Telephone": "269-7192", "ID": "", "First name": "Clifford", "Address": "260 Fuelling St. Quincy MA", "Contact for future races": "yes", "Email": "familiarity@gmail.com"}, {"How did you hear about race": "", "Last name": "Trevino", "Gender": "female", "Age": "2", "Telephone": "589-9521", "ID": "", "First name": "Adela", "Address": "691 Preserves St. Braintree MA", "Contact for future races": "yes", "Email": "economical@gmail.com"}, {"How did you hear about race": "", "Last name": "Levine", "Gender": "male", "Age": "22", "Telephone": "863-1933", "ID": "", "First name": "Rafael", "Address": "729 Scuppered St. Weymouth MA", "Contact for future races": "yes", "Email": "inez@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Soto", "Gender": "male", "Age": "57", "Telephone": "194-7799", "ID": "", "First name": "Tommy", "Address": "480 Skyrockets St. Weymouth MA", "Contact for future races": "no", "Email": "moseley@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Flowers", "Gender": "female", "Age": "79", "Telephone": "394-9520", "ID": "", "First name": "Lucia", "Address": "624 Epoch Pkwy. Quincy MA", "Contact for future races": "yes", "Email": "malls@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Jenkins", "Gender": "", "Age": "98", "Telephone": "350-1724", "ID": "", "First name": "Wayne", "Address": "252 Kingdoms Way Quincy MA", "Contact for future races": "no", "Email": "bloods@gmail.com"}, {"How did you hear about race": "", "Last name": "Combs", "Gender": "female", "Age": "36", "Telephone": "", "ID": "", "First name": "Kari", "Address": "89 Flinched Ln. Quincy MA", "Contact for future races": "yes", "Email": "silvias@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Fulton", "Gender": "female", "Age": "51", "Telephone": "815-6284", "ID": "", "First name": "Michelle", "Address": "451 Staged Ln. Norwell MA", "Contact for future races": "no", "Email": "terraced@hotmail.com"}, {"How did you hear about race": "", "Last name": "Cash", "Gender": "male", "Age": "52", "Telephone": "342-2655", "ID": "", "First name": "Clarence", "Address": "856 Escalators St. Quincy MA", "Contact for future races": "yes", "Email": "inventorys@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Maynard", "Gender": "male", "Age": "69", "Telephone": "366-5087", "ID": "", "First name": "Ricky", "Address": "89 Ninetys Pkwy. Braintree MA", "Contact for future races": "no", "Email": "cheeks@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Flores", "Gender": "male", "Age": "35", "Telephone": "778-7913", "ID": "", "First name": "", "Address": "851 Acruxs Ln. Quincy MA", "Contact for future races": "yes", "Email": "casios@gmail.com"}, {"How did you hear about race": "", "Last name": "Christensen", "Gender": "female", "Age": "95", "Telephone": "917-1642", "ID": "", "First name": "Susanne", "Address": "412 Paterson St. Hingham MA", "Contact for future races": "no", "Email": "aquavits@gmail.com"}, {"How did you hear about race": "", "Last name": "Hendrix", "Gender": "male", "Age": "84", "Telephone": "847-5929", "ID": "", "First name": "Clifton", "Address": "574 Epidemiology Way Norwell MA", "Contact for future races": "", "Email": "tricky@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Baker", "Gender": "", "Age": "32", "Telephone": "218-5584", "ID": "", "First name": "Joe", "Address": "234 Infatuating Pkwy. Norwell MA", "Contact for future races": "", "Email": "luciuss@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Battle", "Gender": "female", "Age": "35", "Telephone": "367-2599", "ID": "", "First name": "Angelique", "Address": "88 Bogging St. Hanover MA", "Contact for future races": "yes", "Email": "doughnut@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Little", "Gender": "female", "Age": "27", "Telephone": "257-3111", "ID": "", "First name": "Katina", "Address": "188 Mouthfuls Dr. Hingham MA", "Contact for future races": "no", "Email": "stolypin@hotmail.com"}, {"How did you hear about race": "", "Last name": "Irwin", "Gender": "female", "Age": "78", "Telephone": "424-9971", "ID": "", "First name": "", "Address": "554 Zips St. Norwell MA", "Contact for future races": "yes", "Email": "winners@gmail.com"}, {"How did you hear about race": "", "Last name": "Conway", "Gender": "", "Age": "17", "Telephone": "333-8182", "ID": "", "First name": "", "Address": "866 Placers St. Quincy MA", "Contact for future races": "no", "Email": "steepnesss@gmail.com"}, {"How did you hear about race": "church", "Last name": "Wall", "Gender": "male", "Age": "81", "Telephone": "264-4784", "ID": "", "First name": "Bryan", "Address": "", "Contact for future races": "no", "Email": "clipping@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hester", "Gender": "female", "Age": "75", "Telephone": "112-3437", "ID": "", "First name": "Shelby", "Address": "522 Misjudgements Ln. Braintree MA", "Contact for future races": "no", "Email": "austria@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Scott", "Gender": "female", "Age": "51", "Telephone": "350-7987", "ID": "", "First name": "Dixie", "Address": "623 Dostoevsky Ln. Quincy MA", "Contact for future races": "yes", "Email": "ramada@gmail.com"}, {"How did you hear about race": "", "Last name": "Byers", "Gender": "male", "Age": "7", "Telephone": "379-8099", "ID": "", "First name": "Roger", "Address": "913 Frittered St. Braintree MA", "Contact for future races": "", "Email": "shiftiest@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "", "Gender": "male", "Age": "60", "Telephone": "542-7433", "ID": "", "First name": "Brett", "Address": "54 Gladioluss Pkwy. Norwell MA", "Contact for future races": "no", "Email": "routes@gmail.com"}, {"How did you hear about race": "", "Last name": "Stevenson", "Gender": "female", "Age": "47", "Telephone": "196-1823", "ID": "", "First name": "Wilda", "Address": "798 Uncle Dr. Hanover MA", "Contact for future races": "no", "Email": "hostilely@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Padilla", "Gender": "male", "Age": "76", "Telephone": "232-1951", "ID": "", "First name": "Frederick", "Address": "833 Phalluses St. Braintree MA", "Contact for future races": "no", "Email": "prevailing@gmail.com"}, {"How did you hear about race": "", "Last name": "Cantu", "Gender": "female", "Age": "49", "Telephone": "634-5289", "ID": "", "First name": "Bridgett", "Address": "171 Invalidates St. Hingham MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Sherman", "Gender": "male", "Age": "80", "Telephone": "215-4795", "ID": "", "First name": "Clifford", "Address": "364 Politics St. Quincy MA", "Contact for future races": "yes", "Email": "defoliants@hotmail.com"}, {"How did you hear about race": "", "Last name": "Forbes", "Gender": "male", "Age": "77", "Telephone": "326-6072", "ID": "", "First name": "Edward", "Address": "595 Detriments Ln. Norwell MA", "Contact for future races": "yes", "Email": "precedent@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Weber", "Gender": "", "Age": "11", "Telephone": "641-1011", "ID": "", "First name": "Troy", "Address": "724 Dislike Pkwy. Quincy MA", "Contact for future races": "yes", "Email": "turgenev@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mccray", "Gender": "female", "Age": "45", "Telephone": "786-7494", "ID": "", "First name": "Sara", "Address": "606 Distributions Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "assorting@gmail.com"}, {"How did you hear about race": "", "Last name": "Mcneil", "Gender": "", "Age": "5", "Telephone": "210-6365", "ID": "", "First name": "", "Address": "38 Favorably Ln. Norwell MA", "Contact for future races": "no", "Email": "watchmans@gmail.com"}, {"How did you hear about race": "church", "Last name": "Ayers", "Gender": "female", "Age": "1", "Telephone": "157-8726", "ID": "", "First name": "Haley", "Address": "298 Lesleys Dr. Braintree MA", "Contact for future races": "no", "Email": "janiss@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Roberson", "Gender": "male", "Age": "18", "Telephone": "279-9228", "ID": "", "First name": "Karl", "Address": "89 Carapace Pkwy. Hanover MA", "Contact for future races": "no", "Email": "roderick@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Pena", "Gender": "female", "Age": "13", "Telephone": "251-8542", "ID": "", "First name": "Lorrie", "Address": "633 Busybody St. Hanover MA", "Contact for future races": "yes", "Email": "developments@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Tyler", "Gender": "female", "Age": "85", "Telephone": "", "ID": "", "First name": "Pearl", "Address": "145 Plasma Dr. Hanover MA", "Contact for future races": "no", "Email": "interconnection@gmail.com"}, {"How did you hear about race": "", "Last name": "Kaufman", "Gender": "male", "Age": "0", "Telephone": "", "ID": "", "First name": "Virgil", "Address": "884 Octagonal Ln. Hingham MA", "Contact for future races": "no", "Email": "campaigning@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mcguire", "Gender": "female", "Age": "33", "Telephone": "599-9829", "ID": "", "First name": "Ethel", "Address": "495 Poppycocks Dr. Braintree MA", "Contact for future races": "no", "Email": "its@gmail.com"}, {"How did you hear about race": "church", "Last name": "Herman", "Gender": "male", "Age": "31", "Telephone": "535-2067", "ID": "", "First name": "Reginald", "Address": "", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "a friend", "Last name": "Davis", "Gender": "male", "Age": "75", "Telephone": "630-5213", "ID": "", "First name": "Donald", "Address": "131 Pretexts St. Hingham MA", "Contact for future races": "no", "Email": "fryers@hotmail.com"}, {"How did you hear about race": "", "Last name": "Rowland", "Gender": "female", "Age": "31", "Telephone": "128-4576", "ID": "", "First name": "Daisy", "Address": "834 Seamans Ln. Weymouth MA", "Contact for future races": "no", "Email": "ravaged@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Palmer", "Gender": "female", "Age": "4", "Telephone": "300-4439", "ID": "", "First name": "Mallory", "Address": "558 Elliot Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "clemenceau@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hull", "Gender": "female", "Age": "10", "Telephone": "401-9404", "ID": "", "First name": "Janette", "Address": "59 Quoited Ln. Hanover MA", "Contact for future races": "yes", "Email": "gentled@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mcdonald", "Gender": "male", "Age": "46", "Telephone": "431-8457", "ID": "", "First name": "Jon", "Address": "965 Joggers Way Norwell MA", "Contact for future races": "yes", "Email": "tans@gmail.com"}, {"How did you hear about race": "school", "Last name": "Snow", "Gender": "male", "Age": "50", "Telephone": "376-2487", "ID": "", "First name": "Julio", "Address": "398 Terminates St. Braintree MA", "Contact for future races": "no", "Email": "mack@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Joyce", "Gender": "male", "Age": "1", "Telephone": "", "ID": "", "First name": "Clinton", "Address": "805 Sphinxes St. Braintree MA", "Contact for future races": "no", "Email": "coping@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Bryant", "Gender": "female", "Age": "42", "Telephone": "438-5448", "ID": "", "First name": "Amanda", "Address": "195 Replies St. Weymouth MA", "Contact for future races": "yes", "Email": "juncos@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Kim", "Gender": "male", "Age": "52", "Telephone": "830-1012", "ID": "", "First name": "Bernard", "Address": "742 Purdue Dr. Hingham MA", "Contact for future races": "yes", "Email": "sock@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Barber", "Gender": "male", "Age": "18", "Telephone": "326-1563", "ID": "", "First name": "Sam", "Address": "851 Transfigurations Pkwy. Quincy MA", "Contact for future races": "yes", "Email": "sureties@gmail.com"}, {"How did you hear about race": "", "Last name": "Hampton", "Gender": "female", "Age": "63", "Telephone": "479-6811", "ID": "", "First name": "", "Address": "", "Contact for future races": "yes", "Email": "distills@gmail.com"}, {"How did you hear about race": "", "Last name": "Michael", "Gender": "female", "Age": "41", "Telephone": "520-5791", "ID": "", "First name": "Abigail", "Address": "359 Tout St. Braintree MA", "Contact for future races": "no", "Email": "loges@gmail.com"}, {"How did you hear about race": "", "Last name": "Lott", "Gender": "female", "Age": "36", "Telephone": "389-5624", "ID": "", "First name": "Meredith", "Address": "745 Seclude Dr. Quincy MA", "Contact for future races": "yes", "Email": "depressingly@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Stewart", "Gender": "female", "Age": "34", "Telephone": "678-6318", "ID": "", "First name": "Sharlene", "Address": "632 Wildlife Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "cautious@hotmail.com"}, {"How did you hear about race": "", "Last name": "Evans", "Gender": "female", "Age": "16", "Telephone": "740-9923", "ID": "", "First name": "Adrienne", "Address": "", "Contact for future races": "no", "Email": "cellulites@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Sharpe", "Gender": "female", "Age": "85", "Telephone": "313-9467", "ID": "", "First name": "Arline", "Address": "748 Hexameters Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "rebuked@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Cortez", "Gender": "female", "Age": "8", "Telephone": "621-4943", "ID": "", "First name": "Sabrina", "Address": "121 Liveliest Dr. Braintree MA", "Contact for future races": "no", "Email": "demolition@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Fischer", "Gender": "female", "Age": "17", "Telephone": "625-7092", "ID": "", "First name": "Marjorie", "Address": "416 Amperages Ln. Hanover MA", "Contact for future races": "yes", "Email": "treacherously@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Vaughan", "Gender": "male", "Age": "45", "Telephone": "818-9251", "ID": "", "First name": "Lloyd", "Address": "894 Murasakis Dr. Quincy MA", "Contact for future races": "no", "Email": "harmoniousnesss@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Powers", "Gender": "male", "Age": "31", "Telephone": "446-4448", "ID": "", "First name": "Bernard", "Address": "634 Rationalization St. Weymouth MA", "Contact for future races": "", "Email": "deice@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Murray", "Gender": "female", "Age": "78", "Telephone": "167-9086", "ID": "", "First name": "", "Address": "361 Disuses Ln. Quincy MA", "Contact for future races": "yes", "Email": "defaults@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Velez", "Gender": "female", "Age": "49", "Telephone": "716-7350", "ID": "", "First name": "Rita", "Address": "", "Contact for future races": "yes", "Email": "sawdust@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Preston", "Gender": "male", "Age": "88", "Telephone": "449-8990", "ID": "", "First name": "Aaron", "Address": "221 Sherman St. Hingham MA", "Contact for future races": "yes", "Email": "narratives@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Buck", "Gender": "female", "Age": "49", "Telephone": "237-7933", "ID": "", "First name": "Lora", "Address": "165 Headmistresses Dr. Weymouth MA", "Contact for future races": "no", "Email": "alleluia@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Ortiz", "Gender": "female", "Age": "75", "Telephone": "378-1826", "ID": "", "First name": "Lea", "Address": "866 Counterintelligence Dr. Hanover MA", "Contact for future races": "no", "Email": "scroungers@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Harrell", "Gender": "male", "Age": "97", "Telephone": "317-7435", "ID": "", "First name": "Brandon", "Address": "104 Consecration Ln. Braintree MA", "Contact for future races": "yes", "Email": "liquidators@hotmail.com"}, {"How did you hear about race": "", "Last name": "Miller", "Gender": "female", "Age": "48", "Telephone": "772-5387", "ID": "", "First name": "Noemi", "Address": "678 Deterioration St. Hanover MA", "Contact for future races": "no", "Email": "hows@gmail.com"}, {"How did you hear about race": "", "Last name": "Valencia", "Gender": "female", "Age": "50", "Telephone": "210-2434", "ID": "", "First name": "Norma", "Address": "780 Browner Ln. Hanover MA", "Contact for future races": "yes", "Email": "illegalities@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Perry", "Gender": "male", "Age": "3", "Telephone": "", "ID": "", "First name": "Elmer", "Address": "100 Unukalhais Dr. Hingham MA", "Contact for future races": "no", "Email": "madly@gmail.com"}, {"How did you hear about race": "", "Last name": "Miller", "Gender": "female", "Age": "44", "Telephone": "320-5352", "ID": "", "First name": "", "Address": "48 Pilothouses St. Hanover MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "came last year", "Last name": "Weeks", "Gender": "male", "Age": "3", "Telephone": "303-2179", "ID": "", "First name": "Ted", "Address": "329 Epicurean Pkwy. Quincy MA", "Contact for future races": "no", "Email": "reputedly@gmail.com"}, {"How did you hear about race": "", "Last name": "Berry", "Gender": "female", "Age": "71", "Telephone": "900-6598", "ID": "", "First name": "Charlotte", "Address": "403 Galileos St. Norwell MA", "Contact for future races": "no", "Email": "radicalism@gmail.com"}, {"How did you hear about race": "", "Last name": "Lott", "Gender": "female", "Age": "59", "Telephone": "805-8673", "ID": "", "First name": "Natalia", "Address": "", "Contact for future races": "yes", "Email": "druggists@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Knapp", "Gender": "female", "Age": "7", "Telephone": "374-9193", "ID": "", "First name": "Sabrina", "Address": "789 Claps Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "bridgetts@gmail.com"}, {"How did you hear about race": "school", "Last name": "", "Gender": "male", "Age": "58", "Telephone": "744-6680", "ID": "", "First name": "Darryl", "Address": "165 Horrid St. Norwell MA", "Contact for future races": "no", "Email": "asocial@gmail.com"}, {"How did you hear about race": "", "Last name": "Arnold", "Gender": "male", "Age": "30", "Telephone": "333-5018", "ID": "", "First name": "Bernard", "Address": "760 Luck St. Braintree MA", "Contact for future races": "no", "Email": "enjoins@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Clemons", "Gender": "female", "Age": "10", "Telephone": "520-3016", "ID": "", "First name": "Rene", "Address": "261 Deregulate St. Quincy MA", "Contact for future races": "no", "Email": "atrocities@gmail.com"}, {"How did you hear about race": "", "Last name": "Velazquez", "Gender": "female", "Age": "85", "Telephone": "305-9026", "ID": "", "First name": "Savannah", "Address": "663 Deformations Pkwy. Hanover MA", "Contact for future races": "", "Email": "kingdoms@gmail.com"}, {"How did you hear about race": "", "Last name": "Powell", "Gender": "female", "Age": "96", "Telephone": "562-1323", "ID": "", "First name": "Karina", "Address": "661 Reason Way Hingham MA", "Contact for future races": "yes", "Email": "munro@hotmail.com"}, {"How did you hear about race": "", "Last name": "Carlson", "Gender": "female", "Age": "18", "Telephone": "237-5822", "ID": "", "First name": "Mia", "Address": "", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "came last year", "Last name": "Landry", "Gender": "male", "Age": "69", "Telephone": "500-1346", "ID": "", "First name": "Lee", "Address": "562 Exuberantly Dr. Norwell MA", "Contact for future races": "yes", "Email": "recommendation@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Salinas", "Gender": "male", "Age": "7", "Telephone": "713-2892", "ID": "", "First name": "Howard", "Address": "471 Lelas St. Hanover MA", "Contact for future races": "yes", "Email": "astronauticss@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Finley", "Gender": "male", "Age": "33", "Telephone": "727-4332", "ID": "", "First name": "Ted", "Address": "719 Gladioluses St. Quincy MA", "Contact for future races": "yes", "Email": "hangdog@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Franks", "Gender": "male", "Age": "28", "Telephone": "218-7561", "ID": "", "First name": "Ted", "Address": "430 Misadventure Dr. Norwell MA", "Contact for future races": "yes", "Email": "marimbas@hotmail.com"}, {"How did you hear about race": "", "Last name": "Nash", "Gender": "female", "Age": "5", "Telephone": "824-5589", "ID": "", "First name": "Lea", "Address": "487 Scruffy Ln. Braintree MA", "Contact for future races": "no", "Email": "expectorates@gmail.com"}, {"How did you hear about race": "church", "Last name": "Ellison", "Gender": "male", "Age": "16", "Telephone": "135-9981", "ID": "", "First name": "Rafael", "Address": "563 Shortcakes St. Quincy MA", "Contact for future races": "yes", "Email": "arrogates@hotmail.com"}, {"How did you hear about race": "", "Last name": "Olsen", "Gender": "female", "Age": "38", "Telephone": "798-9675", "ID": "", "First name": "Lucille", "Address": "131 Jaywalking Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "cardozo@gmail.com"}, {"How did you hear about race": "school", "Last name": "Gill", "Gender": "female", "Age": "86", "Telephone": "566-3362", "ID": "", "First name": "Isabella", "Address": "952 Replicating St. Norwell MA", "Contact for future races": "no", "Email": "crucible@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Marshall", "Gender": "male", "Age": "20", "Telephone": "942-9293", "ID": "", "First name": "Ralph", "Address": "132 Tinderboxes Ln. Braintree MA", "Contact for future races": "yes", "Email": "hebraic@gmail.com"}, {"How did you hear about race": "", "Last name": "Villarreal", "Gender": "male", "Age": "75", "Telephone": "525-3496", "ID": "", "First name": "Edwin", "Address": "922 Relinquish Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "giselle@gmail.com"}, {"How did you hear about race": "church", "Last name": "Meyer", "Gender": "female", "Age": "44", "Telephone": "518-9715", "ID": "", "First name": "Audra", "Address": "296 Kerenskys St. Hingham MA", "Contact for future races": "no", "Email": "becks@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Holcomb", "Gender": "male", "Age": "73", "Telephone": "630-9813", "ID": "", "First name": "Albert", "Address": "460 Prodigality Ln. Quincy MA", "Contact for future races": "yes", "Email": "pseudonym@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Durham", "Gender": "male", "Age": "42", "Telephone": "", "ID": "", "First name": "Daryl", "Address": "322 Extempore Dr. Quincy MA", "Contact for future races": "yes", "Email": "unethical@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Buckley", "Gender": "male", "Age": "21", "Telephone": "762-5832", "ID": "", "First name": "Sidney", "Address": "220 Scintillation St. Norwell MA", "Contact for future races": "yes", "Email": "whitehorses@gmail.com"}, {"How did you hear about race": "school", "Last name": "Mclean", "Gender": "female", "Age": "6", "Telephone": "140-8674", "ID": "", "First name": "Tanisha", "Address": "140 Rapping Way Hingham MA", "Contact for future races": "yes", "Email": "fanciers@hotmail.com"}, {"How did you hear about race": "", "Last name": "Hampton", "Gender": "male", "Age": "72", "Telephone": "187-3551", "ID": "", "First name": "Daniel", "Address": "500 Beholden St. Hanover MA", "Contact for future races": "yes", "Email": "disciples@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Tillman", "Gender": "female", "Age": "70", "Telephone": "926-5240", "ID": "", "First name": "Paulette", "Address": "53 Multiplexing Way Hanover MA", "Contact for future races": "yes", "Email": "tendencies@gmail.com"}, {"How did you hear about race": "", "Last name": "Norton", "Gender": "male", "Age": "69", "Telephone": "964-3485", "ID": "", "First name": "Craig", "Address": "646 Cloudiness St. Braintree MA", "Contact for future races": "no", "Email": "provo@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Pena", "Gender": "male", "Age": "94", "Telephone": "114-2346", "ID": "", "First name": "Carl", "Address": "814 Indelible Pkwy. Hanover MA", "Contact for future races": "no", "Email": "aquanauts@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Chapman", "Gender": "male", "Age": "88", "Telephone": "740-8540", "ID": "", "First name": "Theodore", "Address": "43 Catwalk Ln. Norwell MA", "Contact for future races": "no", "Email": "outbursts@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Jefferson", "Gender": "male", "Age": "12", "Telephone": "754-6030", "ID": "", "First name": "Rene", "Address": "525 Visitors Way Braintree MA", "Contact for future races": "", "Email": "antedating@gmail.com"}, {"How did you hear about race": "church", "Last name": "Byers", "Gender": "male", "Age": "96", "Telephone": "492-2874", "ID": "", "First name": "Joe", "Address": "787 Harvester Ln. Weymouth MA", "Contact for future races": "yes", "Email": "inveigles@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Burch", "Gender": "male", "Age": "41", "Telephone": "226-8932", "ID": "", "First name": "Jacob", "Address": "877 Formatting Ln. Hingham MA", "Contact for future races": "no", "Email": "skimpy@hotmail.com"}, {"How did you hear about race": "", "Last name": "Gill", "Gender": "male", "Age": "72", "Telephone": "722-3098", "ID": "", "First name": "Brett", "Address": "985 Icelands St. Braintree MA", "Contact for future races": "yes", "Email": "fishing@gmail.com"}, {"How did you hear about race": "", "Last name": "Lancaster", "Gender": "female", "Age": "75", "Telephone": "668-9359", "ID": "", "First name": "Carlene", "Address": "339 Hindustans Dr. Norwell MA", "Contact for future races": "no", "Email": "suetonius@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Leon", "Gender": "female", "Age": "97", "Telephone": "785-8032", "ID": "", "First name": "Jeannine", "Address": "89 Corrosion St. Weymouth MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "church", "Last name": "Carr", "Gender": "male", "Age": "93", "Telephone": "276-4025", "ID": "", "First name": "Wesley", "Address": "800 Grenoble Way Norwell MA", "Contact for future races": "no", "Email": "thoth@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Chapman", "Gender": "female", "Age": "98", "Telephone": "408-1852", "ID": "", "First name": "Jo", "Address": "134 Disciplines St. Quincy MA", "Contact for future races": "yes", "Email": "chaperons@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Carroll", "Gender": "male", "Age": "91", "Telephone": "669-8413", "ID": "", "First name": "Francis", "Address": "846 Divergent Pkwy. Norwell MA", "Contact for future races": "no", "Email": "gs@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Garner", "Gender": "male", "Age": "26", "Telephone": "519-9690", "ID": "", "First name": "Darryl", "Address": "273 Taylors St. Braintree MA", "Contact for future races": "yes", "Email": "diffidences@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Tate", "Gender": "male", "Age": "61", "Telephone": "592-7372", "ID": "", "First name": "Alexander", "Address": "797 Moos St. Hingham MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "church", "Last name": "Cobb", "Gender": "male", "Age": "10", "Telephone": "656-4437", "ID": "", "First name": "Salvador", "Address": "437 Spire Way Braintree MA", "Contact for future races": "no", "Email": "rosella@hotmail.com"}, {"How did you hear about race": "", "Last name": "Rivera", "Gender": "male", "Age": "44", "Telephone": "408-7735", "ID": "", "First name": "Jack", "Address": "25 Chaucers Dr. Hanover MA", "Contact for future races": "yes", "Email": "mackinac@hotmail.com"}, {"How did you hear about race": "", "Last name": "Wall", "Gender": "", "Age": "39", "Telephone": "291-7476", "ID": "", "First name": "Gordon", "Address": "791 Didnt St. Hanover MA", "Contact for future races": "yes", "Email": "align@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Trujillo", "Gender": "female", "Age": "75", "Telephone": "110-9369", "ID": "", "First name": "Grace", "Address": "495 Interweaves Ln. Braintree MA", "Contact for future races": "yes", "Email": "perm@gmail.com"}, {"How did you hear about race": "school", "Last name": "Mckinney", "Gender": "female", "Age": "35", "Telephone": "498-5639", "ID": "", "First name": "Jacqueline", "Address": "794 Roadways St. Quincy MA", "Contact for future races": "no", "Email": "deck@gmail.com"}, {"How did you hear about race": "school", "Last name": "Ferguson", "Gender": "male", "Age": "26", "Telephone": "227-3650", "ID": "", "First name": "Bradley", "Address": "61 Ronnies Pkwy. Braintree MA", "Contact for future races": "no", "Email": "proficiency@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Gregory", "Gender": "female", "Age": "14", "Telephone": "871-6143", "ID": "", "First name": "Selma", "Address": "423 Heresies St. Hingham MA", "Contact for future races": "yes", "Email": "coifs@gmail.com"}, {"How did you hear about race": "church", "Last name": "Dominguez", "Gender": "male", "Age": "89", "Telephone": "766-1988", "ID": "", "First name": "Duane", "Address": "465 Lards St. Hanover MA", "Contact for future races": "no", "Email": "besoms@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Lester", "Gender": "male", "Age": "73", "Telephone": "650-3861", "ID": "", "First name": "Floyd", "Address": "140 Clayiest Dr. Braintree MA", "Contact for future races": "no", "Email": "iciest@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Aguilar", "Gender": "female", "Age": "62", "Telephone": "493-5035", "ID": "", "First name": "Lina", "Address": "850 Fluff Dr. Quincy MA", "Contact for future races": "", "Email": "sluggishly@gmail.com"}, {"How did you hear about race": "", "Last name": "Bates", "Gender": "female", "Age": "80", "Telephone": "545-2549", "ID": "", "First name": "Vilma", "Address": "658 Reeks Dr. Norwell MA", "Contact for future races": "yes", "Email": "boys@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Barrera", "Gender": "male", "Age": "38", "Telephone": "223-2927", "ID": "", "First name": "Ted", "Address": "275 Fizzled St. Quincy MA", "Contact for future races": "no", "Email": "inexpedient@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Turner", "Gender": "male", "Age": "39", "Telephone": "693-2491", "ID": "", "First name": "Jacob", "Address": "153 Menders Pkwy. Norwell MA", "Contact for future races": "no", "Email": "burrows@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Saunders", "Gender": "male", "Age": "49", "Telephone": "776-8797", "ID": "", "First name": "Francisco", "Address": "406 Backer Dr. Hingham MA", "Contact for future races": "", "Email": "brashly@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Norton", "Gender": "female", "Age": "81", "Telephone": "192-9937", "ID": "", "First name": "Sheri", "Address": "560 Flanneled Ln. Braintree MA", "Contact for future races": "yes", "Email": "mantelpieces@hotmail.com"}, {"How did you hear about race": "school", "Last name": "", "Gender": "", "Age": "6", "Telephone": "", "ID": "", "First name": "Brett", "Address": "811 Ensnares Way Weymouth MA", "Contact for future races": "yes", "Email": "roadshow@gmail.com"}, {"How did you hear about race": "", "Last name": "Dunn", "Gender": "male", "Age": "76", "Telephone": "866-9441", "ID": "", "First name": "Allan", "Address": "851 Stuffiness Pkwy. Quincy MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "came last year", "Last name": "Hester", "Gender": "", "Age": "53", "Telephone": "922-8066", "ID": "", "First name": "Joseph", "Address": "672 Norberto St. Norwell MA", "Contact for future races": "yes", "Email": "amman@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Frazier", "Gender": "female", "Age": "73", "Telephone": "938-5363", "ID": "", "First name": "Brittany", "Address": "70 Integrates St. Hingham MA", "Contact for future races": "yes", "Email": "hibernated@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Hensley", "Gender": "female", "Age": "93", "Telephone": "645-1141", "ID": "", "First name": "Leanna", "Address": "457 Potting Dr. Hingham MA", "Contact for future races": "yes", "Email": "albumens@gmail.com"}, {"How did you hear about race": "church", "Last name": "Waters", "Gender": "female", "Age": "81", "Telephone": "131-4460", "ID": "", "First name": "Lolita", "Address": "266 Grownups Dr. Quincy MA", "Contact for future races": "yes", "Email": "suppuration@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Ashley", "Gender": "male", "Age": "26", "Telephone": "999-7158", "ID": "", "First name": "Chad", "Address": "390 Milquetoasts St. Weymouth MA", "Contact for future races": "yes", "Email": "malory@gmail.com"}, {"How did you hear about race": "school", "Last name": "Franco", "Gender": "female", "Age": "67", "Telephone": "194-4127", "ID": "", "First name": "Nita", "Address": "", "Contact for future races": "yes", "Email": "unlatch@gmail.com"}, {"How did you hear about race": "school", "Last name": "Stout", "Gender": "male", "Age": "34", "Telephone": "724-6113", "ID": "", "First name": "Ivan", "Address": "306 Discontinuous St. Quincy MA", "Contact for future races": "", "Email": "fishy@hotmail.com"}, {"How did you hear about race": "", "Last name": "Sampson", "Gender": "male", "Age": "97", "Telephone": "439-8374", "ID": "", "First name": "Gene", "Address": "86 Poses St. Norwell MA", "Contact for future races": "no", "Email": "cocoanut@hotmail.com"}, {"How did you hear about race": "", "Last name": "Bauer", "Gender": "female", "Age": "49", "Telephone": "415-3496", "ID": "", "First name": "Lacy", "Address": "55 Jeffersons Pkwy. Quincy MA", "Contact for future races": "no", "Email": "encroachments@hotmail.com"}, {"How did you hear about race": "", "Last name": "Hunter", "Gender": "female", "Age": "90", "Telephone": "333-2869", "ID": "", "First name": "Patty", "Address": "377 Terrorism St. Quincy MA", "Contact for future races": "yes", "Email": "downstream@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mcclure", "Gender": "female", "Age": "70", "Telephone": "437-4320", "ID": "", "First name": "Beverley", "Address": "421 Silents St. Braintree MA", "Contact for future races": "yes", "Email": "florentine@hotmail.com"}, {"How did you hear about race": "", "Last name": "Langley", "Gender": "female", "Age": "93", "Telephone": "938-7748", "ID": "", "First name": "Kayla", "Address": "491 Paps Dr. Hanover MA", "Contact for future races": "yes", "Email": "waking@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Dejesus", "Gender": "female", "Age": "89", "Telephone": "597-7272", "ID": "", "First name": "Taylor", "Address": "659 Shanes St. Norwell MA", "Contact for future races": "", "Email": "southwestern@hotmail.com"}, {"How did you hear about race": "", "Last name": "Lambert", "Gender": "female", "Age": "32", "Telephone": "498-9249", "ID": "", "First name": "Lynn", "Address": "753 Demise Way Braintree MA", "Contact for future races": "no", "Email": "farewells@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mooney", "Gender": "female", "Age": "54", "Telephone": "990-1243", "ID": "", "First name": "Amelia", "Address": "782 Mellifluous Dr. Norwell MA", "Contact for future races": "no", "Email": "dwarfs@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Sherman", "Gender": "", "Age": "93", "Telephone": "588-1510", "ID": "", "First name": "Yvette", "Address": "691 Sandalwoods Ln. Quincy MA", "Contact for future races": "no", "Email": "prominent@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Bishop", "Gender": "male", "Age": "26", "Telephone": "523-7267", "ID": "", "First name": "Elmer", "Address": "140 Perpetrates St. Weymouth MA", "Contact for future races": "no", "Email": "panmunjom@gmail.com"}, {"How did you hear about race": "church", "Last name": "Cochran", "Gender": "male", "Age": "10", "Telephone": "391-4286", "ID": "", "First name": "Shawn", "Address": "620 Caleb Dr. Hingham MA", "Contact for future races": "no", "Email": "thumbtack@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Lewis", "Gender": "male", "Age": "72", "Telephone": "809-6838", "ID": "", "First name": "Antonio", "Address": "584 Apologys St. Quincy MA", "Contact for future races": "no", "Email": "faithlessness@gmail.com"}, {"How did you hear about race": "school", "Last name": "Lynn", "Gender": "female", "Age": "13", "Telephone": "152-2090", "ID": "", "First name": "Rosario", "Address": "442 Unprompted Dr. Quincy MA", "Contact for future races": "yes", "Email": "lancashire@gmail.com"}, {"How did you hear about race": "", "Last name": "Moody", "Gender": "male", "Age": "2", "Telephone": "875-5767", "ID": "", "First name": "", "Address": "669 Styrofoams Dr. Braintree MA", "Contact for future races": "no", "Email": "fundamentals@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Callahan", "Gender": "male", "Age": "61", "Telephone": "353-5328", "ID": "", "First name": "Neil", "Address": "139 Russel St. Quincy MA", "Contact for future races": "no", "Email": "maharani@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Salinas", "Gender": "male", "Age": "18", "Telephone": "", "ID": "", "First name": "Henry", "Address": "580 Tan St. Hingham MA", "Contact for future races": "", "Email": "crunches@gmail.com"}, {"How did you hear about race": "church", "Last name": "Pruitt", "Gender": "male", "Age": "53", "Telephone": "787-5268", "ID": "", "First name": "", "Address": "801 Daimler St. Weymouth MA", "Contact for future races": "yes", "Email": "milder@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Chandler", "Gender": "male", "Age": "5", "Telephone": "992-4429", "ID": "", "First name": "", "Address": "956 Ricocheting Dr. Quincy MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Gibbs", "Gender": "male", "Age": "16", "Telephone": "792-2391", "ID": "", "First name": "Clarence", "Address": "638 Antipodes Dr. Norwell MA", "Contact for future races": "no", "Email": "circulations@gmail.com"}, {"How did you hear about race": "church", "Last name": "Mosley", "Gender": "female", "Age": "85", "Telephone": "318-8553", "ID": "", "First name": "Sallie", "Address": "862 Slims Dr. Hingham MA", "Contact for future races": "no", "Email": "viral@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Tyson", "Gender": "female", "Age": "39", "Telephone": "136-6452", "ID": "", "First name": "Tommie", "Address": "46 Sandblasting St. Quincy MA", "Contact for future races": "yes", "Email": "punctured@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Beck", "Gender": "female", "Age": "81", "Telephone": "175-7962", "ID": "", "First name": "Jerri", "Address": "144 Fagots Ln. Hanover MA", "Contact for future races": "no", "Email": "neglects@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Kramer", "Gender": "male", "Age": "68", "Telephone": "915-8660", "ID": "", "First name": "Jimmy", "Address": "68 Anaesthesias Ln. Braintree MA", "Contact for future races": "yes", "Email": "grossly@gmail.com"}, {"How did you hear about race": "", "Last name": "Lowery", "Gender": "female", "Age": "5", "Telephone": "434-9190", "ID": "", "First name": "Jerri", "Address": "42 Rodents Pkwy. Braintree MA", "Contact for future races": "yes", "Email": "manpowers@gmail.com"}, {"How did you hear about race": "school", "Last name": "Hopper", "Gender": "female", "Age": "75", "Telephone": "436-6266", "ID": "", "First name": "Melba", "Address": "288 Batted Ln. Weymouth MA", "Contact for future races": "no", "Email": "forever@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Santana", "Gender": "male", "Age": "34", "Telephone": "794-4205", "ID": "", "First name": "", "Address": "371 Eula Dr. Weymouth MA", "Contact for future races": "no", "Email": "consists@hotmail.com"}, {"How did you hear about race": "", "Last name": "Lowery", "Gender": "male", "Age": "98", "Telephone": "677-2508", "ID": "", "First name": "Wallace", "Address": "862 Chirps Ln. Hanover MA", "Contact for future races": "no", "Email": "boozes@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Shaw", "Gender": "", "Age": "40", "Telephone": "524-5604", "ID": "", "First name": "", "Address": "", "Contact for future races": "yes", "Email": "erratically@gmail.com"}, {"How did you hear about race": "church", "Last name": "Douglas", "Gender": "female", "Age": "80", "Telephone": "113-4050", "ID": "", "First name": "Lillian", "Address": "919 Di St. Quincy MA", "Contact for future races": "no", "Email": "goofed@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Gallegos", "Gender": "male", "Age": "37", "Telephone": "702-2621", "ID": "", "First name": "Clifton", "Address": "339 Hibernia Ln. Braintree MA", "Contact for future races": "yes", "Email": "truthfulnesss@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Booker", "Gender": "male", "Age": "46", "Telephone": "942-7700", "ID": "", "First name": "Clarence", "Address": "195 Evictions St. Hingham MA", "Contact for future races": "yes", "Email": "britchess@gmail.com"}, {"How did you hear about race": "", "Last name": "Kline", "Gender": "male", "Age": "22", "Telephone": "504-4909", "ID": "", "First name": "Ray", "Address": "270 Launches Ln. Hanover MA", "Contact for future races": "yes", "Email": "glided@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Rodriquez", "Gender": "male", "Age": "95", "Telephone": "186-4425", "ID": "", "First name": "Darrell", "Address": "843 Chewer Pkwy. Quincy MA", "Contact for future races": "no", "Email": "pleases@gmail.com"}, {"How did you hear about race": "", "Last name": "Franklin", "Gender": "male", "Age": "33", "Telephone": "573-7886", "ID": "", "First name": "Mike", "Address": "524 Abut St. Hingham MA", "Contact for future races": "yes", "Email": "swain@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Irwin", "Gender": "male", "Age": "99", "Telephone": "439-1975", "ID": "", "First name": "Terrence", "Address": "", "Contact for future races": "no", "Email": "loonys@hotmail.com"}, {"How did you hear about race": "", "Last name": "Baxter", "Gender": "male", "Age": "89", "Telephone": "968-1590", "ID": "", "First name": "Gordon", "Address": "893 Paws St. Hingham MA", "Contact for future races": "no", "Email": "altars@gmail.com"}, {"How did you hear about race": "church", "Last name": "Potts", "Gender": "male", "Age": "37", "Telephone": "", "ID": "", "First name": "Herman", "Address": "489 Mannishness St. Norwell MA", "Contact for future races": "no", "Email": "favoritisms@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Pearson", "Gender": "female", "Age": "8", "Telephone": "721-7059", "ID": "", "First name": "Lorie", "Address": "269 Shaykhs St. Norwell MA", "Contact for future races": "yes", "Email": "jaundices@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mercer", "Gender": "male", "Age": "63", "Telephone": "199-3070", "ID": "", "First name": "Roy", "Address": "665 Transfigure St. Hanover MA", "Contact for future races": "yes", "Email": "billionth@hotmail.com"}, {"How did you hear about race": "", "Last name": "Collins", "Gender": "female", "Age": "58", "Telephone": "710-4341", "ID": "", "First name": "Sheena", "Address": "162 Ceremonys Way Hanover MA", "Contact for future races": "yes", "Email": "compartmentalize@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Strong", "Gender": "male", "Age": "44", "Telephone": "492-7362", "ID": "", "First name": "Virgil", "Address": "791 Cowering St. Braintree MA", "Contact for future races": "no", "Email": "ransack@gmail.com"}, {"How did you hear about race": "school", "Last name": "Collins", "Gender": "", "Age": "18", "Telephone": "417-9706", "ID": "", "First name": "Roslyn", "Address": "998 Tabernacle St. Hanover MA", "Contact for future races": "yes", "Email": "infinitive@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Christensen", "Gender": "female", "Age": "53", "Telephone": "249-9619", "ID": "", "First name": "Katina", "Address": "729 Asymptotic Way Hingham MA", "Contact for future races": "yes", "Email": "eva@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Gillespie", "Gender": "male", "Age": "57", "Telephone": "", "ID": "", "First name": "Ryan", "Address": "319 Creations Ln. Braintree MA", "Contact for future races": "no", "Email": "phosphor@gmail.com"}, {"How did you hear about race": "", "Last name": "Bowman", "Gender": "female", "Age": "75", "Telephone": "219-4452", "ID": "", "First name": "Marla", "Address": "64 Prerequisite Way Hanover MA", "Contact for future races": "yes", "Email": "seals@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Brock", "Gender": "female", "Age": "45", "Telephone": "369-9423", "ID": "", "First name": "Lee", "Address": "640 Entrenchment St. Hanover MA", "Contact for future races": "no", "Email": "borodin@gmail.com"}, {"How did you hear about race": "school", "Last name": "Hendrix", "Gender": "male", "Age": "25", "Telephone": "301-3544", "ID": "", "First name": "James", "Address": "278 Stampedes Way Hingham MA", "Contact for future races": "yes", "Email": "deidre@hotmail.com"}, {"How did you hear about race": "", "Last name": "Powers", "Gender": "male", "Age": "42", "Telephone": "343-9678", "ID": "", "First name": "Richard", "Address": "601 Juliettes St. Norwell MA", "Contact for future races": "no", "Email": "ch\u00e2teaux@gmail.com"}, {"How did you hear about race": "", "Last name": "Delaney", "Gender": "male", "Age": "73", "Telephone": "522-7785", "ID": "", "First name": "Stanley", "Address": "803 Fighter St. Norwell MA", "Contact for future races": "yes", "Email": "overpriced@gmail.com"}, {"How did you hear about race": "school", "Last name": "Robbins", "Gender": "male", "Age": "40", "Telephone": "696-1243", "ID": "", "First name": "", "Address": "576 Obstructs Ln. Braintree MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Allen", "Gender": "male", "Age": "56", "Telephone": "558-2657", "ID": "", "First name": "Ken", "Address": "928 Cloisters Ln. Hanover MA", "Contact for future races": "", "Email": "tangerines@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "", "Gender": "male", "Age": "41", "Telephone": "259-1818", "ID": "", "First name": "Wayne", "Address": "907 Alkaloids St. Weymouth MA", "Contact for future races": "no", "Email": "idols@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Simmons", "Gender": "female", "Age": "67", "Telephone": "118-2816", "ID": "", "First name": "Christy", "Address": "203 Actinium St. Quincy MA", "Contact for future races": "no", "Email": "sapient@gmail.com"}, {"How did you hear about race": "school", "Last name": "Aguirre", "Gender": "male", "Age": "38", "Telephone": "333-9921", "ID": "", "First name": "Troy", "Address": "653 Mickys Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "excavate@gmail.com"}, {"How did you hear about race": "school", "Last name": "Garrett", "Gender": "", "Age": "92", "Telephone": "235-2233", "ID": "", "First name": "Alan", "Address": "56 Kookiness St. Hanover MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Chaney", "Gender": "male", "Age": "64", "Telephone": "", "ID": "", "First name": "Kyle", "Address": "69 Abate Way Hingham MA", "Contact for future races": "no", "Email": "commentated@gmail.com"}, {"How did you hear about race": "", "Last name": "Finch", "Gender": "male", "Age": "65", "Telephone": "537-9703", "ID": "", "First name": "John", "Address": "233 Fractured Dr. Braintree MA", "Contact for future races": "", "Email": "bart\u00f3k@hotmail.com"}, {"How did you hear about race": "", "Last name": "Pace", "Gender": "male", "Age": "61", "Telephone": "455-2382", "ID": "", "First name": "Willie", "Address": "74 Cynically St. Norwell MA", "Contact for future races": "yes", "Email": "bridalveil@gmail.com"}, {"How did you hear about race": "", "Last name": "Koch", "Gender": "male", "Age": "52", "Telephone": "130-1420", "ID": "", "First name": "Erik", "Address": "796 Caw Way Norwell MA", "Contact for future races": "no", "Email": "preceptors@gmail.com"}, {"How did you hear about race": "", "Last name": "Reese", "Gender": "male", "Age": "74", "Telephone": "946-3974", "ID": "", "First name": "James", "Address": "592 Nastinesss St. Quincy MA", "Contact for future races": "no", "Email": "integritys@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Petty", "Gender": "female", "Age": "48", "Telephone": "694-2112", "ID": "", "First name": "Claudine", "Address": "305 Nonplused Pkwy. Braintree MA", "Contact for future races": "yes", "Email": "pebbling@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Freeman", "Gender": "female", "Age": "33", "Telephone": "387-8588", "ID": "", "First name": "Carole", "Address": "646 Perfume St. Quincy MA", "Contact for future races": "yes", "Email": "mermaids@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Knowles", "Gender": "female", "Age": "63", "Telephone": "911-7387", "ID": "", "First name": "Claudette", "Address": "822 Dewar St. Braintree MA", "Contact for future races": "yes", "Email": "disprove@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Schmidt", "Gender": "male", "Age": "21", "Telephone": "980-7804", "ID": "", "First name": "", "Address": "323 Hodgkin Ln. Braintree MA", "Contact for future races": "no", "Email": "unending@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Jarvis", "Gender": "male", "Age": "56", "Telephone": "726-8958", "ID": "", "First name": "", "Address": "147 Lammed Ln. Quincy MA", "Contact for future races": "yes", "Email": "charred@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Hatfield", "Gender": "male", "Age": "46", "Telephone": "737-6596", "ID": "", "First name": "Jay", "Address": "702 Farce Ln. Hingham MA", "Contact for future races": "no", "Email": "hacksaw@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Wyatt", "Gender": "male", "Age": "1", "Telephone": "988-3955", "ID": "", "First name": "Jeremy", "Address": "51 Campgrounds St. Weymouth MA", "Contact for future races": "yes", "Email": "odin@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Sampson", "Gender": "female", "Age": "63", "Telephone": "581-5441", "ID": "", "First name": "Keri", "Address": "32 Baker St. Norwell MA", "Contact for future races": "no", "Email": "pole@gmail.com"}, {"How did you hear about race": "", "Last name": "Ferrell", "Gender": "male", "Age": "2", "Telephone": "521-6149", "ID": "", "First name": "Johnnie", "Address": "11 Retreading St. Weymouth MA", "Contact for future races": "yes", "Email": "computes@gmail.com"}, {"How did you hear about race": "church", "Last name": "Soto", "Gender": "female", "Age": "59", "Telephone": "212-8247", "ID": "", "First name": "Anne", "Address": "155 Accrue Ln. Weymouth MA", "Contact for future races": "yes", "Email": "homogeneously@hotmail.com"}, {"How did you hear about race": "", "Last name": "Newton", "Gender": "female", "Age": "6", "Telephone": "891-5413", "ID": "", "First name": "Bernadette", "Address": "632 Colliers Ln. Weymouth MA", "Contact for future races": "yes", "Email": "roundhouse@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Trujillo", "Gender": "male", "Age": "77", "Telephone": "493-8127", "ID": "", "First name": "Armando", "Address": "360 Follicles Dr. Weymouth MA", "Contact for future races": "no", "Email": "collation@hotmail.com"}, {"How did you hear about race": "", "Last name": "Reyes", "Gender": "female", "Age": "63", "Telephone": "413-8459", "ID": "", "First name": "Susie", "Address": "181 Loth Way Braintree MA", "Contact for future races": "yes", "Email": "weatherizes@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Cochran", "Gender": "male", "Age": "29", "Telephone": "681-5192", "ID": "", "First name": "Ronnie", "Address": "", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "church", "Last name": "Church", "Gender": "female", "Age": "81", "Telephone": "357-2915", "ID": "", "First name": "Esmeralda", "Address": "526 Discouraging St. Braintree MA", "Contact for future races": "yes", "Email": "belongings@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Howard", "Gender": "male", "Age": "57", "Telephone": "241-9159", "ID": "", "First name": "Casey", "Address": "645 Recruitments St. Norwell MA", "Contact for future races": "no", "Email": "hebrides@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Chen", "Gender": "male", "Age": "34", "Telephone": "965-4095", "ID": "", "First name": "Angel", "Address": "743 Waterloos Ln. Weymouth MA", "Contact for future races": "yes", "Email": "generals@gmail.com"}, {"How did you hear about race": "", "Last name": "Middleton", "Gender": "male", "Age": "59", "Telephone": "308-5930", "ID": "", "First name": "", "Address": "", "Contact for future races": "no", "Email": "austerer@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Roth", "Gender": "female", "Age": "56", "Telephone": "265-1091", "ID": "", "First name": "Lynnette", "Address": "680 Tablecloth St. Hingham MA", "Contact for future races": "no", "Email": "stratospheres@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Shaw", "Gender": "female", "Age": "44", "Telephone": "351-6730", "ID": "", "First name": "Maryellen", "Address": "860 Lockouts Ln. Quincy MA", "Contact for future races": "yes", "Email": "downtimes@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Wade", "Gender": "male", "Age": "12", "Telephone": "556-1182", "ID": "", "First name": "Seth", "Address": "760 Candides St. Quincy MA", "Contact for future races": "no", "Email": "narrate@hotmail.com"}, {"How did you hear about race": "", "Last name": "Hogan", "Gender": "male", "Age": "40", "Telephone": "153-1181", "ID": "", "First name": "Terrence", "Address": "", "Contact for future races": "yes", "Email": "woolliest@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Craig", "Gender": "female", "Age": "82", "Telephone": "919-1206", "ID": "", "First name": "Marisa", "Address": "809 Thronging Dr. Hingham MA", "Contact for future races": "yes", "Email": "obeyed@gmail.com"}, {"How did you hear about race": "", "Last name": "Keller", "Gender": "female", "Age": "32", "Telephone": "372-1339", "ID": "", "First name": "Ruthie", "Address": "983 Anticlockwise Ln. Quincy MA", "Contact for future races": "yes", "Email": "divvys@gmail.com"}, {"How did you hear about race": "", "Last name": "Weeks", "Gender": "male", "Age": "64", "Telephone": "436-5368", "ID": "", "First name": "Charles", "Address": "", "Contact for future races": "yes", "Email": "stand@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Snow", "Gender": "female", "Age": "45", "Telephone": "548-9589", "ID": "", "First name": "Brandi", "Address": "181 Proportionally Way Norwell MA", "Contact for future races": "yes", "Email": "employers@gmail.com"}, {"How did you hear about race": "", "Last name": "Dillon", "Gender": "male", "Age": "86", "Telephone": "262-3093", "ID": "", "First name": "", "Address": "539 Clairvoyant Ln. Weymouth MA", "Contact for future races": "yes", "Email": "acupuncturists@hotmail.com"}, {"How did you hear about race": "", "Last name": "Russo", "Gender": "female", "Age": "71", "Telephone": "413-6257", "ID": "", "First name": "Francesca", "Address": "147 Recipients St. Hanover MA", "Contact for future races": "yes", "Email": "betook@gmail.com"}, {"How did you hear about race": "", "Last name": "Tillman", "Gender": "female", "Age": "27", "Telephone": "487-7747", "ID": "", "First name": "Dolores", "Address": "480 Lockwoods Dr. Hanover MA", "Contact for future races": "yes", "Email": "drab@gmail.com"}, {"How did you hear about race": "church", "Last name": "Huber", "Gender": "male", "Age": "34", "Telephone": "203-6923", "ID": "", "First name": "Victor", "Address": "981 Hatters Ln. Hingham MA", "Contact for future races": "yes", "Email": "washouts@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Rice", "Gender": "female", "Age": "44", "Telephone": "103-3377", "ID": "", "First name": "Ivy", "Address": "579 Chairman St. Weymouth MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "came last year", "Last name": "Mosley", "Gender": "male", "Age": "7", "Telephone": "888-5828", "ID": "", "First name": "Tom", "Address": "88 Serials Dr. Quincy MA", "Contact for future races": "", "Email": "gouts@gmail.com"}, {"How did you hear about race": "school", "Last name": "Flores", "Gender": "female", "Age": "20", "Telephone": "416-7584", "ID": "", "First name": "Helene", "Address": "801 Pandas Pkwy. Quincy MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "a friend", "Last name": "Roy", "Gender": "male", "Age": "22", "Telephone": "122-9209", "ID": "", "First name": "Joseph", "Address": "257 Bursitis St. Hanover MA", "Contact for future races": "yes", "Email": "hypothalami@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Keller", "Gender": "male", "Age": "37", "Telephone": "768-1377", "ID": "", "First name": "Marvin", "Address": "432 Pickles Way Weymouth MA", "Contact for future races": "yes", "Email": "caterwauls@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Guerra", "Gender": "male", "Age": "55", "Telephone": "187-5248", "ID": "", "First name": "Felix", "Address": "516 Duress St. Weymouth MA", "Contact for future races": "no", "Email": "critter@gmail.com"}, {"How did you hear about race": "school", "Last name": "Simpson", "Gender": "male", "Age": "66", "Telephone": "598-5775", "ID": "", "First name": "Eddie", "Address": "222 Disinfectants Ln. Hanover MA", "Contact for future races": "yes", "Email": "roughhouses@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Gilliam", "Gender": "female", "Age": "33", "Telephone": "", "ID": "", "First name": "Liliana", "Address": "596 Morals St. Braintree MA", "Contact for future races": "no", "Email": "staffs@gmail.com"}, {"How did you hear about race": "church", "Last name": "Keller", "Gender": "male", "Age": "34", "Telephone": "566-6166", "ID": "", "First name": "Jared", "Address": "657 Recursion Pkwy. Norwell MA", "Contact for future races": "yes", "Email": "plasticines@gmail.com"}, {"How did you hear about race": "", "Last name": "Reilly", "Gender": "", "Age": "13", "Telephone": "601-5340", "ID": "", "First name": "Cheryl", "Address": "", "Contact for future races": "", "Email": "muckier@gmail.com"}, {"How did you hear about race": "school", "Last name": "Mclaughlin", "Gender": "female", "Age": "46", "Telephone": "958-4711", "ID": "", "First name": "Shawna", "Address": "367 D\u00e9Railleur Dr. Braintree MA", "Contact for future races": "", "Email": "prolongations@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Berg", "Gender": "female", "Age": "53", "Telephone": "", "ID": "", "First name": "", "Address": "822 Chemicals Way Braintree MA", "Contact for future races": "no", "Email": "heliums@gmail.com"}, {"How did you hear about race": "school", "Last name": "Kelley", "Gender": "male", "Age": "77", "Telephone": "973-3089", "ID": "", "First name": "Karl", "Address": "58 Babbling St. Norwell MA", "Contact for future races": "yes", "Email": "replacing@hotmail.com"}, {"How did you hear about race": "", "Last name": "Hurley", "Gender": "male", "Age": "45", "Telephone": "700-8246", "ID": "", "First name": "Randy", "Address": "806 Gassy Ln. Braintree MA", "Contact for future races": "no", "Email": "yous@gmail.com"}, {"How did you hear about race": "school", "Last name": "Lawrence", "Gender": "", "Age": "74", "Telephone": "796-4410", "ID": "", "First name": "Lonnie", "Address": "41 Hovhaness Dr. Braintree MA", "Contact for future races": "yes", "Email": "victimizing@gmail.com"}, {"How did you hear about race": "", "Last name": "Gentry", "Gender": "male", "Age": "7", "Telephone": "175-3952", "ID": "", "First name": "Ernest", "Address": "331 Anemones Ln. Norwell MA", "Contact for future races": "no", "Email": "appositely@gmail.com"}, {"How did you hear about race": "school", "Last name": "Sullivan", "Gender": "female", "Age": "5", "Telephone": "978-3295", "ID": "", "First name": "Helen", "Address": "666 Outside Way Quincy MA", "Contact for future races": "yes", "Email": "jainism@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Sampson", "Gender": "female", "Age": "44", "Telephone": "409-8729", "ID": "", "First name": "Wendy", "Address": "265 Wealthy St. Weymouth MA", "Contact for future races": "yes", "Email": "moderns@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Ferguson", "Gender": "male", "Age": "42", "Telephone": "315-2604", "ID": "", "First name": "Cory", "Address": "", "Contact for future races": "yes", "Email": "castrations@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Harmon", "Gender": "female", "Age": "80", "Telephone": "284-2421", "ID": "", "First name": "Lorrie", "Address": "", "Contact for future races": "no", "Email": "crick@hotmail.com"}, {"How did you hear about race": "", "Last name": "Slater", "Gender": "female", "Age": "79", "Telephone": "571-8884", "ID": "", "First name": "Rosalyn", "Address": "630 Avalanche Ln. Quincy MA", "Contact for future races": "no", "Email": "kickoff@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Galloway", "Gender": "male", "Age": "84", "Telephone": "185-1991", "ID": "", "First name": "Jared", "Address": "341 Castors St. Quincy MA", "Contact for future races": "yes", "Email": "sensibility@gmail.com"}, {"How did you hear about race": "", "Last name": "Blanchard", "Gender": "male", "Age": "41", "Telephone": "", "ID": "", "First name": "Tyrone", "Address": "20 Poland St. Hingham MA", "Contact for future races": "", "Email": "metastasized@gmail.com"}, {"How did you hear about race": "", "Last name": "Cash", "Gender": "male", "Age": "62", "Telephone": "338-9867", "ID": "", "First name": "Ronald", "Address": "17 Lichen Way Quincy MA", "Contact for future races": "", "Email": "jackrabbits@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "", "Gender": "", "Age": "97", "Telephone": "749-4359", "ID": "", "First name": "", "Address": "255 Hondurans St. Braintree MA", "Contact for future races": "no", "Email": "exclusivitys@gmail.com"}, {"How did you hear about race": "", "Last name": "Conway", "Gender": "female", "Age": "75", "Telephone": "825-2093", "ID": "", "First name": "Eugenia", "Address": "276 Gringos Pkwy. Hingham MA", "Contact for future races": "no", "Email": "marauded@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Underwood", "Gender": "male", "Age": "26", "Telephone": "936-8585", "ID": "", "First name": "Allan", "Address": "52 Reputing St. Weymouth MA", "Contact for future races": "yes", "Email": "vanquishes@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Molina", "Gender": "", "Age": "9", "Telephone": "639-1284", "ID": "", "First name": "Maryann", "Address": "329 Manipulates St. Quincy MA", "Contact for future races": "yes", "Email": "rosie@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Montoya", "Gender": "male", "Age": "26", "Telephone": "507-5663", "ID": "", "First name": "Jesus", "Address": "56 Kline St. Weymouth MA", "Contact for future races": "yes", "Email": "postmarked@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Hudson", "Gender": "female", "Age": "35", "Telephone": "343-6516", "ID": "", "First name": "Fanny", "Address": "306 Banister Way Hingham MA", "Contact for future races": "yes", "Email": "offenders@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Goff", "Gender": "male", "Age": "14", "Telephone": "294-8433", "ID": "", "First name": "Norman", "Address": "291 Declared Way Hingham MA", "Contact for future races": "no", "Email": "bobsledded@hotmail.com"}, {"How did you hear about race": "", "Last name": "Wooten", "Gender": "female", "Age": "7", "Telephone": "166-3761", "ID": "", "First name": "Marcella", "Address": "203 Spiritualisms Dr. Norwell MA", "Contact for future races": "no", "Email": "autobiographys@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Beasley", "Gender": "female", "Age": "82", "Telephone": "120-4995", "ID": "", "First name": "Letitia", "Address": "905 Handily St. Quincy MA", "Contact for future races": "yes", "Email": "incompleteness@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mccarty", "Gender": "male", "Age": "82", "Telephone": "782-7891", "ID": "", "First name": "Alberto", "Address": "282 Moral St. Quincy MA", "Contact for future races": "yes", "Email": "sedate@gmail.com"}, {"How did you hear about race": "", "Last name": "Hobbs", "Gender": "female", "Age": "55", "Telephone": "208-1169", "ID": "", "First name": "Andrea", "Address": "150 Soloing Dr. Braintree MA", "Contact for future races": "yes", "Email": "pulsars@gmail.com"}, {"How did you hear about race": "school", "Last name": "Alvarez", "Gender": "male", "Age": "86", "Telephone": "966-6128", "ID": "", "First name": "Alex", "Address": "206 Ravishes Ln. Hanover MA", "Contact for future races": "no", "Email": "bobsledded@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Booker", "Gender": "female", "Age": "50", "Telephone": "316-9798", "ID": "", "First name": "Lorna", "Address": "338 Denouncements Pkwy. Norwell MA", "Contact for future races": "no", "Email": "notebooks@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Robinson", "Gender": "male", "Age": "51", "Telephone": "728-3629", "ID": "", "First name": "Ian", "Address": "349 Roil St. Quincy MA", "Contact for future races": "yes", "Email": "ragged@hotmail.com"}, {"How did you hear about race": "", "Last name": "Floyd", "Gender": "male", "Age": "65", "Telephone": "101-4526", "ID": "", "First name": "Cody", "Address": "880 Revel St. Quincy MA", "Contact for future races": "yes", "Email": "sol@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Wiggins", "Gender": "male", "Age": "41", "Telephone": "793-1352", "ID": "", "First name": "Stephen", "Address": "737 Scamps St. Norwell MA", "Contact for future races": "yes", "Email": "municipal@gmail.com"}, {"How did you hear about race": "school", "Last name": "Fitzpatrick", "Gender": "male", "Age": "56", "Telephone": "", "ID": "", "First name": "Phillip", "Address": "113 Capra Way Hanover MA", "Contact for future races": "no", "Email": "jutted@gmail.com"}, {"How did you hear about race": "", "Last name": "Shepherd", "Gender": "female", "Age": "14", "Telephone": "709-8154", "ID": "", "First name": "Beverly", "Address": "389 Robuster Dr. Quincy MA", "Contact for future races": "yes", "Email": "overshoes@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Carson", "Gender": "female", "Age": "88", "Telephone": "690-4927", "ID": "", "First name": "Chelsea", "Address": "827 Aliasing St. Weymouth MA", "Contact for future races": "yes", "Email": "samuel@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Kaufman", "Gender": "female", "Age": "67", "Telephone": "520-8929", "ID": "", "First name": "Sheree", "Address": "161 Matador Way Norwell MA", "Contact for future races": "no", "Email": "loggings@gmail.com"}, {"How did you hear about race": "", "Last name": "England", "Gender": "male", "Age": "48", "Telephone": "", "ID": "", "First name": "Patrick", "Address": "468 Altruists St. Quincy MA", "Contact for future races": "", "Email": "scalawags@hotmail.com"}, {"How did you hear about race": "", "Last name": "Lee", "Gender": "female", "Age": "75", "Telephone": "111-6411", "ID": "", "First name": "Ashley", "Address": "965 Magoo Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "excellence@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Case", "Gender": "female", "Age": "82", "Telephone": "694-6913", "ID": "", "First name": "Lois", "Address": "563 Greetings St. Quincy MA", "Contact for future races": "no", "Email": "ernestine@gmail.com"}, {"How did you hear about race": "church", "Last name": "Rasmussen", "Gender": "male", "Age": "82", "Telephone": "103-4540", "ID": "", "First name": "Jorge", "Address": "968 Legalese Pkwy. Norwell MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "school", "Last name": "Mckay", "Gender": "female", "Age": "86", "Telephone": "704-4000", "ID": "", "First name": "Corine", "Address": "193 Arks Ln. Quincy MA", "Contact for future races": "yes", "Email": "mindedness@gmail.com"}, {"How did you hear about race": "", "Last name": "Guerrero", "Gender": "male", "Age": "36", "Telephone": "837-9385", "ID": "", "First name": "Wallace", "Address": "356 Tabbies St. Quincy MA", "Contact for future races": "no", "Email": "concurrences@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mcmillan", "Gender": "male", "Age": "6", "Telephone": "576-6926", "ID": "", "First name": "Wayne", "Address": "322 Refusal St. Braintree MA", "Contact for future races": "yes", "Email": "obdurate@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Lara", "Gender": "male", "Age": "52", "Telephone": "247-3392", "ID": "", "First name": "Reginald", "Address": "24 Waterworks Way Hingham MA", "Contact for future races": "yes", "Email": "bitterns@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Zamora", "Gender": "male", "Age": "46", "Telephone": "830-2324", "ID": "", "First name": "Jonathan", "Address": "615 Liquidators Dr. Quincy MA", "Contact for future races": "yes", "Email": "internships@hotmail.com"}, {"How did you hear about race": "", "Last name": "Gentry", "Gender": "female", "Age": "35", "Telephone": "198-5019", "ID": "", "First name": "", "Address": "355 Gunrunner St. Norwell MA", "Contact for future races": "no", "Email": "flags@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Morrow", "Gender": "male", "Age": "60", "Telephone": "748-3463", "ID": "", "First name": "Andy", "Address": "480 Jalape\u00f1O Ln. Quincy MA", "Contact for future races": "yes", "Email": "divests@hotmail.com"}, {"How did you hear about race": "", "Last name": "Farrell", "Gender": "female", "Age": "54", "Telephone": "858-1927", "ID": "", "First name": "Guadalupe", "Address": "65 Reporter Ln. Weymouth MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "church", "Last name": "Ware", "Gender": "female", "Age": "56", "Telephone": "573-3690", "ID": "", "First name": "Tonya", "Address": "997 Fonts Ln. Hanover MA", "Contact for future races": "no", "Email": "work@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Sherman", "Gender": "male", "Age": "57", "Telephone": "352-2110", "ID": "", "First name": "Marcus", "Address": "255 Inhabitable Pkwy. Braintree MA", "Contact for future races": "no", "Email": "conciliatory@gmail.com"}, {"How did you hear about race": "school", "Last name": "Davenport", "Gender": "male", "Age": "82", "Telephone": "383-1358", "ID": "", "First name": "Daryl", "Address": "470 Tureens Way Weymouth MA", "Contact for future races": "no", "Email": "stats@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mann", "Gender": "male", "Age": "34", "Telephone": "190-5722", "ID": "", "First name": "Erik", "Address": "18 Largenesss St. Weymouth MA", "Contact for future races": "yes", "Email": "jabber@hotmail.com"}, {"How did you hear about race": "church", "Last name": "", "Gender": "male", "Age": "42", "Telephone": "711-1451", "ID": "", "First name": "Darren", "Address": "405 Sauciness Way Braintree MA", "Contact for future races": "yes", "Email": "crackerjacks@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Petty", "Gender": "female", "Age": "17", "Telephone": "805-2002", "ID": "", "First name": "Carolyn", "Address": "228 Sampan Pkwy. Norwell MA", "Contact for future races": "no", "Email": "supporters@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Adams", "Gender": "male", "Age": "62", "Telephone": "203-5083", "ID": "", "First name": "Darrell", "Address": "822 Repossessed St. Hanover MA", "Contact for future races": "yes", "Email": "craggiest@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Kemp", "Gender": "male", "Age": "89", "Telephone": "104-2665", "ID": "", "First name": "Maurice", "Address": "787 Ridgepoles Ln. Hanover MA", "Contact for future races": "no", "Email": "dallying@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Gillespie", "Gender": "female", "Age": "83", "Telephone": "876-3535", "ID": "", "First name": "Gertrude", "Address": "484 Betwixt Ln. Hingham MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "school", "Last name": "Tate", "Gender": "female", "Age": "37", "Telephone": "583-5139", "ID": "", "First name": "Molly", "Address": "457 Perspiring St. Quincy MA", "Contact for future races": "no", "Email": "triples@hotmail.com"}, {"How did you hear about race": "", "Last name": "Payne", "Gender": "male", "Age": "54", "Telephone": "494-7381", "ID": "", "First name": "Clinton", "Address": "265 Abolitionists Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "thoroughly@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Barrera", "Gender": "female", "Age": "31", "Telephone": "175-9167", "ID": "", "First name": "Lily", "Address": "498 Unaided St. Weymouth MA", "Contact for future races": "no", "Email": "cobols@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Harrell", "Gender": "female", "Age": "77", "Telephone": "445-3532", "ID": "", "First name": "Dollie", "Address": "441 Wrongheadedly St. Hingham MA", "Contact for future races": "no", "Email": "gilds@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Clayton", "Gender": "female", "Age": "20", "Telephone": "228-3937", "ID": "", "First name": "Hattie", "Address": "115 Divisive Dr. Hanover MA", "Contact for future races": "no", "Email": "timescales@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Williamson", "Gender": "female", "Age": "65", "Telephone": "990-8987", "ID": "", "First name": "Brooke", "Address": "880 Hostessed St. Hanover MA", "Contact for future races": "", "Email": "grackles@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Fitzpatrick", "Gender": "female", "Age": "48", "Telephone": "921-8494", "ID": "", "First name": "Neva", "Address": "897 Purged St. Norwell MA", "Contact for future races": "yes", "Email": "complaint@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Rosa", "Gender": "male", "Age": "67", "Telephone": "796-5705", "ID": "", "First name": "Willard", "Address": "461 Comestibles Dr. Weymouth MA", "Contact for future races": "no", "Email": "cardiologist@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Oneil", "Gender": "female", "Age": "13", "Telephone": "287-3946", "ID": "", "First name": "Cristina", "Address": "518 Allie St. Hanover MA", "Contact for future races": "no", "Email": "quadruples@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hutchinson", "Gender": "male", "Age": "78", "Telephone": "532-4137", "ID": "", "First name": "Jeffrey", "Address": "604 Ceramic St. Quincy MA", "Contact for future races": "no", "Email": "durantes@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Carr", "Gender": "male", "Age": "17", "Telephone": "486-6478", "ID": "", "First name": "Eric", "Address": "562 Prattling Dr. Quincy MA", "Contact for future races": "no", "Email": "satisfy@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Dean", "Gender": "female", "Age": "94", "Telephone": "831-8600", "ID": "", "First name": "Rosanne", "Address": "390 Ballrooms Dr. Braintree MA", "Contact for future races": "no", "Email": "bristled@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Cox", "Gender": "male", "Age": "61", "Telephone": "211-2330", "ID": "", "First name": "Marc", "Address": "41 Willies Ln. Hanover MA", "Contact for future races": "", "Email": "colt@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Charles", "Gender": "female", "Age": "27", "Telephone": "526-4413", "ID": "", "First name": "Sophie", "Address": "797 Indiras Ln. Weymouth MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "school", "Last name": "Moody", "Gender": "female", "Age": "30", "Telephone": "367-2561", "ID": "", "First name": "Meredith", "Address": "978 Docilitys Ln. Hingham MA", "Contact for future races": "no", "Email": "embarrassingly@gmail.com"}, {"How did you hear about race": "", "Last name": "Cobb", "Gender": "female", "Age": "84", "Telephone": "560-1904", "ID": "", "First name": "Liliana", "Address": "136 Tyrants Ln. Braintree MA", "Contact for future races": "no", "Email": "depositor@gmail.com"}, {"How did you hear about race": "school", "Last name": "Nash", "Gender": "female", "Age": "57", "Telephone": "226-6395", "ID": "", "First name": "Mai", "Address": "318 Punctuality St. Braintree MA", "Contact for future races": "yes", "Email": "acrobatics@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Sanders", "Gender": "female", "Age": "73", "Telephone": "919-8639", "ID": "", "First name": "Laura", "Address": "141 Tight Dr. Braintree MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "church", "Last name": "Davis", "Gender": "male", "Age": "63", "Telephone": "973-9491", "ID": "", "First name": "Clinton", "Address": "506 Lorenzos Way Hingham MA", "Contact for future races": "yes", "Email": "devastated@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Swanson", "Gender": "female", "Age": "68", "Telephone": "615-5757", "ID": "", "First name": "Angel", "Address": "404 Lifework Dr. Weymouth MA", "Contact for future races": "yes", "Email": "tameka@gmail.com"}, {"How did you hear about race": "church", "Last name": "Lyons", "Gender": "female", "Age": "27", "Telephone": "133-1213", "ID": "", "First name": "Hattie", "Address": "763 Ideally Ln. Hanover MA", "Contact for future races": "yes", "Email": "odyssey@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Small", "Gender": "male", "Age": "51", "Telephone": "780-2287", "ID": "", "First name": "Daniel", "Address": "323 Desktop St. Braintree MA", "Contact for future races": "yes", "Email": "rockier@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Levine", "Gender": "female", "Age": "89", "Telephone": "670-5223", "ID": "", "First name": "Carmella", "Address": "559 Airdropping Ln. Hingham MA", "Contact for future races": "no", "Email": "july@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Perez", "Gender": "male", "Age": "56", "Telephone": "101-9825", "ID": "", "First name": "Aaron", "Address": "719 Slews St. Hanover MA", "Contact for future races": "no", "Email": "decommissioned@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Griffin", "Gender": "female", "Age": "33", "Telephone": "961-4017", "ID": "", "First name": "Leta", "Address": "212 Blintzes Dr. Weymouth MA", "Contact for future races": "yes", "Email": "usages@gmail.com"}, {"How did you hear about race": "", "Last name": "Knox", "Gender": "male", "Age": "17", "Telephone": "284-8732", "ID": "", "First name": "Lonnie", "Address": "635 Regulates Ln. Hingham MA", "Contact for future races": "no", "Email": "leukocyte@gmail.com"}, {"How did you hear about race": "school", "Last name": "Lindsay", "Gender": "male", "Age": "41", "Telephone": "117-2983", "ID": "", "First name": "Harvey", "Address": "195 Reiterates St. Norwell MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "school", "Last name": "Ruiz", "Gender": "female", "Age": "98", "Telephone": "", "ID": "", "First name": "Stacey", "Address": "", "Contact for future races": "yes", "Email": "typewriters@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Contreras", "Gender": "female", "Age": "42", "Telephone": "914-5801", "ID": "", "First name": "Yesenia", "Address": "773 Warhol Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "cation@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Oneal", "Gender": "male", "Age": "32", "Telephone": "906-7871", "ID": "", "First name": "Ian", "Address": "473 Flashers St. Hingham MA", "Contact for future races": "no", "Email": "disclosures@gmail.com"}, {"How did you hear about race": "church", "Last name": "Delacruz", "Gender": "female", "Age": "12", "Telephone": "470-5728", "ID": "", "First name": "Hilary", "Address": "34 Amaranths St. Hingham MA", "Contact for future races": "yes", "Email": "solder@gmail.com"}, {"How did you hear about race": "", "Last name": "Miller", "Gender": "female", "Age": "85", "Telephone": "525-7221", "ID": "", "First name": "Vivian", "Address": "106 Executioners St. Braintree MA", "Contact for future races": "yes", "Email": "weary@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Allison", "Gender": "male", "Age": "76", "Telephone": "193-3138", "ID": "", "First name": "Bryan", "Address": "569 Outcrops Pkwy. Hingham MA", "Contact for future races": "no", "Email": "reconnoitering@hotmail.com"}, {"How did you hear about race": "", "Last name": "Bradford", "Gender": "female", "Age": "69", "Telephone": "468-6087", "ID": "", "First name": "Claudine", "Address": "452 Personify Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "kiowas@hotmail.com"}, {"How did you hear about race": "", "Last name": "Bush", "Gender": "male", "Age": "90", "Telephone": "671-4034", "ID": "", "First name": "Harold", "Address": "164 Remind St. Quincy MA", "Contact for future races": "yes", "Email": "pothooks@gmail.com"}, {"How did you hear about race": "school", "Last name": "Jones", "Gender": "female", "Age": "39", "Telephone": "792-7886", "ID": "", "First name": "Pearl", "Address": "41 Apertures Ln. Quincy MA", "Contact for future races": "yes", "Email": "overtaking@gmail.com"}, {"How did you hear about race": "", "Last name": "Daniel", "Gender": "male", "Age": "33", "Telephone": "374-2126", "ID": "", "First name": "Nathaniel", "Address": "694 Audaciousness St. Norwell MA", "Contact for future races": "yes", "Email": "undershirts@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Beasley", "Gender": "", "Age": "80", "Telephone": "417-5341", "ID": "", "First name": "Lee", "Address": "140 Kutuzov Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "rags@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Noel", "Gender": "male", "Age": "90", "Telephone": "248-3011", "ID": "", "First name": "Marion", "Address": "769 Weeklies Dr. Quincy MA", "Contact for future races": "no", "Email": "mushrooms@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Maxwell", "Gender": "female", "Age": "76", "Telephone": "546-1505", "ID": "", "First name": "Sharlene", "Address": "926 Nickels Dr. Norwell MA", "Contact for future races": "no", "Email": "fledglings@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Elliott", "Gender": "female", "Age": "41", "Telephone": "905-3979", "ID": "", "First name": "Harriet", "Address": "749 Distill St. Braintree MA", "Contact for future races": "yes", "Email": "marchers@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Reeves", "Gender": "female", "Age": "50", "Telephone": "504-1422", "ID": "", "First name": "Rhea", "Address": "", "Contact for future races": "no", "Email": "bevel@gmail.com"}, {"How did you hear about race": "", "Last name": "Kramer", "Gender": "female", "Age": "33", "Telephone": "563-1573", "ID": "", "First name": "Kasey", "Address": "962 Regretfully Dr. Norwell MA", "Contact for future races": "no", "Email": "egoist@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Reynolds", "Gender": "female", "Age": "10", "Telephone": "528-6835", "ID": "", "First name": "Katelyn", "Address": "140 Avid St. Quincy MA", "Contact for future races": "no", "Email": "rosiest@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Pittman", "Gender": "female", "Age": "71", "Telephone": "854-1535", "ID": "", "First name": "Deirdre", "Address": "754 Duns Dr. Hingham MA", "Contact for future races": "no", "Email": "rationalist@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "", "Gender": "female", "Age": "50", "Telephone": "453-6554", "ID": "", "First name": "Jerri", "Address": "924 Clients Dr. Norwell MA", "Contact for future races": "yes", "Email": "scrutinys@gmail.com"}, {"How did you hear about race": "school", "Last name": "Whitney", "Gender": "male", "Age": "68", "Telephone": "658-5483", "ID": "", "First name": "Larry", "Address": "936 Rightly Way Braintree MA", "Contact for future races": "no", "Email": "alar@hotmail.com"}, {"How did you hear about race": "", "Last name": "Walsh", "Gender": "female", "Age": "8", "Telephone": "135-1414", "ID": "", "First name": "Sara", "Address": "584 Dodges Ln. Hingham MA", "Contact for future races": "no", "Email": "counterpanes@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Keith", "Gender": "male", "Age": "26", "Telephone": "530-9643", "ID": "", "First name": "Sean", "Address": "459 Haunts St. Quincy MA", "Contact for future races": "no", "Email": "cartels@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Sellers", "Gender": "female", "Age": "60", "Telephone": "", "ID": "", "First name": "Adriana", "Address": "893 Randy St. Hanover MA", "Contact for future races": "no", "Email": "union@hotmail.com"}, {"How did you hear about race": "", "Last name": "Patrick", "Gender": "female", "Age": "69", "Telephone": "521-4320", "ID": "", "First name": "Dale", "Address": "161 Conciliated Ln. Norwell MA", "Contact for future races": "no", "Email": "detonations@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Richmond", "Gender": "male", "Age": "47", "Telephone": "827-7799", "ID": "", "First name": "Kenneth", "Address": "929 Hardheartednesss St. Braintree MA", "Contact for future races": "no", "Email": "communicants@hotmail.com"}, {"How did you hear about race": "", "Last name": "Stewart", "Gender": "female", "Age": "85", "Telephone": "700-3403", "ID": "", "First name": "Dionne", "Address": "4 Cabals St. Hingham MA", "Contact for future races": "no", "Email": "soused@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Kirk", "Gender": "male", "Age": "12", "Telephone": "578-8658", "ID": "", "First name": "Samuel", "Address": "933 Partaker St. Norwell MA", "Contact for future races": "", "Email": "photographing@gmail.com"}, {"How did you hear about race": "", "Last name": "Santana", "Gender": "female", "Age": "14", "Telephone": "632-2216", "ID": "", "First name": "Sonia", "Address": "119 Damaged Ln. Weymouth MA", "Contact for future races": "no", "Email": "palmyra@gmail.com"}, {"How did you hear about race": "school", "Last name": "Collier", "Gender": "male", "Age": "77", "Telephone": "958-5629", "ID": "", "First name": "Andre", "Address": "381 Roughshod Way Weymouth MA", "Contact for future races": "yes", "Email": "lubrication@hotmail.com"}, {"How did you hear about race": "", "Last name": "Garrett", "Gender": "female", "Age": "42", "Telephone": "967-1035", "ID": "", "First name": "Irma", "Address": "339 Communists Dr. Norwell MA", "Contact for future races": "yes", "Email": "recommence@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Boone", "Gender": "female", "Age": "79", "Telephone": "196-5255", "ID": "", "First name": "Dina", "Address": "110 Trumbull Way Hanover MA", "Contact for future races": "no", "Email": "sidestrokes@gmail.com"}, {"How did you hear about race": "church", "Last name": "Welch", "Gender": "female", "Age": "59", "Telephone": "249-8852", "ID": "", "First name": "Kerry", "Address": "99 Rolland St. Weymouth MA", "Contact for future races": "no", "Email": "viewing@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Sanford", "Gender": "male", "Age": "52", "Telephone": "371-9494", "ID": "", "First name": "Mario", "Address": "227 Deductions Way Braintree MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "school", "Last name": "Booth", "Gender": "female", "Age": "47", "Telephone": "562-9609", "ID": "", "First name": "Celia", "Address": "592 Lonesome Way Hanover MA", "Contact for future races": "no", "Email": "snub@gmail.com"}, {"How did you hear about race": "church", "Last name": "Mcleod", "Gender": "female", "Age": "92", "Telephone": "497-7586", "ID": "", "First name": "Alexandra", "Address": "855 Semiautomatics Way Braintree MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "church", "Last name": "Jensen", "Gender": "female", "Age": "99", "Telephone": "314-1133", "ID": "", "First name": "Andrea", "Address": "755 Tertiary Pkwy. Norwell MA", "Contact for future races": "yes", "Email": "biscuits@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Shelton", "Gender": "", "Age": "64", "Telephone": "470-2248", "ID": "", "First name": "Penny", "Address": "343 Infernal St. Braintree MA", "Contact for future races": "no", "Email": "hipparchus@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Daniel", "Gender": "female", "Age": "36", "Telephone": "135-4348", "ID": "", "First name": "Josephine", "Address": "196 Awfullest St. Hanover MA", "Contact for future races": "yes", "Email": "cases@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Acosta", "Gender": "", "Age": "76", "Telephone": "795-6076", "ID": "", "First name": "Serena", "Address": "364 Illiteracy St. Hanover MA", "Contact for future races": "yes", "Email": "outliving@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Nash", "Gender": "female", "Age": "76", "Telephone": "872-8508", "ID": "", "First name": "Kerri", "Address": "462 Pakistanis Way Braintree MA", "Contact for future races": "yes", "Email": "scholastic@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Martin", "Gender": "male", "Age": "84", "Telephone": "909-6704", "ID": "", "First name": "Timothy", "Address": "675 Chattiness Dr. Quincy MA", "Contact for future races": "no", "Email": "truncheon@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Cunningham", "Gender": "female", "Age": "12", "Telephone": "559-8709", "ID": "", "First name": "Megan", "Address": "867 Extirpations St. Braintree MA", "Contact for future races": "yes", "Email": "pigmys@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mcfarland", "Gender": "female", "Age": "78", "Telephone": "844-2808", "ID": "", "First name": "Janna", "Address": "664 Bridgette Way Weymouth MA", "Contact for future races": "no", "Email": "wool@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Kelly", "Gender": "female", "Age": "53", "Telephone": "874-7657", "ID": "", "First name": "Valerie", "Address": "73 Fliers St. Hanover MA", "Contact for future races": "no", "Email": "rotarys@gmail.com"}, {"How did you hear about race": "school", "Last name": "Sanders", "Gender": "female", "Age": "39", "Telephone": "781-9125", "ID": "", "First name": "Suzanne", "Address": "24 Rooming St. Weymouth MA", "Contact for future races": "yes", "Email": "reincarnate@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Christian", "Gender": "female", "Age": "39", "Telephone": "585-4666", "ID": "", "First name": "Ila", "Address": "446 Hallucinogenic St. Hingham MA", "Contact for future races": "no", "Email": "defend@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Maddox", "Gender": "male", "Age": "45", "Telephone": "930-6281", "ID": "", "First name": "Francis", "Address": "865 Blessedness Ln. Hanover MA", "Contact for future races": "no", "Email": "irritably@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mason", "Gender": "male", "Age": "77", "Telephone": "242-6644", "ID": "", "First name": "Derek", "Address": "123 Clipboards Way Weymouth MA", "Contact for future races": "no", "Email": "orvilles@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mccullough", "Gender": "male", "Age": "97", "Telephone": "741-3195", "ID": "", "First name": "Earl", "Address": "22 Hunt St. Hanover MA", "Contact for future races": "yes", "Email": "schooners@gmail.com"}, {"How did you hear about race": "", "Last name": "Carroll", "Gender": "male", "Age": "2", "Telephone": "323-2377", "ID": "", "First name": "Rodney", "Address": "717 Armrest St. Braintree MA", "Contact for future races": "no", "Email": "semiramis@hotmail.com"}, {"How did you hear about race": "", "Last name": "Shaffer", "Gender": "female", "Age": "17", "Telephone": "574-5655", "ID": "", "First name": "Lilian", "Address": "741 Organics St. Norwell MA", "Contact for future races": "no", "Email": "amaru@gmail.com"}, {"How did you hear about race": "church", "Last name": "Trevino", "Gender": "male", "Age": "13", "Telephone": "100-4548", "ID": "", "First name": "Ronnie", "Address": "283 Footprints Pkwy. Braintree MA", "Contact for future races": "yes", "Email": "those@gmail.com"}, {"How did you hear about race": "", "Last name": "Woods", "Gender": "male", "Age": "0", "Telephone": "855-7458", "ID": "", "First name": "Ted", "Address": "", "Contact for future races": "yes", "Email": "palefaces@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Newman", "Gender": "female", "Age": "30", "Telephone": "427-9702", "ID": "", "First name": "", "Address": "540 Countermand St. Braintree MA", "Contact for future races": "yes", "Email": "copes@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mathews", "Gender": "female", "Age": "51", "Telephone": "418-4433", "ID": "", "First name": "Catherine", "Address": "", "Contact for future races": "no", "Email": "arsenals@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Boyer", "Gender": "male", "Age": "75", "Telephone": "626-2014", "ID": "", "First name": "Allan", "Address": "130 Per St. Quincy MA", "Contact for future races": "yes", "Email": "freemasons@hotmail.com"}, {"How did you hear about race": "", "Last name": "Moore", "Gender": "male", "Age": "63", "Telephone": "461-9959", "ID": "", "First name": "Antonio", "Address": "", "Contact for future races": "yes", "Email": "twirler@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Sandoval", "Gender": "female", "Age": "43", "Telephone": "617-1322", "ID": "", "First name": "", "Address": "215 Woodmans Ln. Hanover MA", "Contact for future races": "yes", "Email": "humanitys@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Roy", "Gender": "female", "Age": "42", "Telephone": "847-2846", "ID": "", "First name": "Ruby", "Address": "99 Downcast St. Weymouth MA", "Contact for future races": "no", "Email": "crinkly@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Ratliff", "Gender": "female", "Age": "22", "Telephone": "125-8551", "ID": "", "First name": "Rochelle", "Address": "815 Peninsulas St. Weymouth MA", "Contact for future races": "yes", "Email": "mousetraps@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Ford", "Gender": "male", "Age": "85", "Telephone": "290-7966", "ID": "", "First name": "Adrian", "Address": "163 Prehensile Ln. Norwell MA", "Contact for future races": "yes", "Email": "posited@hotmail.com"}, {"How did you hear about race": "school", "Last name": "", "Gender": "male", "Age": "30", "Telephone": "628-5110", "ID": "", "First name": "", "Address": "285 Humblenesss St. Hingham MA", "Contact for future races": "yes", "Email": "prevalence@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Garcia", "Gender": "male", "Age": "92", "Telephone": "502-9952", "ID": "", "First name": "Sam", "Address": "326 Turtledove Ln. Hanover MA", "Contact for future races": "no", "Email": "lighted@gmail.com"}, {"How did you hear about race": "school", "Last name": "Rios", "Gender": "male", "Age": "45", "Telephone": "903-9916", "ID": "", "First name": "Mike", "Address": "427 Truant St. Norwell MA", "Contact for future races": "yes", "Email": "fathomed@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Bowen", "Gender": "", "Age": "67", "Telephone": "904-5293", "ID": "", "First name": "Wesley", "Address": "399 Pupas Dr. Quincy MA", "Contact for future races": "no", "Email": "jfks@gmail.com"}, {"How did you hear about race": "school", "Last name": "Chang", "Gender": "female", "Age": "75", "Telephone": "841-5631", "ID": "", "First name": "Annabelle", "Address": "538 Cradles Ln. Weymouth MA", "Contact for future races": "no", "Email": "contingency@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Dunn", "Gender": "female", "Age": "38", "Telephone": "828-8734", "ID": "", "First name": "", "Address": "108 Forwards Ln. Norwell MA", "Contact for future races": "no", "Email": "bequeaths@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Sellers", "Gender": "female", "Age": "80", "Telephone": "149-5001", "ID": "", "First name": "Letha", "Address": "768 Blearily Dr. Hingham MA", "Contact for future races": "no", "Email": "lodger@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Anderson", "Gender": "male", "Age": "15", "Telephone": "984-7991", "ID": "", "First name": "", "Address": "696 Largest Way Hanover MA", "Contact for future races": "yes", "Email": "accentuations@gmail.com"}, {"How did you hear about race": "", "Last name": "Livingston", "Gender": "female", "Age": "78", "Telephone": "157-7867", "ID": "", "First name": "Lorna", "Address": "224 Outline Ln. Weymouth MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Randolph", "Gender": "female", "Age": "88", "Telephone": "635-1435", "ID": "", "First name": "Jerry", "Address": "643 Nationally Dr. Weymouth MA", "Contact for future races": "yes", "Email": "knowles@hotmail.com"}, {"How did you hear about race": "", "Last name": "Cox", "Gender": "male", "Age": "45", "Telephone": "639-4786", "ID": "", "First name": "Milton", "Address": "238 Liquefying Dr. Weymouth MA", "Contact for future races": "no", "Email": "parboiling@gmail.com"}, {"How did you hear about race": "church", "Last name": "Raymond", "Gender": "male", "Age": "53", "Telephone": "140-6199", "ID": "", "First name": "Greg", "Address": "419 Abstainers St. Quincy MA", "Contact for future races": "no", "Email": "convoying@hotmail.com"}, {"How did you hear about race": "", "Last name": "Shaw", "Gender": "female", "Age": "34", "Telephone": "380-5005", "ID": "", "First name": "Candace", "Address": "3 Realistically St. Hanover MA", "Contact for future races": "yes", "Email": "passel@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Woods", "Gender": "male", "Age": "19", "Telephone": "270-3722", "ID": "", "First name": "Bill", "Address": "9 Posts Ln. Braintree MA", "Contact for future races": "yes", "Email": "tweeters@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Ewing", "Gender": "female", "Age": "15", "Telephone": "868-4002", "ID": "", "First name": "Opal", "Address": "226 Fettle St. Quincy MA", "Contact for future races": "no", "Email": "buds@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Patton", "Gender": "female", "Age": "67", "Telephone": "107-3300", "ID": "", "First name": "Leonor", "Address": "853 Miseries Ln. Norwell MA", "Contact for future races": "no", "Email": "pessimistic@gmail.com"}, {"How did you hear about race": "", "Last name": "Jefferson", "Gender": "male", "Age": "77", "Telephone": "845-5005", "ID": "", "First name": "", "Address": "799 Roomfuls St. Norwell MA", "Contact for future races": "", "Email": "deteriorated@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Blake", "Gender": "male", "Age": "75", "Telephone": "213-4798", "ID": "", "First name": "Melvin", "Address": "349 Readings Ln. Norwell MA", "Contact for future races": "no", "Email": "seans@gmail.com"}, {"How did you hear about race": "church", "Last name": "Bennett", "Gender": "female", "Age": "94", "Telephone": "331-5281", "ID": "", "First name": "Lindsay", "Address": "", "Contact for future races": "no", "Email": "okras@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Wilson", "Gender": "female", "Age": "96", "Telephone": "340-4236", "ID": "", "First name": "Susie", "Address": "740 Chanciest Dr. Braintree MA", "Contact for future races": "no", "Email": "bestir@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Malone", "Gender": "male", "Age": "92", "Telephone": "909-2498", "ID": "", "First name": "Luis", "Address": "609 Fannies St. Weymouth MA", "Contact for future races": "no", "Email": "laos@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Acosta", "Gender": "female", "Age": "22", "Telephone": "", "ID": "", "First name": "Elizabeth", "Address": "573 Cribs St. Braintree MA", "Contact for future races": "no", "Email": "agonizing@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Bell", "Gender": "male", "Age": "34", "Telephone": "191-6059", "ID": "", "First name": "", "Address": "458 Hammetts Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "god@gmail.com"}, {"How did you hear about race": "", "Last name": "", "Gender": "female", "Age": "76", "Telephone": "", "ID": "", "First name": "Sonia", "Address": "369 Coasting Ln. Hingham MA", "Contact for future races": "no", "Email": "elaboratenesss@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Marshall", "Gender": "male", "Age": "62", "Telephone": "593-8965", "ID": "", "First name": "Antonio", "Address": "760 Carnivals Ln. Hingham MA", "Contact for future races": "yes", "Email": "larding@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Gardner", "Gender": "female", "Age": "1", "Telephone": "738-1997", "ID": "", "First name": "Marcia", "Address": "929 Mg St. Weymouth MA", "Contact for future races": "yes", "Email": "kochabs@gmail.com"}, {"How did you hear about race": "", "Last name": "Serrano", "Gender": "male", "Age": "58", "Telephone": "476-9986", "ID": "", "First name": "Timothy", "Address": "899 Balling St. Braintree MA", "Contact for future races": "no", "Email": "mashs@gmail.com"}, {"How did you hear about race": "", "Last name": "Hyde", "Gender": "female", "Age": "45", "Telephone": "599-7867", "ID": "", "First name": "Tamera", "Address": "279 Inclinations Ln. Hanover MA", "Contact for future races": "no", "Email": "tintinnabulations@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Mcneil", "Gender": "male", "Age": "98", "Telephone": "213-7867", "ID": "", "First name": "Glenn", "Address": "612 Betrothals Dr. Quincy MA", "Contact for future races": "no", "Email": "substantives@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Roberts", "Gender": "female", "Age": "44", "Telephone": "323-5393", "ID": "", "First name": "Phoebe", "Address": "684 Servomechanism Dr. Braintree MA", "Contact for future races": "yes", "Email": "errors@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Collins", "Gender": "male", "Age": "8", "Telephone": "984-5579", "ID": "", "First name": "Francisco", "Address": "664 Legalisms Dr. Hingham MA", "Contact for future races": "yes", "Email": "objectives@gmail.com"}, {"How did you hear about race": "school", "Last name": "Johnson", "Gender": "male", "Age": "96", "Telephone": "511-8328", "ID": "", "First name": "Jacob", "Address": "91 Recited Pkwy. Hingham MA", "Contact for future races": "no", "Email": "burls@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Petty", "Gender": "female", "Age": "82", "Telephone": "271-3502", "ID": "", "First name": "Jodi", "Address": "828 Segregationists Ln. Quincy MA", "Contact for future races": "yes", "Email": "rubicund@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Anthony", "Gender": "male", "Age": "32", "Telephone": "397-2136", "ID": "", "First name": "Marcus", "Address": "583 Milkmaids St. Weymouth MA", "Contact for future races": "yes", "Email": "megalomaniacs@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Olsen", "Gender": "male", "Age": "31", "Telephone": "504-2257", "ID": "", "First name": "Ted", "Address": "609 Nichiren Pkwy. Hanover MA", "Contact for future races": "no", "Email": "ruffled@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Ewing", "Gender": "male", "Age": "17", "Telephone": "901-5877", "ID": "", "First name": "Carlos", "Address": "386 Forsook Dr. Braintree MA", "Contact for future races": "no", "Email": "intonations@gmail.com"}, {"How did you hear about race": "school", "Last name": "Nunez", "Gender": "female", "Age": "80", "Telephone": "333-6419", "ID": "", "First name": "Janis", "Address": "104 Accompanists St. Braintree MA", "Contact for future races": "no", "Email": "assemblymans@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Moss", "Gender": "female", "Age": "52", "Telephone": "931-2410", "ID": "", "First name": "Ladonna", "Address": "134 Drowsily Pkwy. Hingham MA", "Contact for future races": "no", "Email": "tanagers@gmail.com"}, {"How did you hear about race": "school", "Last name": "Saunders", "Gender": "male", "Age": "38", "Telephone": "940-6075", "ID": "", "First name": "Jessie", "Address": "752 Volumes Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "plexiglases@hotmail.com"}, {"How did you hear about race": "", "Last name": "Farmer", "Gender": "male", "Age": "13", "Telephone": "234-6283", "ID": "", "First name": "Oscar", "Address": "867 Civilians Ln. Hingham MA", "Contact for future races": "yes", "Email": "boxers@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Weber", "Gender": "male", "Age": "76", "Telephone": "392-2546", "ID": "", "First name": "Jaime", "Address": "27 Unoriginal St. Weymouth MA", "Contact for future races": "", "Email": "maalox@hotmail.com"}, {"How did you hear about race": "", "Last name": "Goodman", "Gender": "female", "Age": "34", "Telephone": "157-9096", "ID": "", "First name": "Dee", "Address": "949 Byrd St. Weymouth MA", "Contact for future races": "no", "Email": "compacting@hotmail.com"}, {"How did you hear about race": "", "Last name": "Chan", "Gender": "male", "Age": "46", "Telephone": "151-4260", "ID": "", "First name": "Troy", "Address": "412 Prosecutes St. Hingham MA", "Contact for future races": "no", "Email": "remunerations@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Craig", "Gender": "male", "Age": "80", "Telephone": "560-4535", "ID": "", "First name": "Guy", "Address": "260 Palikir Dr. Norwell MA", "Contact for future races": "yes", "Email": "bleated@hotmail.com"}, {"How did you hear about race": "", "Last name": "Marks", "Gender": "female", "Age": "88", "Telephone": "174-9998", "ID": "", "First name": "Marquita", "Address": "112 Attack Pkwy. Quincy MA", "Contact for future races": "no", "Email": "logrollings@hotmail.com"}, {"How did you hear about race": "", "Last name": "Klein", "Gender": "male", "Age": "69", "Telephone": "478-3570", "ID": "", "First name": "Cecil", "Address": "894 Lushest St. Braintree MA", "Contact for future races": "yes", "Email": "ozarkss@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Schultz", "Gender": "male", "Age": "28", "Telephone": "391-4289", "ID": "", "First name": "", "Address": "42 Regicide St. Hanover MA", "Contact for future races": "yes", "Email": "execrates@gmail.com"}, {"How did you hear about race": "school", "Last name": "Webster", "Gender": "female", "Age": "80", "Telephone": "959-7169", "ID": "", "First name": "Ann", "Address": "670 Sanskrit Ln. Norwell MA", "Contact for future races": "yes", "Email": "thats@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Burch", "Gender": "female", "Age": "69", "Telephone": "", "ID": "", "First name": "Trina", "Address": "244 Pbss St. Quincy MA", "Contact for future races": "yes", "Email": "luiss@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Galloway", "Gender": "male", "Age": "73", "Telephone": "369-5428", "ID": "", "First name": "Howard", "Address": "340 Deteriorate Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "polytechnics@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hopper", "Gender": "female", "Age": "21", "Telephone": "585-9937", "ID": "", "First name": "Janelle", "Address": "673 Supermarkets Way Hingham MA", "Contact for future races": "yes", "Email": "judaisms@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Scott", "Gender": "male", "Age": "9", "Telephone": "", "ID": "", "First name": "Todd", "Address": "695 Abortive St. Quincy MA", "Contact for future races": "yes", "Email": "civics@gmail.com"}, {"How did you hear about race": "school", "Last name": "Langley", "Gender": "female", "Age": "96", "Telephone": "751-2365", "ID": "", "First name": "Catherine", "Address": "112 Helplessness Dr. Braintree MA", "Contact for future races": "yes", "Email": "sage@hotmail.com"}, {"How did you hear about race": "", "Last name": "Hutchinson", "Gender": "female", "Age": "30", "Telephone": "793-2716", "ID": "", "First name": "Phoebe", "Address": "707 Grayed Dr. Norwell MA", "Contact for future races": "yes", "Email": "reckon@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Barry", "Gender": "female", "Age": "74", "Telephone": "708-2891", "ID": "", "First name": "", "Address": "985 Emblazoned Dr. Hingham MA", "Contact for future races": "yes", "Email": "braced@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Harris", "Gender": "male", "Age": "7", "Telephone": "205-5546", "ID": "", "First name": "Clarence", "Address": "158 Neurology St. Hanover MA", "Contact for future races": "no", "Email": "digested@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Moody", "Gender": "female", "Age": "78", "Telephone": "290-5925", "ID": "", "First name": "Alissa", "Address": "156 Chatterers Dr. Quincy MA", "Contact for future races": "yes", "Email": "barrings@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Rosales", "Gender": "male", "Age": "31", "Telephone": "524-6714", "ID": "", "First name": "Darrell", "Address": "425 Discovering Dr. Braintree MA", "Contact for future races": "yes", "Email": "summers@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Alvarado", "Gender": "female", "Age": "51", "Telephone": "229-5241", "ID": "", "First name": "Minerva", "Address": "596 Stevedores Dr. Hanover MA", "Contact for future races": "no", "Email": "underpaid@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Maxwell", "Gender": "female", "Age": "88", "Telephone": "617-1214", "ID": "", "First name": "Minerva", "Address": "500 Interrelationship Dr. Hingham MA", "Contact for future races": "no", "Email": "foreknowledges@hotmail.com"}, {"How did you hear about race": "", "Last name": "Pierce", "Gender": "male", "Age": "14", "Telephone": "320-7057", "ID": "", "First name": "David", "Address": "325 Pits Dr. Quincy MA", "Contact for future races": "yes", "Email": "parkas@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Cabrera", "Gender": "female", "Age": "32", "Telephone": "610-5253", "ID": "", "First name": "Meredith", "Address": "360 Morrows St. Hanover MA", "Contact for future races": "no", "Email": "customers@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Stevenson", "Gender": "female", "Age": "75", "Telephone": "833-6758", "ID": "", "First name": "Patrice", "Address": "376 Pouts St. Weymouth MA", "Contact for future races": "yes", "Email": "rationalizes@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Cash", "Gender": "male", "Age": "2", "Telephone": "995-3170", "ID": "", "First name": "Lloyd", "Address": "573 Rationalizing Ln. Norwell MA", "Contact for future races": "yes", "Email": "dumfounding@hotmail.com"}, {"How did you hear about race": "", "Last name": "Bryan", "Gender": "female", "Age": "16", "Telephone": "", "ID": "", "First name": "Beverley", "Address": "835 Flawlessly St. Braintree MA", "Contact for future races": "yes", "Email": "aquaplaning@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Ortiz", "Gender": "female", "Age": "9", "Telephone": "367-4966", "ID": "", "First name": "Krista", "Address": "", "Contact for future races": "no", "Email": "campanellas@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Pratt", "Gender": "female", "Age": "10", "Telephone": "283-6511", "ID": "", "First name": "Roxanne", "Address": "363 Kroger St. Hanover MA", "Contact for future races": "no", "Email": "naked@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Briggs", "Gender": "", "Age": "55", "Telephone": "449-4041", "ID": "", "First name": "Penny", "Address": "19 Imperative St. Norwell MA", "Contact for future races": "", "Email": "delinquents@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Martin", "Gender": "female", "Age": "34", "Telephone": "553-6276", "ID": "", "First name": "Vilma", "Address": "758 Gauls St. Hanover MA", "Contact for future races": "yes", "Email": "southwests@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Chambers", "Gender": "male", "Age": "20", "Telephone": "239-3997", "ID": "", "First name": "", "Address": "438 Bleeped Ln. Braintree MA", "Contact for future races": "no", "Email": "insensibility@gmail.com"}, {"How did you hear about race": "", "Last name": "Wright", "Gender": "male", "Age": "6", "Telephone": "", "ID": "", "First name": "Hugh", "Address": "621 Stokers Ln. Quincy MA", "Contact for future races": "no", "Email": "rapprochements@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hull", "Gender": "male", "Age": "16", "Telephone": "788-7682", "ID": "", "First name": "Francisco", "Address": "428 Winced Ln. Hanover MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Foley", "Gender": "female", "Age": "32", "Telephone": "627-3376", "ID": "", "First name": "Bridget", "Address": "369 Imitations St. Quincy MA", "Contact for future races": "no", "Email": "dimaggio@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Garcia", "Gender": "female", "Age": "57", "Telephone": "432-7474", "ID": "", "First name": "", "Address": "", "Contact for future races": "no", "Email": "chimps@hotmail.com"}, {"How did you hear about race": "church", "Last name": "", "Gender": "male", "Age": "26", "Telephone": "863-9600", "ID": "", "First name": "Casey", "Address": "349 Bushings St. Weymouth MA", "Contact for future races": "no", "Email": "beta@gmail.com"}, {"How did you hear about race": "", "Last name": "Hammond", "Gender": "female", "Age": "55", "Telephone": "183-6285", "ID": "", "First name": "Muriel", "Address": "880 Nieces St. Braintree MA", "Contact for future races": "yes", "Email": "belabors@gmail.com"}, {"How did you hear about race": "", "Last name": "Thomas", "Gender": "male", "Age": "55", "Telephone": "639-2370", "ID": "", "First name": "Bill", "Address": "503 Phonographs Ln. Hanover MA", "Contact for future races": "", "Email": "lubricate@gmail.com"}, {"How did you hear about race": "school", "Last name": "Vincent", "Gender": "male", "Age": "84", "Telephone": "560-2406", "ID": "", "First name": "Andre", "Address": "867 Genitalia Pkwy. Braintree MA", "Contact for future races": "no", "Email": "sacrilegious@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Peters", "Gender": "male", "Age": "98", "Telephone": "775-2622", "ID": "", "First name": "Leo", "Address": "699 Overheads Dr. Norwell MA", "Contact for future races": "no", "Email": "sixpences@gmail.com"}, {"How did you hear about race": "school", "Last name": "Travis", "Gender": "male", "Age": "5", "Telephone": "622-4322", "ID": "", "First name": "Johnny", "Address": "447 Ramshackle Way Hingham MA", "Contact for future races": "no", "Email": "receptivitys@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Alexander", "Gender": "female", "Age": "73", "Telephone": "993-8112", "ID": "", "First name": "Essie", "Address": "360 Msgs Ln. Braintree MA", "Contact for future races": "yes", "Email": "flusters@hotmail.com"}, {"How did you hear about race": "", "Last name": "Wolf", "Gender": "female", "Age": "78", "Telephone": "356-8511", "ID": "", "First name": "Leah", "Address": "926 Divisions St. Braintree MA", "Contact for future races": "no", "Email": "misfeasances@hotmail.com"}, {"How did you hear about race": "", "Last name": "Ryan", "Gender": "female", "Age": "7", "Telephone": "520-9661", "ID": "", "First name": "Mai", "Address": "224 Quirked Dr. Quincy MA", "Contact for future races": "no", "Email": "latests@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Bolton", "Gender": "male", "Age": "64", "Telephone": "837-3632", "ID": "", "First name": "Thomas", "Address": "18 Outlived St. Hanover MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "a friend", "Last name": "Payne", "Gender": "male", "Age": "74", "Telephone": "881-3410", "ID": "", "First name": "Alvin", "Address": "555 Bonfire Dr. Hanover MA", "Contact for future races": "yes", "Email": "inversions@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Best", "Gender": "male", "Age": "35", "Telephone": "126-9061", "ID": "", "First name": "Julian", "Address": "571 Pigmies Way Weymouth MA", "Contact for future races": "no", "Email": "brick@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Baldwin", "Gender": "male", "Age": "86", "Telephone": "306-7918", "ID": "", "First name": "Ken", "Address": "545 Miraculously St. Braintree MA", "Contact for future races": "no", "Email": "nimbus@gmail.com"}, {"How did you hear about race": "church", "Last name": "Carlson", "Gender": "female", "Age": "64", "Telephone": "100-8490", "ID": "", "First name": "", "Address": "183 Forum Ln. Hingham MA", "Contact for future races": "yes", "Email": "swilling@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "David", "Gender": "female", "Age": "18", "Telephone": "713-1932", "ID": "", "First name": "Bette", "Address": "448 Geoffrey Way Norwell MA", "Contact for future races": "", "Email": "stylus@hotmail.com"}, {"How did you hear about race": "", "Last name": "Cameron", "Gender": "male", "Age": "48", "Telephone": "286-1047", "ID": "", "First name": "", "Address": "156 Infelicitous St. Weymouth MA", "Contact for future races": "yes", "Email": "jamaicans@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Becker", "Gender": "male", "Age": "58", "Telephone": "462-9960", "ID": "", "First name": "Gene", "Address": "335 Transporter St. Braintree MA", "Contact for future races": "yes", "Email": "candaces@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Bridges", "Gender": "female", "Age": "53", "Telephone": "409-6317", "ID": "", "First name": "Marina", "Address": "", "Contact for future races": "no", "Email": "inspires@gmail.com"}, {"How did you hear about race": "school", "Last name": "Rojas", "Gender": "female", "Age": "59", "Telephone": "795-5969", "ID": "", "First name": "Beatriz", "Address": "0 Jollied Dr. Weymouth MA", "Contact for future races": "yes", "Email": "naphtha@gmail.com"}, {"How did you hear about race": "church", "Last name": "Sweeney", "Gender": "male", "Age": "60", "Telephone": "", "ID": "", "First name": "Jimmie", "Address": "858 Available Dr. Norwell MA", "Contact for future races": "no", "Email": "twiggier@gmail.com"}, {"How did you hear about race": "church", "Last name": "Hale", "Gender": "female", "Age": "42", "Telephone": "732-9208", "ID": "", "First name": "Janelle", "Address": "165 Indulgence St. Braintree MA", "Contact for future races": "yes", "Email": "starving@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Kirkland", "Gender": "male", "Age": "7", "Telephone": "240-5522", "ID": "", "First name": "Adrian", "Address": "341 Captions St. Norwell MA", "Contact for future races": "yes", "Email": "airsickness@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Puckett", "Gender": "female", "Age": "55", "Telephone": "403-2544", "ID": "", "First name": "Ivy", "Address": "736 Atwitter Way Hanover MA", "Contact for future races": "no", "Email": "debauchery@gmail.com"}, {"How did you hear about race": "school", "Last name": "Palmer", "Gender": "female", "Age": "58", "Telephone": "406-8344", "ID": "", "First name": "Anna", "Address": "878 Ganymedes St. Hingham MA", "Contact for future races": "", "Email": "seminole@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Alvarado", "Gender": "male", "Age": "18", "Telephone": "", "ID": "", "First name": "Hugh", "Address": "", "Contact for future races": "yes", "Email": "gospel@gmail.com"}, {"How did you hear about race": "school", "Last name": "Hendricks", "Gender": "female", "Age": "67", "Telephone": "554-2023", "ID": "", "First name": "Bridget", "Address": "256 Butterflies St. Norwell MA", "Contact for future races": "yes", "Email": "sarcomata@hotmail.com"}, {"How did you hear about race": "", "Last name": "Young", "Gender": "male", "Age": "52", "Telephone": "729-3705", "ID": "", "First name": "Dan", "Address": "428 Jurisprudence St. Hanover MA", "Contact for future races": "yes", "Email": "temptresses@gmail.com"}, {"How did you hear about race": "school", "Last name": "Franklin", "Gender": "male", "Age": "8", "Telephone": "701-9474", "ID": "", "First name": "Bradley", "Address": "259 Beautys Dr. Quincy MA", "Contact for future races": "yes", "Email": "refund@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Galloway", "Gender": "female", "Age": "3", "Telephone": "959-3526", "ID": "", "First name": "Hallie", "Address": "709 Saxophonist Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "emus@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Velez", "Gender": "male", "Age": "40", "Telephone": "846-6159", "ID": "", "First name": "Mitchell", "Address": "909 Technocrats Ln. Quincy MA", "Contact for future races": "no", "Email": "adventurers@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Cash", "Gender": "female", "Age": "74", "Telephone": "860-1033", "ID": "", "First name": "Rosa", "Address": "156 Yoghourt Pkwy. Norwell MA", "Contact for future races": "no", "Email": "refrigerators@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Mann", "Gender": "female", "Age": "6", "Telephone": "795-8095", "ID": "", "First name": "Sherrie", "Address": "", "Contact for future races": "yes", "Email": "mamboing@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Gibbs", "Gender": "male", "Age": "62", "Telephone": "862-2823", "ID": "", "First name": "Marion", "Address": "289 Plattes Dr. Quincy MA", "Contact for future races": "no", "Email": "fraudulently@hotmail.com"}, {"How did you hear about race": "", "Last name": "Weaver", "Gender": "female", "Age": "76", "Telephone": "673-8444", "ID": "", "First name": "Kerry", "Address": "450 Persuading Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "fulminations@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Johnson", "Gender": "male", "Age": "26", "Telephone": "909-8311", "ID": "", "First name": "Harold", "Address": "945 Fuzed Dr. Hanover MA", "Contact for future races": "yes", "Email": "quitting@gmail.com"}, {"How did you hear about race": "", "Last name": "Craft", "Gender": "female", "Age": "65", "Telephone": "999-9317", "ID": "", "First name": "Marci", "Address": "150 Skylight Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "circulations@gmail.com"}, {"How did you hear about race": "", "Last name": "Kim", "Gender": "female", "Age": "22", "Telephone": "781-8103", "ID": "", "First name": "Robin", "Address": "268 Abandons Dr. Quincy MA", "Contact for future races": "yes", "Email": "unskilled@hotmail.com"}, {"How did you hear about race": "", "Last name": "Barron", "Gender": "female", "Age": "22", "Telephone": "311-5698", "ID": "", "First name": "Juliana", "Address": "448 Mediterraneans Dr. Hanover MA", "Contact for future races": "", "Email": ""}, {"How did you hear about race": "", "Last name": "Ewing", "Gender": "female", "Age": "3", "Telephone": "335-1489", "ID": "", "First name": "Sybil", "Address": "940 Stars Dr. Quincy MA", "Contact for future races": "yes", "Email": "attacker@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Case", "Gender": "male", "Age": "84", "Telephone": "528-7563", "ID": "", "First name": "Vernon", "Address": "793 Updates Dr. Hanover MA", "Contact for future races": "yes", "Email": "flannels@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Adams", "Gender": "male", "Age": "35", "Telephone": "758-2728", "ID": "", "First name": "Dwayne", "Address": "345 Cortex Pkwy. Norwell MA", "Contact for future races": "no", "Email": "procedural@gmail.com"}, {"How did you hear about race": "", "Last name": "Martinez", "Gender": "male", "Age": "21", "Telephone": "415-7342", "ID": "", "First name": "Alexander", "Address": "702 Tossup Pkwy. Norwell MA", "Contact for future races": "yes", "Email": "trances@gmail.com"}, {"How did you hear about race": "school", "Last name": "Strickland", "Gender": "female", "Age": "97", "Telephone": "164-3397", "ID": "", "First name": "Ellen", "Address": "415 Daren Way Weymouth MA", "Contact for future races": "no", "Email": "hardened@gmail.com"}, {"How did you hear about race": "", "Last name": "Cash", "Gender": "female", "Age": "9", "Telephone": "812-9850", "ID": "", "First name": "Esperanza", "Address": "636 Lsds St. Norwell MA", "Contact for future races": "no", "Email": "counterrevolutions@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Hunt", "Gender": "female", "Age": "75", "Telephone": "716-8609", "ID": "", "First name": "Ofelia", "Address": "0 Redistributions St. Hingham MA", "Contact for future races": "yes", "Email": "tracheas@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Cunningham", "Gender": "male", "Age": "66", "Telephone": "666-5172", "ID": "", "First name": "Steve", "Address": "34 Averys St. Norwell MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "school", "Last name": "York", "Gender": "female", "Age": "35", "Telephone": "221-1072", "ID": "", "First name": "Judith", "Address": "987 Cu Ln. Quincy MA", "Contact for future races": "yes", "Email": "elegies@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mercer", "Gender": "male", "Age": "81", "Telephone": "802-5299", "ID": "", "First name": "Edwin", "Address": "797 Takeover Way Hanover MA", "Contact for future races": "yes", "Email": "balded@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Macias", "Gender": "male", "Age": "31", "Telephone": "657-8413", "ID": "", "First name": "", "Address": "962 Incredulity Ln. Hingham MA", "Contact for future races": "yes", "Email": "belittles@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Leon", "Gender": "male", "Age": "17", "Telephone": "759-9235", "ID": "", "First name": "Marc", "Address": "11 Briquets St. Weymouth MA", "Contact for future races": "yes", "Email": "cranmer@gmail.com"}, {"How did you hear about race": "", "Last name": "Daniel", "Gender": "male", "Age": "11", "Telephone": "400-3339", "ID": "", "First name": "Lewis", "Address": "255 Perverseness Dr. Quincy MA", "Contact for future races": "no", "Email": "fleshlier@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Perkins", "Gender": "male", "Age": "40", "Telephone": "724-7677", "ID": "", "First name": "Eugene", "Address": "907 Chronometers Dr. Quincy MA", "Contact for future races": "no", "Email": "whores@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Rivas", "Gender": "female", "Age": "43", "Telephone": "964-8697", "ID": "", "First name": "James", "Address": "825 Laundryman Ln. Norwell MA", "Contact for future races": "no", "Email": "complainers@gmail.com"}, {"How did you hear about race": "church", "Last name": "Caldwell", "Gender": "male", "Age": "24", "Telephone": "791-3162", "ID": "", "First name": "Lee", "Address": "", "Contact for future races": "yes", "Email": "wholes@hotmail.com"}, {"How did you hear about race": "", "Last name": "Andrews", "Gender": "female", "Age": "9", "Telephone": "542-6073", "ID": "", "First name": "Kristen", "Address": "791 Wets St. Braintree MA", "Contact for future races": "yes", "Email": "pallets@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Hodge", "Gender": "female", "Age": "49", "Telephone": "254-1095", "ID": "", "First name": "Corinne", "Address": "589 Slaughtered St. Braintree MA", "Contact for future races": "no", "Email": "sulky@gmail.com"}, {"How did you hear about race": "school", "Last name": "Savage", "Gender": "male", "Age": "8", "Telephone": "", "ID": "", "First name": "Luis", "Address": "44 Unclasps Way Braintree MA", "Contact for future races": "yes", "Email": "playgoer@gmail.com"}, {"How did you hear about race": "", "Last name": "Mcguire", "Gender": "female", "Age": "60", "Telephone": "657-3004", "ID": "", "First name": "Bianca", "Address": "780 Eugenics Way Hingham MA", "Contact for future races": "no", "Email": "thinks@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Patterson", "Gender": "female", "Age": "90", "Telephone": "343-4698", "ID": "", "First name": "Earlene", "Address": "711 Bellowed St. Hanover MA", "Contact for future races": "yes", "Email": "foregone@gmail.com"}, {"How did you hear about race": "", "Last name": "Gill", "Gender": "male", "Age": "96", "Telephone": "753-3157", "ID": "", "First name": "Terry", "Address": "925 Shocked St. Hingham MA", "Contact for future races": "no", "Email": "vickies@gmail.com"}, {"How did you hear about race": "", "Last name": "Mcguire", "Gender": "male", "Age": "97", "Telephone": "972-2606", "ID": "", "First name": "Curtis", "Address": "426 Deign St. Hanover MA", "Contact for future races": "no", "Email": "derivations@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Burton", "Gender": "male", "Age": "24", "Telephone": "877-5792", "ID": "", "First name": "Jamie", "Address": "121 Mosleys Dr. Quincy MA", "Contact for future races": "", "Email": "turpitude@gmail.com"}, {"How did you hear about race": "", "Last name": "Cain", "Gender": "female", "Age": "21", "Telephone": "607-5830", "ID": "", "First name": "Simone", "Address": "576 Linwoods St. Norwell MA", "Contact for future races": "no", "Email": "levelers@gmail.com"}, {"How did you hear about race": "school", "Last name": "Puckett", "Gender": "male", "Age": "43", "Telephone": "144-3949", "ID": "", "First name": "Kyle", "Address": "", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "West", "Gender": "female", "Age": "10", "Telephone": "934-8131", "ID": "", "First name": "Eddie", "Address": "823 Subheads Ln. Braintree MA", "Contact for future races": "no", "Email": "unwind@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Mcdaniel", "Gender": "male", "Age": "45", "Telephone": "334-2513", "ID": "", "First name": "Neil", "Address": "11 Rambles Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "contorts@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Warner", "Gender": "female", "Age": "64", "Telephone": "545-7396", "ID": "", "First name": "Kerri", "Address": "3 Firs St. Quincy MA", "Contact for future races": "no", "Email": "alfonso@hotmail.com"}, {"How did you hear about race": "", "Last name": "Sawyer", "Gender": "male", "Age": "59", "Telephone": "549-1458", "ID": "", "First name": "Micheal", "Address": "168 Mcgowan Pkwy. Braintree MA", "Contact for future races": "no", "Email": "treatable@gmail.com"}, {"How did you hear about race": "", "Last name": "Figueroa", "Gender": "male", "Age": "47", "Telephone": "691-8806", "ID": "", "First name": "Charlie", "Address": "1 Epitomes Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "regulars@gmail.com"}, {"How did you hear about race": "school", "Last name": "Stark", "Gender": "male", "Age": "7", "Telephone": "156-8791", "ID": "", "First name": "Randy", "Address": "438 Bouncy St. Norwell MA", "Contact for future races": "no", "Email": "mannishly@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Doyle", "Gender": "male", "Age": "78", "Telephone": "778-1189", "ID": "", "First name": "Howard", "Address": "300 Vats St. Norwell MA", "Contact for future races": "no", "Email": "tapes@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Riley", "Gender": "male", "Age": "91", "Telephone": "553-5317", "ID": "", "First name": "Henry", "Address": "302 Shrapnel St. Norwell MA", "Contact for future races": "yes", "Email": "interminably@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Franks", "Gender": "female", "Age": "14", "Telephone": "268-8777", "ID": "", "First name": "Lacey", "Address": "294 Meteorites Ln. Quincy MA", "Contact for future races": "yes", "Email": "hensleys@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Branch", "Gender": "female", "Age": "60", "Telephone": "131-8859", "ID": "", "First name": "Samantha", "Address": "691 Orangutangs St. Norwell MA", "Contact for future races": "yes", "Email": "burnout@gmail.com"}, {"How did you hear about race": "church", "Last name": "Munoz", "Gender": "male", "Age": "73", "Telephone": "130-1174", "ID": "", "First name": "Lawrence", "Address": "434 Ciliums St. Hingham MA", "Contact for future races": "yes", "Email": "repulsively@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Lewis", "Gender": "male", "Age": "25", "Telephone": "100-7567", "ID": "", "First name": "Daniel", "Address": "41 Annotations Ln. Hanover MA", "Contact for future races": "no", "Email": "ushered@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Houston", "Gender": "male", "Age": "61", "Telephone": "229-1833", "ID": "", "First name": "Benjamin", "Address": "532 Medleys Dr. Hingham MA", "Contact for future races": "no", "Email": "outbalancing@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Brown", "Gender": "female", "Age": "20", "Telephone": "798-1948", "ID": "", "First name": "Stephanie", "Address": "", "Contact for future races": "no", "Email": "tranquilest@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Erickson", "Gender": "male", "Age": "23", "Telephone": "677-3785", "ID": "", "First name": "Carlos", "Address": "135 Homed St. Hingham MA", "Contact for future races": "no", "Email": "salines@hotmail.com"}, {"How did you hear about race": "", "Last name": "Rhodes", "Gender": "female", "Age": "9", "Telephone": "505-1030", "ID": "", "First name": "Angela", "Address": "875 Attain Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "chatterboxes@gmail.com"}, {"How did you hear about race": "church", "Last name": "Love", "Gender": "male", "Age": "54", "Telephone": "589-3198", "ID": "", "First name": "Nelson", "Address": "677 Maryellens St. Hingham MA", "Contact for future races": "no", "Email": "footfalls@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hobbs", "Gender": "female", "Age": "70", "Telephone": "436-9781", "ID": "", "First name": "Lorena", "Address": "351 Lightweights St. Norwell MA", "Contact for future races": "yes", "Email": "untimely@gmail.com"}, {"How did you hear about race": "school", "Last name": "Boyer", "Gender": "male", "Age": "91", "Telephone": "", "ID": "", "First name": "Armando", "Address": "18 Adulterers Pkwy. Norwell MA", "Contact for future races": "no", "Email": "joshua@gmail.com"}, {"How did you hear about race": "", "Last name": "Cross", "Gender": "male", "Age": "86", "Telephone": "786-2936", "ID": "", "First name": "Calvin", "Address": "250 Candelabrums St. Braintree MA", "Contact for future races": "no", "Email": "teaspoonsful@gmail.com"}, {"How did you hear about race": "church", "Last name": "Blevins", "Gender": "female", "Age": "90", "Telephone": "989-2278", "ID": "", "First name": "Penny", "Address": "541 Tedious St. Hingham MA", "Contact for future races": "yes", "Email": "strives@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Burke", "Gender": "female", "Age": "63", "Telephone": "710-3457", "ID": "", "First name": "Abigail", "Address": "93 Bevel Ln. Hingham MA", "Contact for future races": "no", "Email": "imbalances@gmail.com"}, {"How did you hear about race": "", "Last name": "Gordon", "Gender": "male", "Age": "75", "Telephone": "144-9248", "ID": "", "First name": "Billy", "Address": "745 Masquerades St. Hanover MA", "Contact for future races": "yes", "Email": "classmate@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Jacobson", "Gender": "male", "Age": "12", "Telephone": "387-6373", "ID": "", "First name": "Clyde", "Address": "883 Southerly Dr. Hanover MA", "Contact for future races": "yes", "Email": "iteration@gmail.com"}, {"How did you hear about race": "", "Last name": "Terry", "Gender": "female", "Age": "4", "Telephone": "137-3851", "ID": "", "First name": "Alexandra", "Address": "451 Lacks St. Braintree MA", "Contact for future races": "yes", "Email": "trustworthiness@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hyde", "Gender": "female", "Age": "2", "Telephone": "", "ID": "", "First name": "Pamela", "Address": "", "Contact for future races": "no", "Email": "planck@gmail.com"}, {"How did you hear about race": "", "Last name": "Powers", "Gender": "male", "Age": "41", "Telephone": "544-9995", "ID": "", "First name": "Christian", "Address": "768 Unhanded Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "microphones@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Odom", "Gender": "male", "Age": "15", "Telephone": "873-5510", "ID": "", "First name": "Leslie", "Address": "900 Carp St. Hanover MA", "Contact for future races": "no", "Email": "expletives@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Melton", "Gender": "", "Age": "77", "Telephone": "737-2028", "ID": "", "First name": "Lessie", "Address": "35 Materialized Dr. Weymouth MA", "Contact for future races": "yes", "Email": "disburses@gmail.com"}, {"How did you hear about race": "", "Last name": "Moon", "Gender": "female", "Age": "63", "Telephone": "526-6246", "ID": "", "First name": "Nellie", "Address": "399 Premises St. Quincy MA", "Contact for future races": "no", "Email": "downfalls@gmail.com"}, {"How did you hear about race": "church", "Last name": "Finley", "Gender": "male", "Age": "67", "Telephone": "297-6110", "ID": "", "First name": "Wallace", "Address": "652 Troll Dr. Quincy MA", "Contact for future races": "no", "Email": "claires@gmail.com"}, {"How did you hear about race": "", "Last name": "Sampson", "Gender": "female", "Age": "16", "Telephone": "", "ID": "", "First name": "Lacy", "Address": "297 Gaseous St. Weymouth MA", "Contact for future races": "yes", "Email": "bidders@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Kaufman", "Gender": "male", "Age": "50", "Telephone": "781-1090", "ID": "", "First name": "Kirk", "Address": "956 Manors St. Norwell MA", "Contact for future races": "yes", "Email": "least@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Farmer", "Gender": "male", "Age": "39", "Telephone": "312-8099", "ID": "", "First name": "Jorge", "Address": "", "Contact for future races": "yes", "Email": "jitterbugged@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hall", "Gender": "male", "Age": "68", "Telephone": "650-8667", "ID": "", "First name": "Frank", "Address": "181 Rings Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "dogfight@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mercado", "Gender": "female", "Age": "34", "Telephone": "786-4966", "ID": "", "First name": "Leonor", "Address": "953 Burgess St. Braintree MA", "Contact for future races": "no", "Email": "elbowing@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Casey", "Gender": "male", "Age": "38", "Telephone": "158-1953", "ID": "", "First name": "Alex", "Address": "524 Audacity Ln. Weymouth MA", "Contact for future races": "yes", "Email": "fulminated@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Curry", "Gender": "male", "Age": "38", "Telephone": "802-6589", "ID": "", "First name": "Tommy", "Address": "976 Lighthearted St. Quincy MA", "Contact for future races": "", "Email": "kennels@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Howe", "Gender": "male", "Age": "11", "Telephone": "830-5116", "ID": "", "First name": "Enrique", "Address": "531 Terminal St. Norwell MA", "Contact for future races": "yes", "Email": "makes@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mccall", "Gender": "male", "Age": "16", "Telephone": "343-5327", "ID": "", "First name": "Mathew", "Address": "42 Trisha St. Norwell MA", "Contact for future races": "no", "Email": "temperaments@gmail.com"}, {"How did you hear about race": "school", "Last name": "Weaver", "Gender": "", "Age": "85", "Telephone": "869-4106", "ID": "", "First name": "Bonita", "Address": "558 Brans Ln. Braintree MA", "Contact for future races": "no", "Email": "fanaticism@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Turner", "Gender": "female", "Age": "78", "Telephone": "814-6802", "ID": "", "First name": "Brandie", "Address": "502 Counterclaiming Ln. Quincy MA", "Contact for future races": "yes", "Email": "herringbone@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Duran", "Gender": "male", "Age": "68", "Telephone": "698-1747", "ID": "", "First name": "Mathew", "Address": "329 Beardmores Dr. Norwell MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Robbins", "Gender": "male", "Age": "20", "Telephone": "873-5631", "ID": "", "First name": "Felix", "Address": "983 Narratives Dr. Hanover MA", "Contact for future races": "no", "Email": "pipit@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Rosario", "Gender": "female", "Age": "70", "Telephone": "363-6200", "ID": "", "First name": "Imogene", "Address": "726 Psychiatrists Pkwy. Quincy MA", "Contact for future races": "yes", "Email": "monogramming@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hamilton", "Gender": "male", "Age": "27", "Telephone": "589-8446", "ID": "", "First name": "Adam", "Address": "544 Oppressors Ln. Hingham MA", "Contact for future races": "no", "Email": "mohairs@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Navarro", "Gender": "male", "Age": "7", "Telephone": "730-8392", "ID": "", "First name": "Leon", "Address": "338 Kringles St. Norwell MA", "Contact for future races": "no", "Email": "swung@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Drake", "Gender": "male", "Age": "63", "Telephone": "491-3822", "ID": "", "First name": "Todd", "Address": "750 Beau Ln. Hanover MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "school", "Last name": "Noel", "Gender": "female", "Age": "45", "Telephone": "355-9948", "ID": "", "First name": "Antonia", "Address": "504 Mindedness Ln. Hanover MA", "Contact for future races": "no", "Email": "shadiness@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mayo", "Gender": "female", "Age": "14", "Telephone": "803-5612", "ID": "", "First name": "Eddie", "Address": "", "Contact for future races": "no", "Email": "levy@gmail.com"}, {"How did you hear about race": "", "Last name": "Stafford", "Gender": "male", "Age": "73", "Telephone": "941-8689", "ID": "", "First name": "", "Address": "341 Leopolds Ln. Quincy MA", "Contact for future races": "", "Email": "skydove@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Lara", "Gender": "male", "Age": "55", "Telephone": "575-6669", "ID": "", "First name": "Kurt", "Address": "532 Bawled St. Norwell MA", "Contact for future races": "yes", "Email": "vijayawadas@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Barlow", "Gender": "female", "Age": "28", "Telephone": "294-8368", "ID": "", "First name": "Chasity", "Address": "263 Petrochemicals St. Quincy MA", "Contact for future races": "yes", "Email": "stipulated@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Allen", "Gender": "male", "Age": "41", "Telephone": "812-4279", "ID": "", "First name": "Floyd", "Address": "525 Predetermination Ln. Braintree MA", "Contact for future races": "no", "Email": "equivalent@hotmail.com"}, {"How did you hear about race": "", "Last name": "Hodges", "Gender": "male", "Age": "90", "Telephone": "168-6750", "ID": "", "First name": "Jose", "Address": "513 Locusts St. Hanover MA", "Contact for future races": "yes", "Email": "initials@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Phelps", "Gender": "female", "Age": "92", "Telephone": "674-7852", "ID": "", "First name": "Alison", "Address": "620 Gratings St. Norwell MA", "Contact for future races": "no", "Email": "donkey@hotmail.com"}, {"How did you hear about race": "", "Last name": "Maxwell", "Gender": "female", "Age": "19", "Telephone": "409-9247", "ID": "", "First name": "Madeline", "Address": "424 Korys Way Norwell MA", "Contact for future races": "yes", "Email": "snafus@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Osborn", "Gender": "male", "Age": "37", "Telephone": "110-3676", "ID": "", "First name": "Kyle", "Address": "262 Lenoir Pkwy. Quincy MA", "Contact for future races": "no", "Email": "ch\u00e2teau@gmail.com"}, {"How did you hear about race": "church", "Last name": "Lawson", "Gender": "female", "Age": "83", "Telephone": "175-4076", "ID": "", "First name": "Jennifer", "Address": "488 Conciliators St. Hingham MA", "Contact for future races": "no", "Email": "whiskers@gmail.com"}, {"How did you hear about race": "", "Last name": "Tucker", "Gender": "male", "Age": "73", "Telephone": "853-7325", "ID": "", "First name": "Dwayne", "Address": "185 Navajos Ln. Hanover MA", "Contact for future races": "", "Email": "undetected@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Clemons", "Gender": "female", "Age": "52", "Telephone": "191-3823", "ID": "", "First name": "Jolene", "Address": "980 Pickfords Pkwy. Quincy MA", "Contact for future races": "no", "Email": "artwork@gmail.com"}, {"How did you hear about race": "", "Last name": "Kline", "Gender": "male", "Age": "58", "Telephone": "541-3103", "ID": "", "First name": "Fred", "Address": "978 Riding St. Hingham MA", "Contact for future races": "yes", "Email": "plaudit@gmail.com"}, {"How did you hear about race": "", "Last name": "", "Gender": "male", "Age": "44", "Telephone": "875-9041", "ID": "", "First name": "Ted", "Address": "349 Gearing Dr. Hingham MA", "Contact for future races": "no", "Email": "verisimilitude@gmail.com"}, {"How did you hear about race": "", "Last name": "Wyatt", "Gender": "female", "Age": "46", "Telephone": "299-3387", "ID": "", "First name": "Jeanne", "Address": "683 Uriah St. Hanover MA", "Contact for future races": "yes", "Email": "submerging@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Coffey", "Gender": "female", "Age": "69", "Telephone": "963-6153", "ID": "", "First name": "Martha", "Address": "739 Catalyst St. Weymouth MA", "Contact for future races": "yes", "Email": "mullahs@gmail.com"}, {"How did you hear about race": "", "Last name": "Paul", "Gender": "female", "Age": "62", "Telephone": "", "ID": "", "First name": "", "Address": "151 Diss Way Quincy MA", "Contact for future races": "no", "Email": "souvenirs@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Forbes", "Gender": "male", "Age": "60", "Telephone": "735-1547", "ID": "", "First name": "Danny", "Address": "165 Macmillan Dr. Weymouth MA", "Contact for future races": "", "Email": "blackheads@gmail.com"}, {"How did you hear about race": "school", "Last name": "Holman", "Gender": "female", "Age": "53", "Telephone": "", "ID": "", "First name": "Jimmie", "Address": "416 Initialed Ln. Quincy MA", "Contact for future races": "no", "Email": "whirls@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Armstrong", "Gender": "male", "Age": "37", "Telephone": "646-1061", "ID": "", "First name": "Alex", "Address": "894 Flagellation St. Weymouth MA", "Contact for future races": "yes", "Email": "mantras@gmail.com"}, {"How did you hear about race": "", "Last name": "Bowman", "Gender": "male", "Age": "60", "Telephone": "118-3810", "ID": "", "First name": "Felix", "Address": "692 Humanest Ln. Braintree MA", "Contact for future races": "no", "Email": "coinciding@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Soto", "Gender": "female", "Age": "39", "Telephone": "762-1706", "ID": "", "First name": "Alexandria", "Address": "650 Beebe Dr. Weymouth MA", "Contact for future races": "yes", "Email": "mono@gmail.com"}, {"How did you hear about race": "school", "Last name": "Cooke", "Gender": "male", "Age": "6", "Telephone": "148-5811", "ID": "", "First name": "Ron", "Address": "132 Virulence Way Braintree MA", "Contact for future races": "no", "Email": "illustrious@gmail.com"}, {"How did you hear about race": "church", "Last name": "Burton", "Gender": "male", "Age": "18", "Telephone": "753-6243", "ID": "", "First name": "Clinton", "Address": "639 Reconquering Dr. Hingham MA", "Contact for future races": "yes", "Email": "retract@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Knowles", "Gender": "male", "Age": "1", "Telephone": "264-4509", "ID": "", "First name": "Leslie", "Address": "277 Rowenas Pkwy. Weymouth MA", "Contact for future races": "", "Email": "vietnams@gmail.com"}, {"How did you hear about race": "", "Last name": "Dodson", "Gender": "male", "Age": "37", "Telephone": "628-9819", "ID": "", "First name": "Douglas", "Address": "223 Youre Dr. Weymouth MA", "Contact for future races": "yes", "Email": "snowboards@hotmail.com"}, {"How did you hear about race": "", "Last name": "Middleton", "Gender": "male", "Age": "3", "Telephone": "127-2001", "ID": "", "First name": "Erik", "Address": "57 Discountenance St. Braintree MA", "Contact for future races": "no", "Email": "beauregard@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Mcmillan", "Gender": "female", "Age": "32", "Telephone": "341-9958", "ID": "", "First name": "Sonja", "Address": "448 Hydroelectric Ln. Quincy MA", "Contact for future races": "", "Email": "kibitzes@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Adams", "Gender": "male", "Age": "71", "Telephone": "447-5141", "ID": "", "First name": "Sidney", "Address": "382 Senior Way Braintree MA", "Contact for future races": "yes", "Email": "m\u00e9tiers@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hendricks", "Gender": "female", "Age": "88", "Telephone": "112-7566", "ID": "", "First name": "Terri", "Address": "102 Puns Dr. Norwell MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Ramsey", "Gender": "female", "Age": "99", "Telephone": "441-3303", "ID": "", "First name": "Jo", "Address": "926 Tyrannosaur Dr. Braintree MA", "Contact for future races": "no", "Email": "chirped@gmail.com"}, {"How did you hear about race": "church", "Last name": "Daniel", "Gender": "female", "Age": "80", "Telephone": "", "ID": "", "First name": "Diane", "Address": "114 Incontestably Dr. Weymouth MA", "Contact for future races": "yes", "Email": "senioritys@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Dodson", "Gender": "female", "Age": "63", "Telephone": "943-9375", "ID": "", "First name": "Amber", "Address": "384 Retreat St. Hanover MA", "Contact for future races": "yes", "Email": "admirals@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Hinton", "Gender": "female", "Age": "67", "Telephone": "939-9867", "ID": "", "First name": "Kara", "Address": "112 Nab Ln. Weymouth MA", "Contact for future races": "yes", "Email": "connive@gmail.com"}, {"How did you hear about race": "school", "Last name": "Beach", "Gender": "male", "Age": "56", "Telephone": "", "ID": "", "First name": "Randy", "Address": "906 Ovulated St. Braintree MA", "Contact for future races": "yes", "Email": "almachs@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Dillon", "Gender": "female", "Age": "18", "Telephone": "370-8911", "ID": "", "First name": "Iris", "Address": "813 Adheres Ln. Hanover MA", "Contact for future races": "yes", "Email": "kipper@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mckay", "Gender": "female", "Age": "34", "Telephone": "794-1144", "ID": "", "First name": "Jeannie", "Address": "300 Masterpiece Ln. Braintree MA", "Contact for future races": "yes", "Email": "spartans@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Kemp", "Gender": "male", "Age": "94", "Telephone": "192-9565", "ID": "", "First name": "Herman", "Address": "358 Tactile Way Quincy MA", "Contact for future races": "yes", "Email": "kiddo@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Stuart", "Gender": "female", "Age": "36", "Telephone": "628-3422", "ID": "", "First name": "Terry", "Address": "889 Navy St. Norwell MA", "Contact for future races": "", "Email": "gantlets@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Hurst", "Gender": "female", "Age": "56", "Telephone": "739-7420", "ID": "", "First name": "Myrtle", "Address": "100 Immortalitys Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "candidness@gmail.com"}, {"How did you hear about race": "", "Last name": "Ratliff", "Gender": "male", "Age": "78", "Telephone": "320-2373", "ID": "", "First name": "Alvin", "Address": "883 Sealants Way Hanover MA", "Contact for future races": "no", "Email": "grumpiest@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Bass", "Gender": "female", "Age": "86", "Telephone": "496-1668", "ID": "", "First name": "Wanda", "Address": "692 Hither Ln. Hingham MA", "Contact for future races": "yes", "Email": "motormouths@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Joseph", "Gender": "", "Age": "65", "Telephone": "851-5229", "ID": "", "First name": "Herman", "Address": "", "Contact for future races": "no", "Email": "hed@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Chang", "Gender": "female", "Age": "53", "Telephone": "997-9933", "ID": "", "First name": "Liliana", "Address": "708 Unconcernedly Ln. Braintree MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Stevens", "Gender": "female", "Age": "91", "Telephone": "291-9362", "ID": "", "First name": "Sarah", "Address": "947 Slacken Ln. Norwell MA", "Contact for future races": "yes", "Email": "gadded@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hooper", "Gender": "female", "Age": "38", "Telephone": "768-9428", "ID": "", "First name": "Natasha", "Address": "584 Determines St. Quincy MA", "Contact for future races": "no", "Email": "canberras@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Nguyen", "Gender": "male", "Age": "75", "Telephone": "123-3566", "ID": "", "First name": "Gabriel", "Address": "576 Sweatshop Way Hanover MA", "Contact for future races": "yes", "Email": "groping@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hart", "Gender": "female", "Age": "91", "Telephone": "699-9109", "ID": "", "First name": "Belinda", "Address": "261 Candice St. Hingham MA", "Contact for future races": "yes", "Email": "playhouses@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Pace", "Gender": "male", "Age": "97", "Telephone": "104-6568", "ID": "", "First name": "Wallace", "Address": "694 Reemerge Ln. Quincy MA", "Contact for future races": "yes", "Email": "urinating@gmail.com"}, {"How did you hear about race": "", "Last name": "Armstrong", "Gender": "female", "Age": "86", "Telephone": "", "ID": "", "First name": "Bernice", "Address": "2 Pursuing Way Braintree MA", "Contact for future races": "yes", "Email": "reveries@gmail.com"}, {"How did you hear about race": "school", "Last name": "Estrada", "Gender": "female", "Age": "35", "Telephone": "149-6783", "ID": "", "First name": "Heather", "Address": "109 Brooks Pkwy. Quincy MA", "Contact for future races": "yes", "Email": "vertically@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Justice", "Gender": "female", "Age": "7", "Telephone": "", "ID": "", "First name": "Carmella", "Address": "800 Recall St. Braintree MA", "Contact for future races": "no", "Email": "jingoism@gmail.com"}, {"How did you hear about race": "church", "Last name": "Calderon", "Gender": "female", "Age": "21", "Telephone": "", "ID": "", "First name": "Lavonne", "Address": "385 Yest Ln. Braintree MA", "Contact for future races": "no", "Email": "aesthetics@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Matthews", "Gender": "male", "Age": "59", "Telephone": "119-2834", "ID": "", "First name": "Shawn", "Address": "965 Glues St. Hingham MA", "Contact for future races": "no", "Email": "beddings@gmail.com"}, {"How did you hear about race": "church", "Last name": "Davenport", "Gender": "female", "Age": "53", "Telephone": "998-2456", "ID": "", "First name": "Mara", "Address": "271 Toeholds Way Hingham MA", "Contact for future races": "no", "Email": "itinerant@gmail.com"}, {"How did you hear about race": "", "Last name": "Spears", "Gender": "female", "Age": "90", "Telephone": "490-3809", "ID": "", "First name": "Latasha", "Address": "257 Shilling Dr. Hanover MA", "Contact for future races": "no", "Email": "reimposes@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Nichols", "Gender": "male", "Age": "39", "Telephone": "737-7265", "ID": "", "First name": "Christopher", "Address": "383 Gins Ln. Quincy MA", "Contact for future races": "no", "Email": "berried@gmail.com"}, {"How did you hear about race": "church", "Last name": "Lewis", "Gender": "female", "Age": "11", "Telephone": "709-7892", "ID": "", "First name": "Lindsay", "Address": "407 Diffidently Dr. Hingham MA", "Contact for future races": "no", "Email": "pimpernels@gmail.com"}, {"How did you hear about race": "", "Last name": "Fuller", "Gender": "male", "Age": "65", "Telephone": "387-4177", "ID": "", "First name": "Jim", "Address": "82 Urinalysis St. Quincy MA", "Contact for future races": "no", "Email": "beheld@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Giles", "Gender": "male", "Age": "76", "Telephone": "710-9016", "ID": "", "First name": "Bobby", "Address": "46 Flagons Ln. Weymouth MA", "Contact for future races": "no", "Email": "eyelashes@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Dalton", "Gender": "female", "Age": "91", "Telephone": "343-2126", "ID": "", "First name": "Terry", "Address": "853 Boards Pkwy. Norwell MA", "Contact for future races": "no", "Email": "maces@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Wilcox", "Gender": "female", "Age": "40", "Telephone": "891-4886", "ID": "", "First name": "Millicent", "Address": "584 Lanced St. Hanover MA", "Contact for future races": "no", "Email": "chivalrous@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Mclean", "Gender": "female", "Age": "82", "Telephone": "833-6873", "ID": "", "First name": "Lana", "Address": "", "Contact for future races": "no", "Email": "dodders@hotmail.com"}, {"How did you hear about race": "", "Last name": "Cardenas", "Gender": "female", "Age": "61", "Telephone": "195-6542", "ID": "", "First name": "Stacie", "Address": "561 Continually St. Quincy MA", "Contact for future races": "yes", "Email": "complemented@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Lamb", "Gender": "female", "Age": "22", "Telephone": "721-3017", "ID": "", "First name": "Jayne", "Address": "77 Thicknesss St. Hingham MA", "Contact for future races": "no", "Email": "landladys@gmail.com"}, {"How did you hear about race": "church", "Last name": "Perkins", "Gender": "", "Age": "27", "Telephone": "686-1646", "ID": "", "First name": "Rafael", "Address": "255 Implode Ln. Hanover MA", "Contact for future races": "", "Email": "warrior@hotmail.com"}, {"How did you hear about race": "", "Last name": "Newton", "Gender": "male", "Age": "20", "Telephone": "664-1390", "ID": "", "First name": "Michael", "Address": "885 Weaver Way Hingham MA", "Contact for future races": "yes", "Email": "comings@gmail.com"}, {"How did you hear about race": "", "Last name": "Vasquez", "Gender": "female", "Age": "66", "Telephone": "743-4221", "ID": "", "First name": "Virgie", "Address": "276 Stinginess Way Weymouth MA", "Contact for future races": "no", "Email": "prettinesss@gmail.com"}, {"How did you hear about race": "school", "Last name": "Bolton", "Gender": "female", "Age": "7", "Telephone": "483-8537", "ID": "", "First name": "Agnes", "Address": "645 Ambrosias St. Weymouth MA", "Contact for future races": "", "Email": "dissects@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Sosa", "Gender": "female", "Age": "75", "Telephone": "900-9699", "ID": "", "First name": "Rowena", "Address": "350 Ashkenazim Way Norwell MA", "Contact for future races": "no", "Email": "ebbed@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Lowery", "Gender": "female", "Age": "76", "Telephone": "127-6973", "ID": "", "First name": "Alba", "Address": "339 Laundress Dr. Hanover MA", "Contact for future races": "no", "Email": "fires@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Beck", "Gender": "female", "Age": "93", "Telephone": "827-1110", "ID": "", "First name": "Elva", "Address": "216 Bathing Dr. Norwell MA", "Contact for future races": "no", "Email": "disembodies@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Morin", "Gender": "female", "Age": "67", "Telephone": "839-2179", "ID": "", "First name": "Joan", "Address": "51 Bo\u00f6Tes Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "nailbrushs@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Sargent", "Gender": "female", "Age": "43", "Telephone": "943-9898", "ID": "", "First name": "Elena", "Address": "989 Disproves St. Quincy MA", "Contact for future races": "no", "Email": "insomniac@gmail.com"}, {"How did you hear about race": "school", "Last name": "Valentine", "Gender": "female", "Age": "41", "Telephone": "779-8488", "ID": "", "First name": "Velma", "Address": "279 Saintliness Dr. Quincy MA", "Contact for future races": "yes", "Email": "creeper@gmail.com"}, {"How did you hear about race": "school", "Last name": "Frost", "Gender": "female", "Age": "19", "Telephone": "758-3975", "ID": "", "First name": "Betty", "Address": "773 Coasted St. Quincy MA", "Contact for future races": "yes", "Email": "motivation@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Nunez", "Gender": "male", "Age": "33", "Telephone": "417-2045", "ID": "", "First name": "Ian", "Address": "4 Bishoprics Way Braintree MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Rowe", "Gender": "female", "Age": "75", "Telephone": "620-3921", "ID": "", "First name": "Glenda", "Address": "638 Noses Ln. Braintree MA", "Contact for future races": "no", "Email": "computations@hotmail.com"}, {"How did you hear about race": "", "Last name": "Hale", "Gender": "male", "Age": "21", "Telephone": "765-5452", "ID": "", "First name": "Jose", "Address": "16 Wiretaps Dr. Norwell MA", "Contact for future races": "yes", "Email": "single@gmail.com"}, {"How did you hear about race": "", "Last name": "Stein", "Gender": "male", "Age": "61", "Telephone": "498-3002", "ID": "", "First name": "Daniel", "Address": "666 Cobwebs Dr. Braintree MA", "Contact for future races": "yes", "Email": "extemporaneous@gmail.com"}, {"How did you hear about race": "", "Last name": "Gates", "Gender": "female", "Age": "12", "Telephone": "925-6643", "ID": "", "First name": "Margie", "Address": "448 Jealousies Dr. Weymouth MA", "Contact for future races": "yes", "Email": "gauze@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Cruz", "Gender": "male", "Age": "57", "Telephone": "837-8232", "ID": "", "First name": "Sergio", "Address": "783 Stores Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "rugs@gmail.com"}, {"How did you hear about race": "school", "Last name": "Diaz", "Gender": "female", "Age": "16", "Telephone": "121-2479", "ID": "", "First name": "Shari", "Address": "916 Indemnities Ln. Quincy MA", "Contact for future races": "yes", "Email": "proffer@hotmail.com"}, {"How did you hear about race": "", "Last name": "Cobb", "Gender": "male", "Age": "38", "Telephone": "533-6303", "ID": "", "First name": "Darren", "Address": "695 Defendants Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "laxer@hotmail.com"}, {"How did you hear about race": "", "Last name": "Jarvis", "Gender": "female", "Age": "48", "Telephone": "899-6528", "ID": "", "First name": "Mai", "Address": "352 Receivers St. Quincy MA", "Contact for future races": "yes", "Email": "conspiracies@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Clemons", "Gender": "male", "Age": "31", "Telephone": "", "ID": "", "First name": "", "Address": "817 Greensboro St. Weymouth MA", "Contact for future races": "yes", "Email": "desdemona@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Solomon", "Gender": "female", "Age": "31", "Telephone": "946-9324", "ID": "", "First name": "", "Address": "63 Maintaining Dr. Braintree MA", "Contact for future races": "no", "Email": "unfairness@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Pruitt", "Gender": "male", "Age": "89", "Telephone": "222-2070", "ID": "", "First name": "Claude", "Address": "854 Reactivated Dr. Norwell MA", "Contact for future races": "no", "Email": "slumbers@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mcmillan", "Gender": "female", "Age": "36", "Telephone": "104-6458", "ID": "", "First name": "Doris", "Address": "544 Pharmacopoeia Ln. Norwell MA", "Contact for future races": "yes", "Email": "ashen@gmail.com"}, {"How did you hear about race": "church", "Last name": "Campos", "Gender": "female", "Age": "45", "Telephone": "864-4240", "ID": "", "First name": "", "Address": "103 Gauntest Ln. Weymouth MA", "Contact for future races": "yes", "Email": "contemporaneous@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Morrow", "Gender": "male", "Age": "35", "Telephone": "", "ID": "", "First name": "Kirk", "Address": "854 Hooper Ln. Weymouth MA", "Contact for future races": "yes", "Email": "brigantines@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Craft", "Gender": "female", "Age": "51", "Telephone": "355-9645", "ID": "", "First name": "Erna", "Address": "804 Scows St. Braintree MA", "Contact for future races": "yes", "Email": "hashing@hotmail.com"}, {"How did you hear about race": "", "Last name": "Kirby", "Gender": "male", "Age": "5", "Telephone": "437-8132", "ID": "", "First name": "Jimmy", "Address": "544 Sabines St. Braintree MA", "Contact for future races": "no", "Email": "slackens@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Cash", "Gender": "male", "Age": "28", "Telephone": "260-6007", "ID": "", "First name": "Jonathan", "Address": "477 Oozes St. Hanover MA", "Contact for future races": "no", "Email": "jackknives@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Merritt", "Gender": "male", "Age": "6", "Telephone": "809-8613", "ID": "", "First name": "Eric", "Address": "329 Choreographs Dr. Hanover MA", "Contact for future races": "no", "Email": "behalfs@gmail.com"}, {"How did you hear about race": "school", "Last name": "Owen", "Gender": "female", "Age": "14", "Telephone": "654-6781", "ID": "", "First name": "Rosalinda", "Address": "83 Portaging Ln. Braintree MA", "Contact for future races": "yes", "Email": "serendipitous@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Kim", "Gender": "female", "Age": "89", "Telephone": "232-1083", "ID": "", "First name": "Gabrielle", "Address": "783 Refiners St. Hanover MA", "Contact for future races": "no", "Email": "lawfulnesss@gmail.com"}, {"How did you hear about race": "", "Last name": "Summers", "Gender": "", "Age": "86", "Telephone": "352-8517", "ID": "", "First name": "Angela", "Address": "870 Supercharging Dr. Braintree MA", "Contact for future races": "no", "Email": "rebuild@gmail.com"}, {"How did you hear about race": "", "Last name": "Jefferson", "Gender": "male", "Age": "67", "Telephone": "803-5299", "ID": "", "First name": "Dale", "Address": "281 Glasgow Ln. Braintree MA", "Contact for future races": "yes", "Email": "stickups@gmail.com"}, {"How did you hear about race": "", "Last name": "Zamora", "Gender": "female", "Age": "68", "Telephone": "", "ID": "", "First name": "Lynne", "Address": "409 Howard Dr. Hingham MA", "Contact for future races": "no", "Email": "cheapskate@gmail.com"}, {"How did you hear about race": "school", "Last name": "Valencia", "Gender": "male", "Age": "67", "Telephone": "643-6162", "ID": "", "First name": "Kyle", "Address": "330 Mink St. Norwell MA", "Contact for future races": "no", "Email": "ancestral@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Contreras", "Gender": "male", "Age": "11", "Telephone": "690-9417", "ID": "", "First name": "Shawn", "Address": "66 Justifications Ln. Quincy MA", "Contact for future races": "no", "Email": "finch@gmail.com"}, {"How did you hear about race": "church", "Last name": "Riggs", "Gender": "male", "Age": "57", "Telephone": "191-6203", "ID": "", "First name": "Jon", "Address": "779 Moiseyevs St. Weymouth MA", "Contact for future races": "no", "Email": "estuarys@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Holman", "Gender": "male", "Age": "97", "Telephone": "471-5379", "ID": "", "First name": "Harvey", "Address": "261 Untimely Dr. Hanover MA", "Contact for future races": "yes", "Email": "straights@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Lowery", "Gender": "male", "Age": "32", "Telephone": "808-1704", "ID": "", "First name": "James", "Address": "941 Airports Ln. Braintree MA", "Contact for future races": "no", "Email": "jaywalk@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Bass", "Gender": "male", "Age": "82", "Telephone": "926-8456", "ID": "", "First name": "", "Address": "558 Grungiest St. Braintree MA", "Contact for future races": "yes", "Email": "nipples@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Horne", "Gender": "female", "Age": "12", "Telephone": "805-7364", "ID": "", "First name": "Rose", "Address": "918 Bellybuttons St. Norwell MA", "Contact for future races": "yes", "Email": "flatfish@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mccray", "Gender": "female", "Age": "65", "Telephone": "", "ID": "", "First name": "Mabel", "Address": "907 Olins Way Quincy MA", "Contact for future races": "no", "Email": "bjorks@hotmail.com"}, {"How did you hear about race": "", "Last name": "Dickson", "Gender": "male", "Age": "24", "Telephone": "227-6084", "ID": "", "First name": "", "Address": "520 Digitized Ln. Braintree MA", "Contact for future races": "no", "Email": "epitomizes@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Reid", "Gender": "male", "Age": "73", "Telephone": "655-8366", "ID": "", "First name": "Isaac", "Address": "579 Overshoots Ln. Weymouth MA", "Contact for future races": "", "Email": "flair@gmail.com"}, {"How did you hear about race": "church", "Last name": "Hubbard", "Gender": "female", "Age": "4", "Telephone": "198-2062", "ID": "", "First name": "Cassie", "Address": "191 Discourse St. Weymouth MA", "Contact for future races": "yes", "Email": "karina@gmail.com"}, {"How did you hear about race": "school", "Last name": "Dennis", "Gender": "male", "Age": "75", "Telephone": "203-8540", "ID": "", "First name": "Ken", "Address": "292 Anabel St. Hanover MA", "Contact for future races": "yes", "Email": "konrad@gmail.com"}, {"How did you hear about race": "", "Last name": "Powers", "Gender": "female", "Age": "69", "Telephone": "311-5173", "ID": "", "First name": "Jana", "Address": "260 Flit St. Braintree MA", "Contact for future races": "no", "Email": "margarita@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Clements", "Gender": "female", "Age": "75", "Telephone": "423-8156", "ID": "", "First name": "Vivian", "Address": "830 Upgrading Way Norwell MA", "Contact for future races": "yes", "Email": "chandras@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hickman", "Gender": "female", "Age": "29", "Telephone": "623-4129", "ID": "", "First name": "Socorro", "Address": "896 Subtract St. Hanover MA", "Contact for future races": "yes", "Email": "blinds@hotmail.com"}, {"How did you hear about race": "", "Last name": "Waters", "Gender": "", "Age": "72", "Telephone": "129-3084", "ID": "", "First name": "Tammi", "Address": "519 Pilfered Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "ills@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mcintyre", "Gender": "male", "Age": "38", "Telephone": "133-3233", "ID": "", "First name": "Virgil", "Address": "220 Semester St. Braintree MA", "Contact for future races": "no", "Email": "burglarized@hotmail.com"}, {"How did you hear about race": "", "Last name": "Dudley", "Gender": "male", "Age": "66", "Telephone": "", "ID": "", "First name": "Glen", "Address": "850 Equation St. Hanover MA", "Contact for future races": "yes", "Email": "mahogany@gmail.com"}, {"How did you hear about race": "school", "Last name": "Sykes", "Gender": "female", "Age": "8", "Telephone": "184-3731", "ID": "", "First name": "Amparo", "Address": "855 Anywhere St. Quincy MA", "Contact for future races": "yes", "Email": "circuiting@gmail.com"}, {"How did you hear about race": "school", "Last name": "Waller", "Gender": "female", "Age": "86", "Telephone": "300-9910", "ID": "", "First name": "Sonja", "Address": "417 Egret St. Hanover MA", "Contact for future races": "no", "Email": "crocodiles@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Burks", "Gender": "male", "Age": "76", "Telephone": "979-5341", "ID": "", "First name": "Steve", "Address": "109 Damon Way Quincy MA", "Contact for future races": "yes", "Email": "marquezs@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Perez", "Gender": "female", "Age": "0", "Telephone": "", "ID": "", "First name": "Loretta", "Address": "12 Wylie Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "ravens@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Harper", "Gender": "", "Age": "15", "Telephone": "810-6528", "ID": "", "First name": "Jacklyn", "Address": "410 Westbound Pkwy. Norwell MA", "Contact for future races": "yes", "Email": "desperado@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Cooke", "Gender": "female", "Age": "83", "Telephone": "131-5949", "ID": "", "First name": "Teri", "Address": "47 Motive Ln. Norwell MA", "Contact for future races": "no", "Email": "typography@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Berger", "Gender": "female", "Age": "74", "Telephone": "622-8751", "ID": "", "First name": "Susanna", "Address": "499 Blubbers Way Hanover MA", "Contact for future races": "yes", "Email": "corralled@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Cantrell", "Gender": "female", "Age": "81", "Telephone": "634-5829", "ID": "", "First name": "Nellie", "Address": "164 Pepper Dr. Quincy MA", "Contact for future races": "yes", "Email": "ms@gmail.com"}, {"How did you hear about race": "church", "Last name": "Weeks", "Gender": "male", "Age": "70", "Telephone": "833-6013", "ID": "", "First name": "Glen", "Address": "539 Persnickety Way Norwell MA", "Contact for future races": "yes", "Email": "caduceus@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Torres", "Gender": "male", "Age": "19", "Telephone": "496-2189", "ID": "", "First name": "Ross", "Address": "178 Permanent Dr. Hingham MA", "Contact for future races": "no", "Email": "margret@gmail.com"}, {"How did you hear about race": "", "Last name": "Gallegos", "Gender": "male", "Age": "17", "Telephone": "850-3720", "ID": "", "First name": "Byron", "Address": "899 Beelines St. Hingham MA", "Contact for future races": "no", "Email": "imperiously@gmail.com"}, {"How did you hear about race": "church", "Last name": "Bryan", "Gender": "male", "Age": "6", "Telephone": "732-9980", "ID": "", "First name": "Harvey", "Address": "346 Storekeepers Dr. Norwell MA", "Contact for future races": "yes", "Email": "tapping@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Nguyen", "Gender": "female", "Age": "93", "Telephone": "816-1111", "ID": "", "First name": "Mamie", "Address": "", "Contact for future races": "yes", "Email": "scissors@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Williamson", "Gender": "female", "Age": "87", "Telephone": "559-1011", "ID": "", "First name": "", "Address": "20 Beardmore St. Hingham MA", "Contact for future races": "yes", "Email": "thoughtfully@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Black", "Gender": "male", "Age": "33", "Telephone": "788-6123", "ID": "", "First name": "Mario", "Address": "200 Welts Dr. Braintree MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Kramer", "Gender": "male", "Age": "13", "Telephone": "739-8692", "ID": "", "First name": "Edgar", "Address": "237 Outplacement St. Weymouth MA", "Contact for future races": "yes", "Email": "lilacs@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Roth", "Gender": "male", "Age": "51", "Telephone": "412-8587", "ID": "", "First name": "Barry", "Address": "980 Driveled St. Hingham MA", "Contact for future races": "no", "Email": "cognate@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Cobb", "Gender": "male", "Age": "44", "Telephone": "125-7635", "ID": "", "First name": "Marvin", "Address": "17 Ornatenesss Way Braintree MA", "Contact for future races": "yes", "Email": "pickups@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Delgado", "Gender": "", "Age": "4", "Telephone": "808-6496", "ID": "", "First name": "Ophelia", "Address": "29 Judgeships Dr. Norwell MA", "Contact for future races": "yes", "Email": "pregnant@gmail.com"}, {"How did you hear about race": "church", "Last name": "Guzman", "Gender": "female", "Age": "91", "Telephone": "584-2157", "ID": "", "First name": "Angeline", "Address": "858 Clarifying St. Hingham MA", "Contact for future races": "yes", "Email": "bagpipes@gmail.com"}, {"How did you hear about race": "church", "Last name": "Salinas", "Gender": "female", "Age": "45", "Telephone": "748-7233", "ID": "", "First name": "Berta", "Address": "757 Rescinds St. Hingham MA", "Contact for future races": "no", "Email": "golds@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Foster", "Gender": "female", "Age": "30", "Telephone": "301-7613", "ID": "", "First name": "Felecia", "Address": "152 Emphysema Ln. Weymouth MA", "Contact for future races": "no", "Email": "outrun@hotmail.com"}, {"How did you hear about race": "school", "Last name": "French", "Gender": "male", "Age": "53", "Telephone": "573-3551", "ID": "", "First name": "Jack", "Address": "690 Unreleased Ln. Braintree MA", "Contact for future races": "yes", "Email": "disobeyed@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Gillespie", "Gender": "female", "Age": "14", "Telephone": "454-8883", "ID": "", "First name": "Willa", "Address": "258 Potassiums St. Quincy MA", "Contact for future races": "no", "Email": "viscounts@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Frank", "Gender": "male", "Age": "53", "Telephone": "506-7900", "ID": "", "First name": "Michael", "Address": "422 Scoutmasters Way Hingham MA", "Contact for future races": "no", "Email": "chaparral@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Sawyer", "Gender": "male", "Age": "69", "Telephone": "973-6198", "ID": "", "First name": "Zachary", "Address": "994 Splutters St. Norwell MA", "Contact for future races": "yes", "Email": "kingdoms@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Taylor", "Gender": "female", "Age": "64", "Telephone": "827-8906", "ID": "", "First name": "Kaitlin", "Address": "507 Demos Dr. Hingham MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Raymond", "Gender": "male", "Age": "64", "Telephone": "657-3776", "ID": "", "First name": "Zachary", "Address": "28 Khartoums Ln. Braintree MA", "Contact for future races": "no", "Email": "coasting@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Taylor", "Gender": "female", "Age": "13", "Telephone": "308-2971", "ID": "", "First name": "", "Address": "379 Gardening St. Quincy MA", "Contact for future races": "no", "Email": "tried@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Greer", "Gender": "female", "Age": "13", "Telephone": "171-7933", "ID": "", "First name": "Rosalind", "Address": "71 Gantrys St. Quincy MA", "Contact for future races": "no", "Email": "discoverys@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Swanson", "Gender": "male", "Age": "57", "Telephone": "717-3433", "ID": "", "First name": "Mitchell", "Address": "913 France St. Hanover MA", "Contact for future races": "yes", "Email": "prut@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Beck", "Gender": "male", "Age": "75", "Telephone": "507-9997", "ID": "", "First name": "Lonnie", "Address": "865 Swards St. Quincy MA", "Contact for future races": "yes", "Email": "etymologist@hotmail.com"}, {"How did you hear about race": "", "Last name": "Fitzgerald", "Gender": "male", "Age": "87", "Telephone": "691-2089", "ID": "", "First name": "Gilbert", "Address": "997 Diagonals St. Norwell MA", "Contact for future races": "yes", "Email": "peevishnesss@gmail.com"}, {"How did you hear about race": "school", "Last name": "Guy", "Gender": "female", "Age": "55", "Telephone": "465-6058", "ID": "", "First name": "Lilian", "Address": "396 Koreans Dr. Weymouth MA", "Contact for future races": "yes", "Email": "rightfulnesss@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Oneil", "Gender": "female", "Age": "88", "Telephone": "236-9653", "ID": "", "First name": "Kristina", "Address": "342 Pakistans Ln. Hingham MA", "Contact for future races": "yes", "Email": "pops@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Casey", "Gender": "male", "Age": "64", "Telephone": "801-7994", "ID": "", "First name": "Angel", "Address": "710 Mari Pkwy. Quincy MA", "Contact for future races": "yes", "Email": "identification@gmail.com"}, {"How did you hear about race": "church", "Last name": "Mckinney", "Gender": "female", "Age": "64", "Telephone": "179-5827", "ID": "", "First name": "Shawn", "Address": "811 Sauntered St. Braintree MA", "Contact for future races": "yes", "Email": "phoneticss@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Dennis", "Gender": "male", "Age": "30", "Telephone": "243-5503", "ID": "", "First name": "Franklin", "Address": "291 Scarifies St. Hingham MA", "Contact for future races": "yes", "Email": "squatters@gmail.com"}, {"How did you hear about race": "church", "Last name": "Moses", "Gender": "female", "Age": "22", "Telephone": "582-8113", "ID": "", "First name": "Rebecca", "Address": "443 Yamagatas Ln. Braintree MA", "Contact for future races": "no", "Email": "hosts@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Foley", "Gender": "male", "Age": "33", "Telephone": "748-1180", "ID": "", "First name": "Armando", "Address": "735 Montpelier Pkwy. Hingham MA", "Contact for future races": "no", "Email": "crotchets@gmail.com"}, {"How did you hear about race": "church", "Last name": "Contreras", "Gender": "female", "Age": "57", "Telephone": "595-5489", "ID": "", "First name": "Isabella", "Address": "211 Guzzle St. Hingham MA", "Contact for future races": "yes", "Email": "shamans@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Carpenter", "Gender": "female", "Age": "95", "Telephone": "183-3766", "ID": "", "First name": "Angelita", "Address": "811 Classicals St. Hanover MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Estes", "Gender": "female", "Age": "46", "Telephone": "831-4064", "ID": "", "First name": "Tracy", "Address": "613 Lazying Way Hingham MA", "Contact for future races": "yes", "Email": "flying@gmail.com"}, {"How did you hear about race": "", "Last name": "Frederick", "Gender": "female", "Age": "43", "Telephone": "653-2232", "ID": "", "First name": "Lisa", "Address": "35 Jiggle Ln. Quincy MA", "Contact for future races": "no", "Email": "cannier@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mejia", "Gender": "male", "Age": "9", "Telephone": "224-2444", "ID": "", "First name": "Henry", "Address": "320 Freshwaters St. Quincy MA", "Contact for future races": "yes", "Email": "illnesss@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Hays", "Gender": "", "Age": "43", "Telephone": "939-8697", "ID": "", "First name": "Addie", "Address": "6 Centrals St. Braintree MA", "Contact for future races": "yes", "Email": "loci@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Wiggins", "Gender": "male", "Age": "6", "Telephone": "740-1682", "ID": "", "First name": "Micheal", "Address": "939 Exempts Ln. Hingham MA", "Contact for future races": "no", "Email": "benefactors@gmail.com"}, {"How did you hear about race": "", "Last name": "Adkins", "Gender": "male", "Age": "10", "Telephone": "810-1044", "ID": "", "First name": "Morris", "Address": "484 Inhabit Dr. Weymouth MA", "Contact for future races": "no", "Email": "bludgeons@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Mcpherson", "Gender": "", "Age": "1", "Telephone": "329-3355", "ID": "", "First name": "Georgina", "Address": "183 Recriminations Dr. Weymouth MA", "Contact for future races": "no", "Email": "wellands@gmail.com"}, {"How did you hear about race": "", "Last name": "Case", "Gender": "female", "Age": "10", "Telephone": "883-2985", "ID": "", "First name": "Mariana", "Address": "746 Allays St. Hingham MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "came last year", "Last name": "Trujillo", "Gender": "female", "Age": "10", "Telephone": "625-7889", "ID": "", "First name": "Nadia", "Address": "659 Kerensky St. Norwell MA", "Contact for future races": "yes", "Email": "unstopped@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Molina", "Gender": "male", "Age": "92", "Telephone": "299-1538", "ID": "", "First name": "Kyle", "Address": "119 Overstay Pkwy. Hanover MA", "Contact for future races": "no", "Email": "percolations@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mclaughlin", "Gender": "", "Age": "45", "Telephone": "358-7225", "ID": "", "First name": "", "Address": "426 Muds Ln. Hingham MA", "Contact for future races": "no", "Email": "wilkersons@gmail.com"}, {"How did you hear about race": "", "Last name": "Rivers", "Gender": "male", "Age": "52", "Telephone": "256-6565", "ID": "", "First name": "Benjamin", "Address": "883 Gibbets Dr. Quincy MA", "Contact for future races": "no", "Email": "catalyzes@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Moreno", "Gender": "female", "Age": "76", "Telephone": "505-7773", "ID": "", "First name": "Doris", "Address": "616 Rush Ln. Braintree MA", "Contact for future races": "yes", "Email": "acreages@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Mullen", "Gender": "female", "Age": "50", "Telephone": "850-2806", "ID": "", "First name": "Brittany", "Address": "74 Interrogatives St. Weymouth MA", "Contact for future races": "no", "Email": "geysers@gmail.com"}, {"How did you hear about race": "", "Last name": "Barber", "Gender": "", "Age": "81", "Telephone": "114-4694", "ID": "", "First name": "Peter", "Address": "894 Bleeps Dr. Hanover MA", "Contact for future races": "no", "Email": "certifications@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Olsen", "Gender": "male", "Age": "53", "Telephone": "", "ID": "", "First name": "Bruce", "Address": "673 Wale Ln. Braintree MA", "Contact for future races": "yes", "Email": "gybing@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Summers", "Gender": "female", "Age": "2", "Telephone": "313-4057", "ID": "", "First name": "Marissa", "Address": "", "Contact for future races": "yes", "Email": "cheep@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Brock", "Gender": "female", "Age": "2", "Telephone": "467-2249", "ID": "", "First name": "Annette", "Address": "", "Contact for future races": "yes", "Email": "disavowals@gmail.com"}, {"How did you hear about race": "church", "Last name": "Daugherty", "Gender": "male", "Age": "25", "Telephone": "753-6932", "ID": "", "First name": "Dwayne", "Address": "643 Cubism Ln. Quincy MA", "Contact for future races": "yes", "Email": "upturning@gmail.com"}, {"How did you hear about race": "", "Last name": "Wiley", "Gender": "male", "Age": "60", "Telephone": "851-7338", "ID": "", "First name": "Ramon", "Address": "257 Wooliest St. Norwell MA", "Contact for future races": "yes", "Email": "retributions@gmail.com"}, {"How did you hear about race": "", "Last name": "Gilmore", "Gender": "male", "Age": "92", "Telephone": "", "ID": "", "First name": "Salvador", "Address": "842 Davidsons St. Hanover MA", "Contact for future races": "no", "Email": "clunky@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Reynolds", "Gender": "male", "Age": "37", "Telephone": "985-5703", "ID": "", "First name": "Phillip", "Address": "919 Garoting Dr. Weymouth MA", "Contact for future races": "no", "Email": "honorary@hotmail.com"}, {"How did you hear about race": "", "Last name": "Martin", "Gender": "female", "Age": "27", "Telephone": "643-5717", "ID": "", "First name": "Madelyn", "Address": "848 Toiler Dr. Hingham MA", "Contact for future races": "yes", "Email": "paran\u00e1@gmail.com"}, {"How did you hear about race": "", "Last name": "Buchanan", "Gender": "male", "Age": "22", "Telephone": "393-7103", "ID": "", "First name": "", "Address": "901 Interviewed Dr. Hingham MA", "Contact for future races": "no", "Email": "rushmore@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Montgomery", "Gender": "female", "Age": "4", "Telephone": "738-2206", "ID": "", "First name": "Sheri", "Address": "783 Burials Ln. Hanover MA", "Contact for future races": "yes", "Email": "stipple@hotmail.com"}, {"How did you hear about race": "", "Last name": "Compton", "Gender": "male", "Age": "65", "Telephone": "940-9723", "ID": "", "First name": "Vernon", "Address": "111 Dross Way Norwell MA", "Contact for future races": "no", "Email": "observances@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Stanton", "Gender": "female", "Age": "0", "Telephone": "737-2949", "ID": "", "First name": "Natasha", "Address": "834 Rhee Ln. Weymouth MA", "Contact for future races": "no", "Email": "reservists@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Valentine", "Gender": "male", "Age": "24", "Telephone": "460-1134", "ID": "", "First name": "Ralph", "Address": "66 Crosschecked Dr. Weymouth MA", "Contact for future races": "yes", "Email": "travestied@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Cunningham", "Gender": "female", "Age": "5", "Telephone": "123-4937", "ID": "", "First name": "Lourdes", "Address": "108 Detox Dr. Quincy MA", "Contact for future races": "no", "Email": "spinnaker@gmail.com"}, {"How did you hear about race": "school", "Last name": "Herrera", "Gender": "male", "Age": "9", "Telephone": "664-8815", "ID": "", "First name": "Julio", "Address": "747 Desk Ln. Quincy MA", "Contact for future races": "no", "Email": "hassle@gmail.com"}, {"How did you hear about race": "school", "Last name": "Hughes", "Gender": "female", "Age": "48", "Telephone": "117-7941", "ID": "", "First name": "Jasmine", "Address": "741 Sol Way Braintree MA", "Contact for future races": "no", "Email": "cosmogonys@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Hood", "Gender": "female", "Age": "88", "Telephone": "515-8064", "ID": "", "First name": "Helena", "Address": "343 Darks St. Weymouth MA", "Contact for future races": "no", "Email": "leviathans@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Andrews", "Gender": "", "Age": "41", "Telephone": "857-5892", "ID": "", "First name": "Sonja", "Address": "893 Concertmaster Pkwy. Braintree MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Goodwin", "Gender": "female", "Age": "0", "Telephone": "808-1708", "ID": "", "First name": "Shannon", "Address": "944 Cosmically St. Weymouth MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Russo", "Gender": "male", "Age": "90", "Telephone": "656-9348", "ID": "", "First name": "Rene", "Address": "788 Conjoins St. Braintree MA", "Contact for future races": "no", "Email": "palindromes@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Riley", "Gender": "", "Age": "81", "Telephone": "773-9677", "ID": "", "First name": "Jay", "Address": "767 Converse Pkwy. Quincy MA", "Contact for future races": "no", "Email": "chattels@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Luna", "Gender": "male", "Age": "65", "Telephone": "866-1355", "ID": "", "First name": "Alan", "Address": "", "Contact for future races": "yes", "Email": "bosn@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Lopez", "Gender": "female", "Age": "8", "Telephone": "863-6560", "ID": "", "First name": "Cara", "Address": "507 Misapprehending St. Quincy MA", "Contact for future races": "no", "Email": "pantheists@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Rojas", "Gender": "", "Age": "76", "Telephone": "400-5001", "ID": "", "First name": "Isabella", "Address": "654 Felicity Dr. Hingham MA", "Contact for future races": "no", "Email": "torahs@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mejia", "Gender": "female", "Age": "35", "Telephone": "883-7167", "ID": "", "First name": "Beverley", "Address": "625 Scoreboards St. Weymouth MA", "Contact for future races": "no", "Email": "gambolling@gmail.com"}, {"How did you hear about race": "", "Last name": "Delacruz", "Gender": "male", "Age": "43", "Telephone": "441-9532", "ID": "", "First name": "Bobby", "Address": "349 Retirees St. Weymouth MA", "Contact for future races": "yes", "Email": "histrionicss@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Odom", "Gender": "female", "Age": "47", "Telephone": "822-5805", "ID": "", "First name": "Kristin", "Address": "74 Wolves St. Hingham MA", "Contact for future races": "yes", "Email": "platoons@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Waters", "Gender": "female", "Age": "20", "Telephone": "205-9175", "ID": "", "First name": "Tammi", "Address": "604 Frustrated St. Weymouth MA", "Contact for future races": "yes", "Email": "vincent@hotmail.com"}, {"How did you hear about race": "", "Last name": "Myers", "Gender": "female", "Age": "13", "Telephone": "676-6245", "ID": "", "First name": "Shelby", "Address": "857 Bestrides Dr. Hingham MA", "Contact for future races": "yes", "Email": "prosecution@hotmail.com"}, {"How did you hear about race": "", "Last name": "Wolf", "Gender": "male", "Age": "46", "Telephone": "646-1252", "ID": "", "First name": "Eduardo", "Address": "", "Contact for future races": "no", "Email": "mademoiselles@gmail.com"}, {"How did you hear about race": "", "Last name": "Ware", "Gender": "male", "Age": "43", "Telephone": "235-5462", "ID": "", "First name": "Johnnie", "Address": "", "Contact for future races": "no", "Email": "livestock@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Miles", "Gender": "female", "Age": "32", "Telephone": "102-2303", "ID": "", "First name": "Reyna", "Address": "734 Undercurrents Dr. Hingham MA", "Contact for future races": "yes", "Email": "expanses@gmail.com"}, {"How did you hear about race": "school", "Last name": "Blanchard", "Gender": "male", "Age": "19", "Telephone": "525-5136", "ID": "", "First name": "Don", "Address": "843 Councilman St. Weymouth MA", "Contact for future races": "yes", "Email": "eminems@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Key", "Gender": "female", "Age": "53", "Telephone": "928-8032", "ID": "", "First name": "Leigh", "Address": "", "Contact for future races": "no", "Email": "consomm\u00e9s@hotmail.com"}, {"How did you hear about race": "", "Last name": "Kerr", "Gender": "male", "Age": "8", "Telephone": "398-8654", "ID": "", "First name": "Rodney", "Address": "922 Stenographic Dr. Braintree MA", "Contact for future races": "no", "Email": ""}, {"How did you hear about race": "", "Last name": "Jacobs", "Gender": "female", "Age": "91", "Telephone": "554-5796", "ID": "", "First name": "Teresa", "Address": "647 Dunks Dr. Hanover MA", "Contact for future races": "no", "Email": "crape@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Vargas", "Gender": "female", "Age": "68", "Telephone": "361-6959", "ID": "", "First name": "Meagan", "Address": "237 Colonels Way Norwell MA", "Contact for future races": "no", "Email": "handicraft@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Cochran", "Gender": "female", "Age": "52", "Telephone": "860-7176", "ID": "", "First name": "Imelda", "Address": "60 Locke Ln. Hingham MA", "Contact for future races": "", "Email": "unescos@gmail.com"}, {"How did you hear about race": "", "Last name": "Weeks", "Gender": "male", "Age": "2", "Telephone": "574-8002", "ID": "", "First name": "Perry", "Address": "700 Aegean Dr. Hingham MA", "Contact for future races": "yes", "Email": "triplicate@hotmail.com"}, {"How did you hear about race": "", "Last name": "Castaneda", "Gender": "female", "Age": "63", "Telephone": "356-8240", "ID": "", "First name": "Aida", "Address": "681 Laze St. Norwell MA", "Contact for future races": "no", "Email": "assortment@gmail.com"}, {"How did you hear about race": "", "Last name": "Bates", "Gender": "male", "Age": "44", "Telephone": "741-9976", "ID": "", "First name": "Corey", "Address": "483 Ingeniously Dr. Norwell MA", "Contact for future races": "no", "Email": "starvation@gmail.com"}, {"How did you hear about race": "church", "Last name": "Hammond", "Gender": "female", "Age": "12", "Telephone": "358-4218", "ID": "", "First name": "Wanda", "Address": "957 Spiels Pkwy. Hingham MA", "Contact for future races": "yes", "Email": "bushelling@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Talley", "Gender": "", "Age": "4", "Telephone": "548-9981", "ID": "", "First name": "Reginald", "Address": "119 Leverages St. Hingham MA", "Contact for future races": "yes", "Email": "charles@gmail.com"}, {"How did you hear about race": "school", "Last name": "Wilkerson", "Gender": "female", "Age": "76", "Telephone": "199-9362", "ID": "", "First name": "Clarice", "Address": "662 Touchdown St. Hingham MA", "Contact for future races": "no", "Email": "clifford@hotmail.com"}, {"How did you hear about race": "", "Last name": "Glover", "Gender": "male", "Age": "58", "Telephone": "232-2703", "ID": "", "First name": "Juan", "Address": "272 Tips St. Norwell MA", "Contact for future races": "yes", "Email": "seismically@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Diaz", "Gender": "male", "Age": "29", "Telephone": "262-4260", "ID": "", "First name": "Salvador", "Address": "269 Ganglions St. Hingham MA", "Contact for future races": "yes", "Email": "poled@gmail.com"}, {"How did you hear about race": "church", "Last name": "Lyons", "Gender": "female", "Age": "74", "Telephone": "395-2873", "ID": "", "First name": "Lisa", "Address": "227 Luisas St. Quincy MA", "Contact for future races": "yes", "Email": "distinguish@gmail.com"}, {"How did you hear about race": "", "Last name": "Dale", "Gender": "female", "Age": "60", "Telephone": "679-1300", "ID": "", "First name": "Rosalinda", "Address": "391 Malignancys St. Norwell MA", "Contact for future races": "no", "Email": "acquisitiveness@hotmail.com"}, {"How did you hear about race": "", "Last name": "Nichols", "Gender": "male", "Age": "72", "Telephone": "589-5931", "ID": "", "First name": "Jose", "Address": "938 Tools St. Quincy MA", "Contact for future races": "yes", "Email": "durations@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Blackburn", "Gender": "female", "Age": "91", "Telephone": "974-1484", "ID": "", "First name": "Dollie", "Address": "171 Lucres Pkwy. Weymouth MA", "Contact for future races": "no", "Email": "bait@gmail.com"}, {"How did you hear about race": "", "Last name": "Melton", "Gender": "", "Age": "31", "Telephone": "904-3058", "ID": "", "First name": "Patrick", "Address": "982 Ungratefulness Ln. Hingham MA", "Contact for future races": "yes", "Email": "franck@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Little", "Gender": "female", "Age": "24", "Telephone": "", "ID": "", "First name": "Faith", "Address": "956 Congolese Dr. Quincy MA", "Contact for future races": "yes", "Email": "palmettos@gmail.com"}, {"How did you hear about race": "a friend", "Last name": "Boyer", "Gender": "", "Age": "49", "Telephone": "970-2041", "ID": "", "First name": "Kari", "Address": "409 Turbans Dr. Hingham MA", "Contact for future races": "no", "Email": "bloggers@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Phelps", "Gender": "male", "Age": "33", "Telephone": "719-4003", "ID": "", "First name": "Stephen", "Address": "509 Goldwater Dr. Hingham MA", "Contact for future races": "yes", "Email": "senates@gmail.com"}, {"How did you hear about race": "", "Last name": "Bonner", "Gender": "female", "Age": "84", "Telephone": "455-7247", "ID": "", "First name": "Vera", "Address": "740 Aims Pkwy. Weymouth MA", "Contact for future races": "yes", "Email": "ducats@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Mcguire", "Gender": "male", "Age": "86", "Telephone": "230-8947", "ID": "", "First name": "Perry", "Address": "700 Jenningss Way Braintree MA", "Contact for future races": "no", "Email": "mott@gmail.com"}, {"How did you hear about race": "school", "Last name": "Cohen", "Gender": "female", "Age": "55", "Telephone": "162-1863", "ID": "", "First name": "Effie", "Address": "917 Butterfingers St. Hanover MA", "Contact for future races": "yes", "Email": "arenas@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Jennings", "Gender": "", "Age": "88", "Telephone": "593-4182", "ID": "", "First name": "Bob", "Address": "926 Consanguinity Ln. Quincy MA", "Contact for future races": "no", "Email": "stick@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Walter", "Gender": "female", "Age": "40", "Telephone": "338-3275", "ID": "", "First name": "Francine", "Address": "", "Contact for future races": "no", "Email": "murderous@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Olson", "Gender": "female", "Age": "99", "Telephone": "261-3477", "ID": "", "First name": "Terri", "Address": "4 Dragon Ln. Norwell MA", "Contact for future races": "yes", "Email": "homeopathic@hotmail.com"}, {"How did you hear about race": "", "Last name": "Davidson", "Gender": "male", "Age": "57", "Telephone": "273-8545", "ID": "", "First name": "Alvin", "Address": "203 Meanings Pkwy. Hanover MA", "Contact for future races": "yes", "Email": "undermines@hotmail.com"}, {"How did you hear about race": "", "Last name": "Glenn", "Gender": "male", "Age": "26", "Telephone": "704-4633", "ID": "", "First name": "Manuel", "Address": "311 Maternitys St. Hingham MA", "Contact for future races": "no", "Email": "fastenings@gmail.com"}, {"How did you hear about race": "school", "Last name": "Roman", "Gender": "male", "Age": "56", "Telephone": "969-2187", "ID": "", "First name": "Ian", "Address": "213 Breezing Ln. Braintree MA", "Contact for future races": "yes", "Email": "ivs@hotmail.com"}, {"How did you hear about race": "church", "Last name": "Diaz", "Gender": "male", "Age": "58", "Telephone": "199-8342", "ID": "", "First name": "Troy", "Address": "980 Outbalances Dr. Hingham MA", "Contact for future races": "yes", "Email": "ganders@hotmail.com"}, {"How did you hear about race": "", "Last name": "Pennington", "Gender": "female", "Age": "9", "Telephone": "906-6099", "ID": "", "First name": "Connie", "Address": "422 Paddling Dr. Norwell MA", "Contact for future races": "yes", "Email": "ordures@hotmail.com"}, {"How did you hear about race": "came last year", "Last name": "Henderson", "Gender": "female", "Age": "9", "Telephone": "830-7655", "ID": "", "First name": "Celina", "Address": "648 Udall St. Braintree MA", "Contact for future races": "yes", "Email": "iphone@gmail.com"}, {"How did you hear about race": "church", "Last name": "Wiggins", "Gender": "female", "Age": "57", "Telephone": "269-4855", "ID": "", "First name": "Jewel", "Address": "596 Dressmakers Dr. Braintree MA", "Contact for future races": "no", "Email": "crustiest@gmail.com"}, {"How did you hear about race": "", "Last name": "Garrison", "Gender": "male", "Age": "20", "Telephone": "110-9167", "ID": "", "First name": "Richard", "Address": "153 Showdowns Way Quincy MA", "Contact for future races": "yes", "Email": "preordains@hotmail.com"}, {"How did you hear about race": "", "Last name": "Mann", "Gender": "female", "Age": "64", "Telephone": "329-2127", "ID": "", "First name": "Lorrie", "Address": "788 Phalanges Pkwy. Norwell MA", "Contact for future races": "yes", "Email": "woodlands@hotmail.com"}, {"How did you hear about race": "a friend", "Last name": "Weiss", "Gender": "male", "Age": "69", "Telephone": "261-3073", "ID": "", "First name": "Andre", "Address": "108 Modems Dr. Quincy MA", "Contact for future races": "no", "Email": "crabbier@gmail.com"}, {"How did you hear about race": "church", "Last name": "Weber", "Gender": "male", "Age": "73", "Telephone": "298-6913", "ID": "", "First name": "Bob", "Address": "936 Loudspeakers Dr. Weymouth MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "", "Last name": "Burnett", "Gender": "male", "Age": "78", "Telephone": "419-9489", "ID": "", "First name": "Everett", "Address": "113 Baritone Pkwy. Norwell MA", "Contact for future races": "", "Email": "sorbonnes@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Thomas", "Gender": "female", "Age": "2", "Telephone": "198-2210", "ID": "", "First name": "Claudia", "Address": "539 Servicewomans St. Norwell MA", "Contact for future races": "yes", "Email": "variegating@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Sampson", "Gender": "male", "Age": "49", "Telephone": "151-3127", "ID": "", "First name": "Richard", "Address": "189 Components Dr. Quincy MA", "Contact for future races": "yes", "Email": "bonnier@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Cash", "Gender": "female", "Age": "12", "Telephone": "937-6554", "ID": "", "First name": "Muriel", "Address": "", "Contact for future races": "yes", "Email": "p\u00e9tain@gmail.com"}, {"How did you hear about race": "", "Last name": "Meyer", "Gender": "male", "Age": "95", "Telephone": "228-6896", "ID": "", "First name": "Clyde", "Address": "587 Obstructivenesss St. Hanover MA", "Contact for future races": "yes", "Email": "emulator@gmail.com"}, {"How did you hear about race": "", "Last name": "Vargas", "Gender": "female", "Age": "69", "Telephone": "588-9331", "ID": "", "First name": "Lauri", "Address": "495 Bereavements Ln. Norwell MA", "Contact for future races": "yes", "Email": "finenesss@hotmail.com"}, {"How did you hear about race": "", "Last name": "Neal", "Gender": "male", "Age": "65", "Telephone": "204-4341", "ID": "", "First name": "Byron", "Address": "735 Quadriphonic St. Hingham MA", "Contact for future races": "yes", "Email": "rulings@gmail.com"}, {"How did you hear about race": "", "Last name": "Carson", "Gender": "female", "Age": "29", "Telephone": "111-2303", "ID": "", "First name": "Vonda", "Address": "330 Splats Pkwy. Hingham MA", "Contact for future races": "no", "Email": "midgets@gmail.com"}, {"How did you hear about race": "church", "Last name": "Hobbs", "Gender": "male", "Age": "5", "Telephone": "133-1031", "ID": "", "First name": "Joel", "Address": "501 Drams St. Hanover MA", "Contact for future races": "no", "Email": "psychs@hotmail.com"}, {"How did you hear about race": "", "Last name": "Bauer", "Gender": "female", "Age": "81", "Telephone": "350-8358", "ID": "", "First name": "Kay", "Address": "305 Contamination Way Quincy MA", "Contact for future races": "yes", "Email": ""}, {"How did you hear about race": "a friend", "Last name": "Gomez", "Gender": "female", "Age": "85", "Telephone": "937-4041", "ID": "", "First name": "Rosemary", "Address": "243 Festooning Ln. Braintree MA", "Contact for future races": "yes", "Email": "compacts@gmail.com"}, {"How did you hear about race": "came last year", "Last name": "Byrd", "Gender": "female", "Age": "9", "Telephone": "498-7019", "ID": "", "First name": "Leann", "Address": "142 Crisscrosses St. Hanover MA", "Contact for future races": "no", "Email": "importantly@hotmail.com"}, {"How did you hear about race": "school", "Last name": "Fleming", "Gender": "male", "Age": "47", "Telephone": "434-8070", "ID": "", "First name": "Jorge", "Address": "344 Ladyship Ln. Weymouth MA", "Contact for future races": "no", "Email": "pittances@hotmail.com"}, {"How did you hear about race": "", "Last name": "Johns", "Gender": "female", "Age": "76", "Telephone": "624-1544", "ID": "", "First name": "", "Address": "52 Fastness St. Norwell MA", "Contact for future races": "yes", "Email": "stenches@gmail.com"}, {"How did you hear about race": "school", "Last name": "Carr", "Gender": "female", "Age": "41", "Telephone": "372-8099", "ID": "", "First name": "Edwina", "Address": "546 Jealously Dr. Norwell MA", "Contact for future races": "no", "Email": "hustingss@gmail.com"}] \ No newline at end of file +[{"First name": "Kelsey", "Age": "75", "Gender": "female", "Last name": "Baker", "ID": "", "Email": "whooshes@hotmail.com"}, {"First name": "Kent", "Age": "81", "Gender": "male", "Last name": "Talley", "ID": "", "Email": "repository@gmail.com"}, {"First name": "Tiffany", "Age": "32", "Gender": "female", "Last name": "Parker", "ID": "", "Email": "wholesalers@gmail.com"}, {"First name": "Matthew", "Age": "16", "Gender": "male", "Last name": "Bender", "ID": "", "Email": "parmesans@hotmail.com"}, {"First name": "Mathew", "Age": "85", "Gender": "male", "Last name": "Richmond", "ID": "", "Email": "superintending@hotmail.com"}, {"First name": "Opal", "Age": "20", "Gender": "female", "Last name": "Harper", "ID": "", "Email": "recursively@hotmail.com"}, {"First name": "Jeremy", "Age": "42", "Gender": "male", "Last name": "Long", "ID": "", "Email": ""}, {"First name": "Marta", "Age": "82", "Gender": "female", "Last name": "Gillespie", "ID": "", "Email": "musicians@hotmail.com"}, {"First name": "Beatriz", "Age": "56", "Gender": "female", "Last name": "Cooper", "ID": "", "Email": "reapply@gmail.com"}, {"First name": "", "Age": "27", "Gender": "male", "Last name": "Vincent", "ID": "", "Email": "veterinaries@gmail.com"}, {"First name": "Sidney", "Age": "47", "Gender": "male", "Last name": "Dodson", "ID": "", "Email": "poised@gmail.com"}, {"First name": "Beulah", "Age": "3", "Gender": "female", "Last name": "Deleon", "ID": "", "Email": "crick@gmail.com"}, {"First name": "Felix", "Age": "62", "Gender": "male", "Last name": "Mueller", "ID": "", "Email": "dicker@gmail.com"}, {"First name": "Brian", "Age": "86", "Gender": "male", "Last name": "Mendez", "ID": "", "Email": "remoteness@hotmail.com"}, {"First name": "Jeannette", "Age": "24", "Gender": "female", "Last name": "Mccullough", "ID": "", "Email": "samovars@gmail.com"}, {"First name": "Milton", "Age": "27", "Gender": "male", "Last name": "Gilbert", "ID": "", "Email": "sleek@gmail.com"}, {"First name": "Abby", "Age": "49", "Gender": "female", "Last name": "Ellison", "ID": "", "Email": "maos@hotmail.com"}, {"First name": "Lynn", "Age": "96", "Gender": "female", "Last name": "Kelly", "ID": "", "Email": "footlockers@gmail.com"}, {"First name": "Daryl", "Age": "88", "Gender": "male", "Last name": "Herring", "ID": "", "Email": "loosenesss@gmail.com"}, {"First name": "Kelly", "Age": "76", "Gender": "male", "Last name": "Holt", "ID": "", "Email": "hogsheads@hotmail.com"}, {"First name": "Chester", "Age": "20", "Gender": "male", "Last name": "Le", "ID": "", "Email": "moonshots@gmail.com"}, {"First name": "Delia", "Age": "15", "Gender": "female", "Last name": "Stuart", "ID": "", "Email": "stalemates@gmail.com"}, {"First name": "Herman", "Age": "73", "Gender": "male", "Last name": "Watson", "ID": "", "Email": "soothsayers@hotmail.com"}, {"First name": "Daniel", "Age": "75", "Gender": "male", "Last name": "Farley", "ID": "", "Email": "heisted@hotmail.com"}, {"First name": "Douglas", "Age": "65", "Gender": "male", "Last name": "Mitchell", "ID": "", "Email": "confessions@hotmail.com"}, {"First name": "Gwendolyn", "Age": "74", "Gender": "female", "Last name": "Long", "ID": "", "Email": "overseers@hotmail.com"}, {"First name": "Sondra", "Age": "46", "Gender": "female", "Last name": "Compton", "ID": "", "Email": "enigma@hotmail.com"}, {"First name": "Gwen", "Age": "12", "Gender": "female", "Last name": "Chang", "ID": "", "Email": "acquirable@hotmail.com"}, {"First name": "Dwayne", "Age": "82", "Gender": "male", "Last name": "Stevenson", "ID": "", "Email": "microfiche@hotmail.com"}, {"First name": "Nicholas", "Age": "22", "Gender": "male", "Last name": "Dyer", "ID": "", "Email": "ingrates@hotmail.com"}, {"First name": "Lawrence", "Age": "53", "Gender": "male", "Last name": "Schroeder", "ID": "", "Email": "grinders@gmail.com"}, {"First name": "Edward", "Age": "90", "Gender": "male", "Last name": "Gibbs", "ID": "", "Email": "indictment@hotmail.com"}, {"First name": "Juan", "Age": "57", "Gender": "male", "Last name": "Johns", "ID": "", "Email": ""}, {"First name": "Olga", "Age": "49", "Gender": "female", "Last name": "Mcconnell", "ID": "", "Email": ""}, {"First name": "Freddie", "Age": "0", "Gender": "male", "Last name": "Clark", "ID": "", "Email": "obduracys@hotmail.com"}, {"First name": "Ronda", "Age": "87", "Gender": "female", "Last name": "Knapp", "ID": "", "Email": "bizet@gmail.com"}, {"First name": "Edwina", "Age": "98", "Gender": "female", "Last name": "Middleton", "ID": "", "Email": "donated@gmail.com"}, {"First name": "Beverly", "Age": "42", "Gender": "female", "Last name": "Bowen", "ID": "", "Email": ""}, {"First name": "Anna", "Age": "76", "Gender": "female", "Last name": "Bentley", "ID": "", "Email": "handbags@gmail.com"}, {"First name": "Mamie", "Age": "71", "Gender": "female", "Last name": "Hunt", "ID": "", "Email": "phonying@hotmail.com"}, {"First name": "Nannie", "Age": "37", "Gender": "female", "Last name": "Benjamin", "ID": "", "Email": "synced@gmail.com"}, {"First name": "Dianne", "Age": "14", "Gender": "female", "Last name": "", "ID": "", "Email": "replies@hotmail.com"}, {"First name": "Greta", "Age": "43", "Gender": "female", "Last name": "Gill", "ID": "", "Email": "cheetos@hotmail.com"}, {"First name": "Elmer", "Age": "27", "Gender": "male", "Last name": "Ingram", "ID": "", "Email": "hearses@gmail.com"}, {"First name": "Marisa", "Age": "54", "Gender": "female", "Last name": "Boyer", "ID": "", "Email": "trail@hotmail.com"}, {"First name": "Sidney", "Age": "90", "Gender": "male", "Last name": "Nicholson", "ID": "", "Email": "handy@gmail.com"}, {"First name": "Elise", "Age": "27", "Gender": "female", "Last name": "Chase", "ID": "", "Email": "oswald@gmail.com"}, {"First name": "", "Age": "86", "Gender": "male", "Last name": "Huber", "ID": "", "Email": "foundering@hotmail.com"}, {"First name": "Willie", "Age": "60", "Gender": "female", "Last name": "Hansen", "ID": "", "Email": "reebok@hotmail.com"}, {"First name": "Henry", "Age": "45", "Gender": "male", "Last name": "Pruitt", "ID": "", "Email": "compressors@hotmail.com"}, {"First name": "Alexandra", "Age": "66", "Gender": "female", "Last name": "Leblanc", "ID": "", "Email": "grimnesss@gmail.com"}, {"First name": "Mark", "Age": "9", "Gender": "male", "Last name": "Savage", "ID": "", "Email": ""}, {"First name": "Darryl", "Age": "71", "Gender": "male", "Last name": "Tate", "ID": "", "Email": "thundercloud@gmail.com"}, {"First name": "Lawrence", "Age": "72", "Gender": "male", "Last name": "Randall", "ID": "", "Email": "prefers@gmail.com"}, {"First name": "Tommie", "Age": "3", "Gender": "female", "Last name": "Green", "ID": "", "Email": "mutineers@gmail.com"}, {"First name": "Sasha", "Age": "13", "Gender": "female", "Last name": "Guerra", "ID": "", "Email": "dills@gmail.com"}, {"First name": "Julio", "Age": "73", "Gender": "male", "Last name": "Mason", "ID": "", "Email": "overlooking@hotmail.com"}, {"First name": "Raymond", "Age": "11", "Gender": "male", "Last name": "Anderson", "ID": "", "Email": "payments@hotmail.com"}, {"First name": "Megan", "Age": "94", "Gender": "female", "Last name": "Duncan", "ID": "", "Email": "venally@hotmail.com"}, {"First name": "Hattie", "Age": "55", "Gender": "female", "Last name": "England", "ID": "", "Email": "noreen@gmail.com"}, {"First name": "", "Age": "60", "Gender": "", "Last name": "Avila", "ID": "", "Email": "tightwad@hotmail.com"}, {"First name": "Ryan", "Age": "3", "Gender": "male", "Last name": "Mendez", "ID": "", "Email": "skinheads@hotmail.com"}, {"First name": "Leon", "Age": "4", "Gender": "male", "Last name": "Harris", "ID": "", "Email": "fruitfulnesss@hotmail.com"}, {"First name": "Anastasia", "Age": "29", "Gender": "female", "Last name": "Sellers", "ID": "", "Email": "sandalwood@gmail.com"}, {"First name": "Jamie", "Age": "87", "Gender": "male", "Last name": "Potts", "ID": "", "Email": "clinicians@gmail.com"}, {"First name": "Terrance", "Age": "73", "Gender": "male", "Last name": "Turner", "ID": "", "Email": "fitzpatrick@hotmail.com"}, {"First name": "Gretchen", "Age": "12", "Gender": "female", "Last name": "Bradley", "ID": "", "Email": "morocco@hotmail.com"}, {"First name": "Sherri", "Age": "51", "Gender": "female", "Last name": "Ortiz", "ID": "", "Email": "stark@hotmail.com"}, {"First name": "Flossie", "Age": "62", "Gender": "female", "Last name": "Harding", "ID": "", "Email": "quintuples@gmail.com"}, {"First name": "Wayne", "Age": "77", "Gender": "male", "Last name": "Britt", "ID": "", "Email": "manicures@hotmail.com"}, {"First name": "Mario", "Age": "18", "Gender": "male", "Last name": "Kim", "ID": "", "Email": "probables@gmail.com"}, {"First name": "Latisha", "Age": "48", "Gender": "female", "Last name": "Conway", "ID": "", "Email": "sequins@gmail.com"}, {"First name": "", "Age": "44", "Gender": "female", "Last name": "Mullins", "ID": "", "Email": "undemanding@hotmail.com"}, {"First name": "Jay", "Age": "40", "Gender": "male", "Last name": "Crane", "ID": "", "Email": "sours@gmail.com"}, {"First name": "Ernest", "Age": "56", "Gender": "male", "Last name": "Waters", "ID": "", "Email": "heeps@gmail.com"}, {"First name": "Louisa", "Age": "42", "Gender": "female", "Last name": "Mcneil", "ID": "", "Email": "thrilled@hotmail.com"}, {"First name": "Jeremy", "Age": "34", "Gender": "male", "Last name": "Moody", "ID": "", "Email": "admittance@gmail.com"}, {"First name": "Bertie", "Age": "61", "Gender": "female", "Last name": "Owen", "ID": "", "Email": "pliability@gmail.com"}, {"First name": "Rhonda", "Age": "35", "Gender": "female", "Last name": "Bray", "ID": "", "Email": "remunerations@gmail.com"}, {"First name": "Mollie", "Age": "98", "Gender": "female", "Last name": "Poole", "ID": "", "Email": "sheratons@gmail.com"}, {"First name": "Lara", "Age": "68", "Gender": "female", "Last name": "Lucas", "ID": "", "Email": "emusic@gmail.com"}, {"First name": "Coleen", "Age": "83", "Gender": "female", "Last name": "Perez", "ID": "", "Email": "kristies@hotmail.com"}, {"First name": "Pearlie", "Age": "87", "Gender": "female", "Last name": "Fleming", "ID": "", "Email": "queens@hotmail.com"}, {"First name": "Chad", "Age": "81", "Gender": "male", "Last name": "Vega", "ID": "", "Email": "accretion@gmail.com"}, {"First name": "Beatrice", "Age": "80", "Gender": "female", "Last name": "Landry", "ID": "", "Email": "conceptualizations@hotmail.com"}, {"First name": "Twila", "Age": "58", "Gender": "female", "Last name": "Glover", "ID": "", "Email": "gait@gmail.com"}, {"First name": "Leroy", "Age": "41", "Gender": "male", "Last name": "Johnson", "ID": "", "Email": "shush@gmail.com"}, {"First name": "Mildred", "Age": "85", "Gender": "female", "Last name": "Crawford", "ID": "", "Email": "widowers@hotmail.com"}, {"First name": "", "Age": "10", "Gender": "female", "Last name": "Roman", "ID": "", "Email": "overrunning@hotmail.com"}, {"First name": "Alfred", "Age": "9", "Gender": "male", "Last name": "Zimmerman", "ID": "", "Email": "libeling@hotmail.com"}, {"First name": "Janet", "Age": "66", "Gender": "female", "Last name": "Hardin", "ID": "", "Email": "nona@gmail.com"}, {"First name": "Stacy", "Age": "52", "Gender": "female", "Last name": "Slater", "ID": "", "Email": "staler@hotmail.com"}, {"First name": "Arnold", "Age": "20", "Gender": "male", "Last name": "Simon", "ID": "", "Email": ""}, {"First name": "Guadalupe", "Age": "77", "Gender": "female", "Last name": "Wilkinson", "ID": "", "Email": "festivitys@hotmail.com"}, {"First name": "Steve", "Age": "60", "Gender": "male", "Last name": "Guthrie", "ID": "", "Email": "bumbled@gmail.com"}, {"First name": "Norman", "Age": "21", "Gender": "male", "Last name": "Holman", "ID": "", "Email": "remounts@hotmail.com"}, {"First name": "Earlene", "Age": "31", "Gender": "female", "Last name": "Lee", "ID": "", "Email": "fathead@hotmail.com"}, {"First name": "Jason", "Age": "73", "Gender": "male", "Last name": "Albert", "ID": "", "Email": "affinitys@hotmail.com"}, {"First name": "", "Age": "73", "Gender": "female", "Last name": "Marks", "ID": "", "Email": "misogynistic@hotmail.com"}, {"First name": "Marshall", "Age": "25", "Gender": "male", "Last name": "Maddox", "ID": "", "Email": "exchange@hotmail.com"}, {"First name": "Earl", "Age": "94", "Gender": "male", "Last name": "Patterson", "ID": "", "Email": "yvettes@gmail.com"}, {"First name": "Tyrone", "Age": "40", "Gender": "male", "Last name": "Velez", "ID": "", "Email": "returned@hotmail.com"}, {"First name": "Howard", "Age": "85", "Gender": "male", "Last name": "Koch", "ID": "", "Email": "renaming@gmail.com"}, {"First name": "Rick", "Age": "47", "Gender": "male", "Last name": "Hinton", "ID": "", "Email": "mess@gmail.com"}, {"First name": "Jordan", "Age": "57", "Gender": "male", "Last name": "Booth", "ID": "", "Email": "paulette@hotmail.com"}, {"First name": "Adam", "Age": "21", "Gender": "male", "Last name": "Foreman", "ID": "", "Email": "allergists@gmail.com"}, {"First name": "Elena", "Age": "91", "Gender": "female", "Last name": "Harrison", "ID": "", "Email": "mesmerisms@hotmail.com"}, {"First name": "Floyd", "Age": "0", "Gender": "male", "Last name": "Mcconnell", "ID": "", "Email": "havens@gmail.com"}, {"First name": "Rhoda", "Age": "57", "Gender": "female", "Last name": "Haley", "ID": "", "Email": "bossier@hotmail.com"}, {"First name": "Gene", "Age": "13", "Gender": "male", "Last name": "Gomez", "ID": "", "Email": "leavening@gmail.com"}, {"First name": "Jan", "Age": "87", "Gender": "female", "Last name": "Garner", "ID": "", "Email": "goaltender@gmail.com"}, {"First name": "Darryl", "Age": "93", "Gender": "male", "Last name": "English", "ID": "", "Email": "neckerchiefs@hotmail.com"}, {"First name": "Marion", "Age": "31", "Gender": "female", "Last name": "Wheeler", "ID": "", "Email": "disguising@hotmail.com"}, {"First name": "Eric", "Age": "36", "Gender": "male", "Last name": "Goff", "ID": "", "Email": "speeder@gmail.com"}, {"First name": "George", "Age": "20", "Gender": "male", "Last name": "Fox", "ID": "", "Email": "rovers@gmail.com"}, {"First name": "John", "Age": "79", "Gender": "male", "Last name": "Reese", "ID": "", "Email": "easts@gmail.com"}, {"First name": "Violet", "Age": "96", "Gender": "female", "Last name": "Pruitt", "ID": "", "Email": "grumpiness@hotmail.com"}, {"First name": "Eugenia", "Age": "60", "Gender": "female", "Last name": "Bean", "ID": "", "Email": "shoo@gmail.com"}, {"First name": "Alfredo", "Age": "39", "Gender": "male", "Last name": "Kent", "ID": "", "Email": "esqs@hotmail.com"}, {"First name": "", "Age": "96", "Gender": "male", "Last name": "Ware", "ID": "", "Email": "referential@hotmail.com"}, {"First name": "Timothy", "Age": "18", "Gender": "male", "Last name": "Stevenson", "ID": "", "Email": "humbly@hotmail.com"}, {"First name": "Lesley", "Age": "24", "Gender": "female", "Last name": "Glass", "ID": "", "Email": "hampering@hotmail.com"}, {"First name": "Gabriel", "Age": "95", "Gender": "", "Last name": "Singleton", "ID": "", "Email": "nws@hotmail.com"}, {"First name": "Lily", "Age": "92", "Gender": "female", "Last name": "Roach", "ID": "", "Email": "alleyways@gmail.com"}, {"First name": "", "Age": "11", "Gender": "female", "Last name": "Berger", "ID": "", "Email": "biotechnologys@gmail.com"}, {"First name": "Tessa", "Age": "60", "Gender": "female", "Last name": "Brennan", "ID": "", "Email": "pollutions@hotmail.com"}, {"First name": "Sophie", "Age": "97", "Gender": "female", "Last name": "Rowland", "ID": "", "Email": "communions@hotmail.com"}, {"First name": "Earnestine", "Age": "54", "Gender": "female", "Last name": "Hicks", "ID": "", "Email": "unspoken@gmail.com"}, {"First name": "Ryan", "Age": "25", "Gender": "male", "Last name": "Graham", "ID": "", "Email": "randell@hotmail.com"}, {"First name": "Harold", "Age": "68", "Gender": "male", "Last name": "Turner", "ID": "", "Email": "cara@gmail.com"}, {"First name": "Odessa", "Age": "0", "Gender": "female", "Last name": "Morgan", "ID": "", "Email": "idahos@gmail.com"}, {"First name": "Edna", "Age": "45", "Gender": "female", "Last name": "Madden", "ID": "", "Email": "sprawl@hotmail.com"}, {"First name": "Lee", "Age": "13", "Gender": "male", "Last name": "Valenzuela", "ID": "", "Email": "banged@hotmail.com"}, {"First name": "Rosetta", "Age": "23", "Gender": "female", "Last name": "Dean", "ID": "", "Email": "gibberishs@hotmail.com"}, {"First name": "Seth", "Age": "19", "Gender": "male", "Last name": "Perry", "ID": "", "Email": "plagiarists@hotmail.com"}, {"First name": "Rebecca", "Age": "37", "Gender": "female", "Last name": "Doyle", "ID": "", "Email": "inflammation@gmail.com"}, {"First name": "Marion", "Age": "69", "Gender": "male", "Last name": "Gillespie", "ID": "", "Email": "corrosions@hotmail.com"}, {"First name": "Geraldine", "Age": "0", "Gender": "", "Last name": "Herrera", "ID": "", "Email": "helvetius@gmail.com"}, {"First name": "Miranda", "Age": "82", "Gender": "female", "Last name": "Justice", "ID": "", "Email": "doughy@hotmail.com"}, {"First name": "Edwin", "Age": "48", "Gender": "male", "Last name": "Sandoval", "ID": "", "Email": "docket@gmail.com"}, {"First name": "Johanna", "Age": "28", "Gender": "female", "Last name": "Mcknight", "ID": "", "Email": "overrating@hotmail.com"}, {"First name": "Beatrice", "Age": "34", "Gender": "female", "Last name": "Jordan", "ID": "", "Email": "rod@gmail.com"}, {"First name": "Donna", "Age": "2", "Gender": "female", "Last name": "Ayers", "ID": "", "Email": "specialists@gmail.com"}, {"First name": "", "Age": "84", "Gender": "female", "Last name": "Fields", "ID": "", "Email": "tycoons@gmail.com"}, {"First name": "Warren", "Age": "65", "Gender": "male", "Last name": "Summers", "ID": "", "Email": "terabytes@gmail.com"}, {"First name": "Jewell", "Age": "44", "Gender": "female", "Last name": "Kelley", "ID": "", "Email": "rocknes@gmail.com"}, {"First name": "Laurel", "Age": "3", "Gender": "female", "Last name": "Dean", "ID": "", "Email": "livens@gmail.com"}, {"First name": "Brad", "Age": "45", "Gender": "male", "Last name": "Phillips", "ID": "", "Email": "incubators@gmail.com"}, {"First name": "Juan", "Age": "11", "Gender": "male", "Last name": "Mccarthy", "ID": "", "Email": "gospels@gmail.com"}, {"First name": "Ellen", "Age": "99", "Gender": "female", "Last name": "Warren", "ID": "", "Email": "cockades@gmail.com"}, {"First name": "Peter", "Age": "36", "Gender": "male", "Last name": "Hernandez", "ID": "", "Email": "aurae@hotmail.com"}, {"First name": "Neil", "Age": "4", "Gender": "", "Last name": "Cooley", "ID": "", "Email": "postage@gmail.com"}, {"First name": "Floyd", "Age": "56", "Gender": "male", "Last name": "Mendez", "ID": "", "Email": "translucent@hotmail.com"}, {"First name": "Lacey", "Age": "20", "Gender": "female", "Last name": "Morse", "ID": "", "Email": "cinders@gmail.com"}, {"First name": "Liliana", "Age": "61", "Gender": "female", "Last name": "Anthony", "ID": "", "Email": "venice@hotmail.com"}, {"First name": "Malinda", "Age": "99", "Gender": "female", "Last name": "Walton", "ID": "", "Email": "fulcrum@hotmail.com"}, {"First name": "Beverly", "Age": "49", "Gender": "female", "Last name": "Valentine", "ID": "", "Email": "bruisers@gmail.com"}, {"First name": "Ilene", "Age": "92", "Gender": "female", "Last name": "Kelly", "ID": "", "Email": "celebrity@hotmail.com"}, {"First name": "Cristina", "Age": "36", "Gender": "female", "Last name": "Lester", "ID": "", "Email": "burmeses@hotmail.com"}, {"First name": "Coleen", "Age": "93", "Gender": "female", "Last name": "Vargas", "ID": "", "Email": "semiprofessionals@gmail.com"}, {"First name": "Tony", "Age": "60", "Gender": "male", "Last name": "Wyatt", "ID": "", "Email": "falsehoods@gmail.com"}, {"First name": "Ella", "Age": "47", "Gender": "female", "Last name": "Hood", "ID": "", "Email": "toiled@gmail.com"}, {"First name": "Mayra", "Age": "90", "Gender": "female", "Last name": "Pratt", "ID": "", "Email": "priestliest@gmail.com"}, {"First name": "Rhonda", "Age": "73", "Gender": "", "Last name": "Burgess", "ID": "", "Email": "whose@gmail.com"}, {"First name": "Freddie", "Age": "40", "Gender": "male", "Last name": "Haney", "ID": "", "Email": ""}, {"First name": "Flossie", "Age": "82", "Gender": "female", "Last name": "Callahan", "ID": "", "Email": "ricotta@gmail.com"}, {"First name": "Cory", "Age": "80", "Gender": "male", "Last name": "Mcintosh", "ID": "", "Email": "rumbled@hotmail.com"}, {"First name": "", "Age": "33", "Gender": "female", "Last name": "Leach", "ID": "", "Email": "luminarys@gmail.com"}, {"First name": "Andy", "Age": "81", "Gender": "male", "Last name": "Gonzalez", "ID": "", "Email": "amalgam@gmail.com"}, {"First name": "Lily", "Age": "67", "Gender": "female", "Last name": "Valentine", "ID": "", "Email": "manfully@gmail.com"}, {"First name": "Casey", "Age": "83", "Gender": "female", "Last name": "Barker", "ID": "", "Email": "pelvic@hotmail.com"}, {"First name": "Benjamin", "Age": "61", "Gender": "male", "Last name": "Eaton", "ID": "", "Email": "mazarin@gmail.com"}, {"First name": "Beth", "Age": "26", "Gender": "female", "Last name": "Marquez", "ID": "", "Email": "dooms@hotmail.com"}, {"First name": "Eula", "Age": "35", "Gender": "female", "Last name": "Clemons", "ID": "", "Email": ""}, {"First name": "Charlie", "Age": "84", "Gender": "male", "Last name": "Berg", "ID": "", "Email": "unsubstantial@gmail.com"}, {"First name": "Hester", "Age": "17", "Gender": "female", "Last name": "Mcknight", "ID": "", "Email": "maples@gmail.com"}, {"First name": "Clifford", "Age": "91", "Gender": "male", "Last name": "Obrien", "ID": "", "Email": "familiarity@gmail.com"}, {"First name": "Adela", "Age": "2", "Gender": "female", "Last name": "Trevino", "ID": "", "Email": "economical@gmail.com"}, {"First name": "Rafael", "Age": "22", "Gender": "male", "Last name": "Levine", "ID": "", "Email": "inez@hotmail.com"}, {"First name": "Tommy", "Age": "57", "Gender": "male", "Last name": "Soto", "ID": "", "Email": "moseley@gmail.com"}, {"First name": "Lucia", "Age": "79", "Gender": "female", "Last name": "Flowers", "ID": "", "Email": "malls@hotmail.com"}, {"First name": "Wayne", "Age": "98", "Gender": "", "Last name": "Jenkins", "ID": "", "Email": "bloods@gmail.com"}, {"First name": "Kari", "Age": "36", "Gender": "female", "Last name": "Combs", "ID": "", "Email": "silvias@gmail.com"}, {"First name": "Michelle", "Age": "51", "Gender": "female", "Last name": "Fulton", "ID": "", "Email": "terraced@hotmail.com"}, {"First name": "Clarence", "Age": "52", "Gender": "male", "Last name": "Cash", "ID": "", "Email": "inventorys@gmail.com"}, {"First name": "Ricky", "Age": "69", "Gender": "male", "Last name": "Maynard", "ID": "", "Email": "cheeks@hotmail.com"}, {"First name": "", "Age": "35", "Gender": "male", "Last name": "Flores", "ID": "", "Email": "casios@gmail.com"}, {"First name": "Susanne", "Age": "95", "Gender": "female", "Last name": "Christensen", "ID": "", "Email": "aquavits@gmail.com"}, {"First name": "Clifton", "Age": "84", "Gender": "male", "Last name": "Hendrix", "ID": "", "Email": "tricky@hotmail.com"}, {"First name": "Joe", "Age": "32", "Gender": "", "Last name": "Baker", "ID": "", "Email": "luciuss@hotmail.com"}, {"First name": "Angelique", "Age": "35", "Gender": "female", "Last name": "Battle", "ID": "", "Email": "doughnut@hotmail.com"}, {"First name": "Katina", "Age": "27", "Gender": "female", "Last name": "Little", "ID": "", "Email": "stolypin@hotmail.com"}, {"First name": "", "Age": "78", "Gender": "female", "Last name": "Irwin", "ID": "", "Email": "winners@gmail.com"}, {"First name": "", "Age": "17", "Gender": "", "Last name": "Conway", "ID": "", "Email": "steepnesss@gmail.com"}, {"First name": "Bryan", "Age": "81", "Gender": "male", "Last name": "Wall", "ID": "", "Email": "clipping@gmail.com"}, {"First name": "Shelby", "Age": "75", "Gender": "female", "Last name": "Hester", "ID": "", "Email": "austria@hotmail.com"}, {"First name": "Dixie", "Age": "51", "Gender": "female", "Last name": "Scott", "ID": "", "Email": "ramada@gmail.com"}, {"First name": "Roger", "Age": "7", "Gender": "male", "Last name": "Byers", "ID": "", "Email": "shiftiest@gmail.com"}, {"First name": "Brett", "Age": "60", "Gender": "male", "Last name": "", "ID": "", "Email": "routes@gmail.com"}, {"First name": "Wilda", "Age": "47", "Gender": "female", "Last name": "Stevenson", "ID": "", "Email": "hostilely@gmail.com"}, {"First name": "Frederick", "Age": "76", "Gender": "male", "Last name": "Padilla", "ID": "", "Email": "prevailing@gmail.com"}, {"First name": "Bridgett", "Age": "49", "Gender": "female", "Last name": "Cantu", "ID": "", "Email": ""}, {"First name": "Clifford", "Age": "80", "Gender": "male", "Last name": "Sherman", "ID": "", "Email": "defoliants@hotmail.com"}, {"First name": "Edward", "Age": "77", "Gender": "male", "Last name": "Forbes", "ID": "", "Email": "precedent@gmail.com"}, {"First name": "Troy", "Age": "11", "Gender": "", "Last name": "Weber", "ID": "", "Email": "turgenev@hotmail.com"}, {"First name": "Sara", "Age": "45", "Gender": "female", "Last name": "Mccray", "ID": "", "Email": "assorting@gmail.com"}, {"First name": "", "Age": "5", "Gender": "", "Last name": "Mcneil", "ID": "", "Email": "watchmans@gmail.com"}, {"First name": "Haley", "Age": "1", "Gender": "female", "Last name": "Ayers", "ID": "", "Email": "janiss@gmail.com"}, {"First name": "Karl", "Age": "18", "Gender": "male", "Last name": "Roberson", "ID": "", "Email": "roderick@hotmail.com"}, {"First name": "Lorrie", "Age": "13", "Gender": "female", "Last name": "Pena", "ID": "", "Email": "developments@gmail.com"}, {"First name": "Pearl", "Age": "85", "Gender": "female", "Last name": "Tyler", "ID": "", "Email": "interconnection@gmail.com"}, {"First name": "Virgil", "Age": "0", "Gender": "male", "Last name": "Kaufman", "ID": "", "Email": "campaigning@hotmail.com"}, {"First name": "Ethel", "Age": "33", "Gender": "female", "Last name": "Mcguire", "ID": "", "Email": "its@gmail.com"}, {"First name": "Reginald", "Age": "31", "Gender": "male", "Last name": "Herman", "ID": "", "Email": ""}, {"First name": "Donald", "Age": "75", "Gender": "male", "Last name": "Davis", "ID": "", "Email": "fryers@hotmail.com"}, {"First name": "Daisy", "Age": "31", "Gender": "female", "Last name": "Rowland", "ID": "", "Email": "ravaged@gmail.com"}, {"First name": "Mallory", "Age": "4", "Gender": "female", "Last name": "Palmer", "ID": "", "Email": "clemenceau@gmail.com"}, {"First name": "Janette", "Age": "10", "Gender": "female", "Last name": "Hull", "ID": "", "Email": "gentled@hotmail.com"}, {"First name": "Jon", "Age": "46", "Gender": "male", "Last name": "Mcdonald", "ID": "", "Email": "tans@gmail.com"}, {"First name": "Julio", "Age": "50", "Gender": "male", "Last name": "Snow", "ID": "", "Email": "mack@hotmail.com"}, {"First name": "Clinton", "Age": "1", "Gender": "male", "Last name": "Joyce", "ID": "", "Email": "coping@gmail.com"}, {"First name": "Amanda", "Age": "42", "Gender": "female", "Last name": "Bryant", "ID": "", "Email": "juncos@hotmail.com"}, {"First name": "Bernard", "Age": "52", "Gender": "male", "Last name": "Kim", "ID": "", "Email": "sock@hotmail.com"}, {"First name": "Sam", "Age": "18", "Gender": "male", "Last name": "Barber", "ID": "", "Email": "sureties@gmail.com"}, {"First name": "", "Age": "63", "Gender": "female", "Last name": "Hampton", "ID": "", "Email": "distills@gmail.com"}, {"First name": "Abigail", "Age": "41", "Gender": "female", "Last name": "Michael", "ID": "", "Email": "loges@gmail.com"}, {"First name": "Meredith", "Age": "36", "Gender": "female", "Last name": "Lott", "ID": "", "Email": "depressingly@gmail.com"}, {"First name": "Sharlene", "Age": "34", "Gender": "female", "Last name": "Stewart", "ID": "", "Email": "cautious@hotmail.com"}, {"First name": "Adrienne", "Age": "16", "Gender": "female", "Last name": "Evans", "ID": "", "Email": "cellulites@hotmail.com"}, {"First name": "Arline", "Age": "85", "Gender": "female", "Last name": "Sharpe", "ID": "", "Email": "rebuked@hotmail.com"}, {"First name": "Sabrina", "Age": "8", "Gender": "female", "Last name": "Cortez", "ID": "", "Email": "demolition@hotmail.com"}, {"First name": "Marjorie", "Age": "17", "Gender": "female", "Last name": "Fischer", "ID": "", "Email": "treacherously@hotmail.com"}, {"First name": "Lloyd", "Age": "45", "Gender": "male", "Last name": "Vaughan", "ID": "", "Email": "harmoniousnesss@hotmail.com"}, {"First name": "Bernard", "Age": "31", "Gender": "male", "Last name": "Powers", "ID": "", "Email": "deice@hotmail.com"}, {"First name": "", "Age": "78", "Gender": "female", "Last name": "Murray", "ID": "", "Email": "defaults@hotmail.com"}, {"First name": "Rita", "Age": "49", "Gender": "female", "Last name": "Velez", "ID": "", "Email": "sawdust@gmail.com"}, {"First name": "Aaron", "Age": "88", "Gender": "male", "Last name": "Preston", "ID": "", "Email": "narratives@gmail.com"}, {"First name": "Lora", "Age": "49", "Gender": "female", "Last name": "Buck", "ID": "", "Email": "alleluia@gmail.com"}, {"First name": "Lea", "Age": "75", "Gender": "female", "Last name": "Ortiz", "ID": "", "Email": "scroungers@hotmail.com"}, {"First name": "Brandon", "Age": "97", "Gender": "male", "Last name": "Harrell", "ID": "", "Email": "liquidators@hotmail.com"}, {"First name": "Noemi", "Age": "48", "Gender": "female", "Last name": "Miller", "ID": "", "Email": "hows@gmail.com"}, {"First name": "Norma", "Age": "50", "Gender": "female", "Last name": "Valencia", "ID": "", "Email": "illegalities@hotmail.com"}, {"First name": "Elmer", "Age": "3", "Gender": "male", "Last name": "Perry", "ID": "", "Email": "madly@gmail.com"}, {"First name": "", "Age": "44", "Gender": "female", "Last name": "Miller", "ID": "", "Email": ""}, {"First name": "Ted", "Age": "3", "Gender": "male", "Last name": "Weeks", "ID": "", "Email": "reputedly@gmail.com"}, {"First name": "Charlotte", "Age": "71", "Gender": "female", "Last name": "Berry", "ID": "", "Email": "radicalism@gmail.com"}, {"First name": "Natalia", "Age": "59", "Gender": "female", "Last name": "Lott", "ID": "", "Email": "druggists@gmail.com"}, {"First name": "Sabrina", "Age": "7", "Gender": "female", "Last name": "Knapp", "ID": "", "Email": "bridgetts@gmail.com"}, {"First name": "Darryl", "Age": "58", "Gender": "male", "Last name": "", "ID": "", "Email": "asocial@gmail.com"}, {"First name": "Bernard", "Age": "30", "Gender": "male", "Last name": "Arnold", "ID": "", "Email": "enjoins@hotmail.com"}, {"First name": "Rene", "Age": "10", "Gender": "female", "Last name": "Clemons", "ID": "", "Email": "atrocities@gmail.com"}, {"First name": "Savannah", "Age": "85", "Gender": "female", "Last name": "Velazquez", "ID": "", "Email": "kingdoms@gmail.com"}, {"First name": "Karina", "Age": "96", "Gender": "female", "Last name": "Powell", "ID": "", "Email": "munro@hotmail.com"}, {"First name": "Mia", "Age": "18", "Gender": "female", "Last name": "Carlson", "ID": "", "Email": ""}, {"First name": "Lee", "Age": "69", "Gender": "male", "Last name": "Landry", "ID": "", "Email": "recommendation@hotmail.com"}, {"First name": "Howard", "Age": "7", "Gender": "male", "Last name": "Salinas", "ID": "", "Email": "astronauticss@hotmail.com"}, {"First name": "Ted", "Age": "33", "Gender": "male", "Last name": "Finley", "ID": "", "Email": "hangdog@gmail.com"}, {"First name": "Ted", "Age": "28", "Gender": "male", "Last name": "Franks", "ID": "", "Email": "marimbas@hotmail.com"}, {"First name": "Lea", "Age": "5", "Gender": "female", "Last name": "Nash", "ID": "", "Email": "expectorates@gmail.com"}, {"First name": "Rafael", "Age": "16", "Gender": "male", "Last name": "Ellison", "ID": "", "Email": "arrogates@hotmail.com"}, {"First name": "Lucille", "Age": "38", "Gender": "female", "Last name": "Olsen", "ID": "", "Email": "cardozo@gmail.com"}, {"First name": "Isabella", "Age": "86", "Gender": "female", "Last name": "Gill", "ID": "", "Email": "crucible@hotmail.com"}, {"First name": "Ralph", "Age": "20", "Gender": "male", "Last name": "Marshall", "ID": "", "Email": "hebraic@gmail.com"}, {"First name": "Edwin", "Age": "75", "Gender": "male", "Last name": "Villarreal", "ID": "", "Email": "giselle@gmail.com"}, {"First name": "Audra", "Age": "44", "Gender": "female", "Last name": "Meyer", "ID": "", "Email": "becks@hotmail.com"}, {"First name": "Albert", "Age": "73", "Gender": "male", "Last name": "Holcomb", "ID": "", "Email": "pseudonym@hotmail.com"}, {"First name": "Daryl", "Age": "42", "Gender": "male", "Last name": "Durham", "ID": "", "Email": "unethical@gmail.com"}, {"First name": "Sidney", "Age": "21", "Gender": "male", "Last name": "Buckley", "ID": "", "Email": "whitehorses@gmail.com"}, {"First name": "Tanisha", "Age": "6", "Gender": "female", "Last name": "Mclean", "ID": "", "Email": "fanciers@hotmail.com"}, {"First name": "Daniel", "Age": "72", "Gender": "male", "Last name": "Hampton", "ID": "", "Email": "disciples@gmail.com"}, {"First name": "Paulette", "Age": "70", "Gender": "female", "Last name": "Tillman", "ID": "", "Email": "tendencies@gmail.com"}, {"First name": "Craig", "Age": "69", "Gender": "male", "Last name": "Norton", "ID": "", "Email": "provo@gmail.com"}, {"First name": "Carl", "Age": "94", "Gender": "male", "Last name": "Pena", "ID": "", "Email": "aquanauts@hotmail.com"}, {"First name": "Theodore", "Age": "88", "Gender": "male", "Last name": "Chapman", "ID": "", "Email": "outbursts@hotmail.com"}, {"First name": "Rene", "Age": "12", "Gender": "male", "Last name": "Jefferson", "ID": "", "Email": "antedating@gmail.com"}, {"First name": "Joe", "Age": "96", "Gender": "male", "Last name": "Byers", "ID": "", "Email": "inveigles@hotmail.com"}, {"First name": "Jacob", "Age": "41", "Gender": "male", "Last name": "Burch", "ID": "", "Email": "skimpy@hotmail.com"}, {"First name": "Brett", "Age": "72", "Gender": "male", "Last name": "Gill", "ID": "", "Email": "fishing@gmail.com"}, {"First name": "Carlene", "Age": "75", "Gender": "female", "Last name": "Lancaster", "ID": "", "Email": "suetonius@hotmail.com"}, {"First name": "Jeannine", "Age": "97", "Gender": "female", "Last name": "Leon", "ID": "", "Email": ""}, {"First name": "Wesley", "Age": "93", "Gender": "male", "Last name": "Carr", "ID": "", "Email": "thoth@gmail.com"}, {"First name": "Jo", "Age": "98", "Gender": "female", "Last name": "Chapman", "ID": "", "Email": "chaperons@hotmail.com"}, {"First name": "Francis", "Age": "91", "Gender": "male", "Last name": "Carroll", "ID": "", "Email": "gs@hotmail.com"}, {"First name": "Darryl", "Age": "26", "Gender": "male", "Last name": "Garner", "ID": "", "Email": "diffidences@hotmail.com"}, {"First name": "Alexander", "Age": "61", "Gender": "male", "Last name": "Tate", "ID": "", "Email": ""}, {"First name": "Salvador", "Age": "10", "Gender": "male", "Last name": "Cobb", "ID": "", "Email": "rosella@hotmail.com"}, {"First name": "Jack", "Age": "44", "Gender": "male", "Last name": "Rivera", "ID": "", "Email": "mackinac@hotmail.com"}, {"First name": "Gordon", "Age": "39", "Gender": "", "Last name": "Wall", "ID": "", "Email": "align@hotmail.com"}, {"First name": "Grace", "Age": "75", "Gender": "female", "Last name": "Trujillo", "ID": "", "Email": "perm@gmail.com"}, {"First name": "Jacqueline", "Age": "35", "Gender": "female", "Last name": "Mckinney", "ID": "", "Email": "deck@gmail.com"}, {"First name": "Bradley", "Age": "26", "Gender": "male", "Last name": "Ferguson", "ID": "", "Email": "proficiency@hotmail.com"}, {"First name": "Selma", "Age": "14", "Gender": "female", "Last name": "Gregory", "ID": "", "Email": "coifs@gmail.com"}, {"First name": "Duane", "Age": "89", "Gender": "male", "Last name": "Dominguez", "ID": "", "Email": "besoms@hotmail.com"}, {"First name": "Floyd", "Age": "73", "Gender": "male", "Last name": "Lester", "ID": "", "Email": "iciest@hotmail.com"}, {"First name": "Lina", "Age": "62", "Gender": "female", "Last name": "Aguilar", "ID": "", "Email": "sluggishly@gmail.com"}, {"First name": "Vilma", "Age": "80", "Gender": "female", "Last name": "Bates", "ID": "", "Email": "boys@gmail.com"}, {"First name": "Ted", "Age": "38", "Gender": "male", "Last name": "Barrera", "ID": "", "Email": "inexpedient@hotmail.com"}, {"First name": "Jacob", "Age": "39", "Gender": "male", "Last name": "Turner", "ID": "", "Email": "burrows@gmail.com"}, {"First name": "Francisco", "Age": "49", "Gender": "male", "Last name": "Saunders", "ID": "", "Email": "brashly@hotmail.com"}, {"First name": "Sheri", "Age": "81", "Gender": "female", "Last name": "Norton", "ID": "", "Email": "mantelpieces@hotmail.com"}, {"First name": "Brett", "Age": "6", "Gender": "", "Last name": "", "ID": "", "Email": "roadshow@gmail.com"}, {"First name": "Allan", "Age": "76", "Gender": "male", "Last name": "Dunn", "ID": "", "Email": ""}, {"First name": "Joseph", "Age": "53", "Gender": "", "Last name": "Hester", "ID": "", "Email": "amman@hotmail.com"}, {"First name": "Brittany", "Age": "73", "Gender": "female", "Last name": "Frazier", "ID": "", "Email": "hibernated@hotmail.com"}, {"First name": "Leanna", "Age": "93", "Gender": "female", "Last name": "Hensley", "ID": "", "Email": "albumens@gmail.com"}, {"First name": "Lolita", "Age": "81", "Gender": "female", "Last name": "Waters", "ID": "", "Email": "suppuration@hotmail.com"}, {"First name": "Chad", "Age": "26", "Gender": "male", "Last name": "Ashley", "ID": "", "Email": "malory@gmail.com"}, {"First name": "Nita", "Age": "67", "Gender": "female", "Last name": "Franco", "ID": "", "Email": "unlatch@gmail.com"}, {"First name": "Ivan", "Age": "34", "Gender": "male", "Last name": "Stout", "ID": "", "Email": "fishy@hotmail.com"}, {"First name": "Gene", "Age": "97", "Gender": "male", "Last name": "Sampson", "ID": "", "Email": "cocoanut@hotmail.com"}, {"First name": "Lacy", "Age": "49", "Gender": "female", "Last name": "Bauer", "ID": "", "Email": "encroachments@hotmail.com"}, {"First name": "Patty", "Age": "90", "Gender": "female", "Last name": "Hunter", "ID": "", "Email": "downstream@hotmail.com"}, {"First name": "Beverley", "Age": "70", "Gender": "female", "Last name": "Mcclure", "ID": "", "Email": "florentine@hotmail.com"}, {"First name": "Kayla", "Age": "93", "Gender": "female", "Last name": "Langley", "ID": "", "Email": "waking@hotmail.com"}, {"First name": "Taylor", "Age": "89", "Gender": "female", "Last name": "Dejesus", "ID": "", "Email": "southwestern@hotmail.com"}, {"First name": "Lynn", "Age": "32", "Gender": "female", "Last name": "Lambert", "ID": "", "Email": "farewells@gmail.com"}, {"First name": "Amelia", "Age": "54", "Gender": "female", "Last name": "Mooney", "ID": "", "Email": "dwarfs@hotmail.com"}, {"First name": "Yvette", "Age": "93", "Gender": "", "Last name": "Sherman", "ID": "", "Email": "prominent@gmail.com"}, {"First name": "Elmer", "Age": "26", "Gender": "male", "Last name": "Bishop", "ID": "", "Email": "panmunjom@gmail.com"}, {"First name": "Shawn", "Age": "10", "Gender": "male", "Last name": "Cochran", "ID": "", "Email": "thumbtack@gmail.com"}, {"First name": "Antonio", "Age": "72", "Gender": "male", "Last name": "Lewis", "ID": "", "Email": "faithlessness@gmail.com"}, {"First name": "Rosario", "Age": "13", "Gender": "female", "Last name": "Lynn", "ID": "", "Email": "lancashire@gmail.com"}, {"First name": "", "Age": "2", "Gender": "male", "Last name": "Moody", "ID": "", "Email": "fundamentals@gmail.com"}, {"First name": "Neil", "Age": "61", "Gender": "male", "Last name": "Callahan", "ID": "", "Email": "maharani@hotmail.com"}, {"First name": "Henry", "Age": "18", "Gender": "male", "Last name": "Salinas", "ID": "", "Email": "crunches@gmail.com"}, {"First name": "", "Age": "53", "Gender": "male", "Last name": "Pruitt", "ID": "", "Email": "milder@hotmail.com"}, {"First name": "", "Age": "5", "Gender": "male", "Last name": "Chandler", "ID": "", "Email": ""}, {"First name": "Clarence", "Age": "16", "Gender": "male", "Last name": "Gibbs", "ID": "", "Email": "circulations@gmail.com"}, {"First name": "Sallie", "Age": "85", "Gender": "female", "Last name": "Mosley", "ID": "", "Email": "viral@hotmail.com"}, {"First name": "Tommie", "Age": "39", "Gender": "female", "Last name": "Tyson", "ID": "", "Email": "punctured@hotmail.com"}, {"First name": "Jerri", "Age": "81", "Gender": "female", "Last name": "Beck", "ID": "", "Email": "neglects@gmail.com"}, {"First name": "Jimmy", "Age": "68", "Gender": "male", "Last name": "Kramer", "ID": "", "Email": "grossly@gmail.com"}, {"First name": "Jerri", "Age": "5", "Gender": "female", "Last name": "Lowery", "ID": "", "Email": "manpowers@gmail.com"}, {"First name": "Melba", "Age": "75", "Gender": "female", "Last name": "Hopper", "ID": "", "Email": "forever@hotmail.com"}, {"First name": "", "Age": "34", "Gender": "male", "Last name": "Santana", "ID": "", "Email": "consists@hotmail.com"}, {"First name": "Wallace", "Age": "98", "Gender": "male", "Last name": "Lowery", "ID": "", "Email": "boozes@hotmail.com"}, {"First name": "", "Age": "40", "Gender": "", "Last name": "Shaw", "ID": "", "Email": "erratically@gmail.com"}, {"First name": "Lillian", "Age": "80", "Gender": "female", "Last name": "Douglas", "ID": "", "Email": "goofed@hotmail.com"}, {"First name": "Clifton", "Age": "37", "Gender": "male", "Last name": "Gallegos", "ID": "", "Email": "truthfulnesss@hotmail.com"}, {"First name": "Clarence", "Age": "46", "Gender": "male", "Last name": "Booker", "ID": "", "Email": "britchess@gmail.com"}, {"First name": "Ray", "Age": "22", "Gender": "male", "Last name": "Kline", "ID": "", "Email": "glided@hotmail.com"}, {"First name": "Darrell", "Age": "95", "Gender": "male", "Last name": "Rodriquez", "ID": "", "Email": "pleases@gmail.com"}, {"First name": "Mike", "Age": "33", "Gender": "male", "Last name": "Franklin", "ID": "", "Email": "swain@hotmail.com"}, {"First name": "Terrence", "Age": "99", "Gender": "male", "Last name": "Irwin", "ID": "", "Email": "loonys@hotmail.com"}, {"First name": "Gordon", "Age": "89", "Gender": "male", "Last name": "Baxter", "ID": "", "Email": "altars@gmail.com"}, {"First name": "Herman", "Age": "37", "Gender": "male", "Last name": "Potts", "ID": "", "Email": "favoritisms@gmail.com"}, {"First name": "Lorie", "Age": "8", "Gender": "female", "Last name": "Pearson", "ID": "", "Email": "jaundices@hotmail.com"}, {"First name": "Roy", "Age": "63", "Gender": "male", "Last name": "Mercer", "ID": "", "Email": "billionth@hotmail.com"}, {"First name": "Sheena", "Age": "58", "Gender": "female", "Last name": "Collins", "ID": "", "Email": "compartmentalize@gmail.com"}, {"First name": "Virgil", "Age": "44", "Gender": "male", "Last name": "Strong", "ID": "", "Email": "ransack@gmail.com"}, {"First name": "Roslyn", "Age": "18", "Gender": "", "Last name": "Collins", "ID": "", "Email": "infinitive@hotmail.com"}, {"First name": "Katina", "Age": "53", "Gender": "female", "Last name": "Christensen", "ID": "", "Email": "eva@gmail.com"}, {"First name": "Ryan", "Age": "57", "Gender": "male", "Last name": "Gillespie", "ID": "", "Email": "phosphor@gmail.com"}, {"First name": "Marla", "Age": "75", "Gender": "female", "Last name": "Bowman", "ID": "", "Email": "seals@hotmail.com"}, {"First name": "Lee", "Age": "45", "Gender": "female", "Last name": "Brock", "ID": "", "Email": "borodin@gmail.com"}, {"First name": "James", "Age": "25", "Gender": "male", "Last name": "Hendrix", "ID": "", "Email": "deidre@hotmail.com"}, {"First name": "Richard", "Age": "42", "Gender": "male", "Last name": "Powers", "ID": "", "Email": "ch\u00e2teaux@gmail.com"}, {"First name": "Stanley", "Age": "73", "Gender": "male", "Last name": "Delaney", "ID": "", "Email": "overpriced@gmail.com"}, {"First name": "", "Age": "40", "Gender": "male", "Last name": "Robbins", "ID": "", "Email": ""}, {"First name": "Ken", "Age": "56", "Gender": "male", "Last name": "Allen", "ID": "", "Email": "tangerines@gmail.com"}, {"First name": "Wayne", "Age": "41", "Gender": "male", "Last name": "", "ID": "", "Email": "idols@hotmail.com"}, {"First name": "Christy", "Age": "67", "Gender": "female", "Last name": "Simmons", "ID": "", "Email": "sapient@gmail.com"}, {"First name": "Troy", "Age": "38", "Gender": "male", "Last name": "Aguirre", "ID": "", "Email": "excavate@gmail.com"}, {"First name": "Alan", "Age": "92", "Gender": "", "Last name": "Garrett", "ID": "", "Email": ""}, {"First name": "Kyle", "Age": "64", "Gender": "male", "Last name": "Chaney", "ID": "", "Email": "commentated@gmail.com"}, {"First name": "John", "Age": "65", "Gender": "male", "Last name": "Finch", "ID": "", "Email": "bart\u00f3k@hotmail.com"}, {"First name": "Willie", "Age": "61", "Gender": "male", "Last name": "Pace", "ID": "", "Email": "bridalveil@gmail.com"}, {"First name": "Erik", "Age": "52", "Gender": "male", "Last name": "Koch", "ID": "", "Email": "preceptors@gmail.com"}, {"First name": "James", "Age": "74", "Gender": "male", "Last name": "Reese", "ID": "", "Email": "integritys@gmail.com"}, {"First name": "Claudine", "Age": "48", "Gender": "female", "Last name": "Petty", "ID": "", "Email": "pebbling@hotmail.com"}, {"First name": "Carole", "Age": "33", "Gender": "female", "Last name": "Freeman", "ID": "", "Email": "mermaids@gmail.com"}, {"First name": "Claudette", "Age": "63", "Gender": "female", "Last name": "Knowles", "ID": "", "Email": "disprove@hotmail.com"}, {"First name": "", "Age": "21", "Gender": "male", "Last name": "Schmidt", "ID": "", "Email": "unending@hotmail.com"}, {"First name": "", "Age": "56", "Gender": "male", "Last name": "Jarvis", "ID": "", "Email": "charred@hotmail.com"}, {"First name": "Jay", "Age": "46", "Gender": "male", "Last name": "Hatfield", "ID": "", "Email": "hacksaw@hotmail.com"}, {"First name": "Jeremy", "Age": "1", "Gender": "male", "Last name": "Wyatt", "ID": "", "Email": "odin@hotmail.com"}, {"First name": "Keri", "Age": "63", "Gender": "female", "Last name": "Sampson", "ID": "", "Email": "pole@gmail.com"}, {"First name": "Johnnie", "Age": "2", "Gender": "male", "Last name": "Ferrell", "ID": "", "Email": "computes@gmail.com"}, {"First name": "Anne", "Age": "59", "Gender": "female", "Last name": "Soto", "ID": "", "Email": "homogeneously@hotmail.com"}, {"First name": "Bernadette", "Age": "6", "Gender": "female", "Last name": "Newton", "ID": "", "Email": "roundhouse@hotmail.com"}, {"First name": "Armando", "Age": "77", "Gender": "male", "Last name": "Trujillo", "ID": "", "Email": "collation@hotmail.com"}, {"First name": "Susie", "Age": "63", "Gender": "female", "Last name": "Reyes", "ID": "", "Email": "weatherizes@gmail.com"}, {"First name": "Ronnie", "Age": "29", "Gender": "male", "Last name": "Cochran", "ID": "", "Email": ""}, {"First name": "Esmeralda", "Age": "81", "Gender": "female", "Last name": "Church", "ID": "", "Email": "belongings@hotmail.com"}, {"First name": "Casey", "Age": "57", "Gender": "male", "Last name": "Howard", "ID": "", "Email": "hebrides@hotmail.com"}, {"First name": "Angel", "Age": "34", "Gender": "male", "Last name": "Chen", "ID": "", "Email": "generals@gmail.com"}, {"First name": "", "Age": "59", "Gender": "male", "Last name": "Middleton", "ID": "", "Email": "austerer@gmail.com"}, {"First name": "Lynnette", "Age": "56", "Gender": "female", "Last name": "Roth", "ID": "", "Email": "stratospheres@hotmail.com"}, {"First name": "Maryellen", "Age": "44", "Gender": "female", "Last name": "Shaw", "ID": "", "Email": "downtimes@gmail.com"}, {"First name": "Seth", "Age": "12", "Gender": "male", "Last name": "Wade", "ID": "", "Email": "narrate@hotmail.com"}, {"First name": "Terrence", "Age": "40", "Gender": "male", "Last name": "Hogan", "ID": "", "Email": "woolliest@hotmail.com"}, {"First name": "Marisa", "Age": "82", "Gender": "female", "Last name": "Craig", "ID": "", "Email": "obeyed@gmail.com"}, {"First name": "Ruthie", "Age": "32", "Gender": "female", "Last name": "Keller", "ID": "", "Email": "divvys@gmail.com"}, {"First name": "Charles", "Age": "64", "Gender": "male", "Last name": "Weeks", "ID": "", "Email": "stand@hotmail.com"}, {"First name": "Brandi", "Age": "45", "Gender": "female", "Last name": "Snow", "ID": "", "Email": "employers@gmail.com"}, {"First name": "", "Age": "86", "Gender": "male", "Last name": "Dillon", "ID": "", "Email": "acupuncturists@hotmail.com"}, {"First name": "Francesca", "Age": "71", "Gender": "female", "Last name": "Russo", "ID": "", "Email": "betook@gmail.com"}, {"First name": "Dolores", "Age": "27", "Gender": "female", "Last name": "Tillman", "ID": "", "Email": "drab@gmail.com"}, {"First name": "Victor", "Age": "34", "Gender": "male", "Last name": "Huber", "ID": "", "Email": "washouts@hotmail.com"}, {"First name": "Ivy", "Age": "44", "Gender": "female", "Last name": "Rice", "ID": "", "Email": ""}, {"First name": "Tom", "Age": "7", "Gender": "male", "Last name": "Mosley", "ID": "", "Email": "gouts@gmail.com"}, {"First name": "Helene", "Age": "20", "Gender": "female", "Last name": "Flores", "ID": "", "Email": ""}, {"First name": "Joseph", "Age": "22", "Gender": "male", "Last name": "Roy", "ID": "", "Email": "hypothalami@hotmail.com"}, {"First name": "Marvin", "Age": "37", "Gender": "male", "Last name": "Keller", "ID": "", "Email": "caterwauls@hotmail.com"}, {"First name": "Felix", "Age": "55", "Gender": "male", "Last name": "Guerra", "ID": "", "Email": "critter@gmail.com"}, {"First name": "Eddie", "Age": "66", "Gender": "male", "Last name": "Simpson", "ID": "", "Email": "roughhouses@gmail.com"}, {"First name": "Liliana", "Age": "33", "Gender": "female", "Last name": "Gilliam", "ID": "", "Email": "staffs@gmail.com"}, {"First name": "Jared", "Age": "34", "Gender": "male", "Last name": "Keller", "ID": "", "Email": "plasticines@gmail.com"}, {"First name": "Cheryl", "Age": "13", "Gender": "", "Last name": "Reilly", "ID": "", "Email": "muckier@gmail.com"}, {"First name": "Shawna", "Age": "46", "Gender": "female", "Last name": "Mclaughlin", "ID": "", "Email": "prolongations@hotmail.com"}, {"First name": "", "Age": "53", "Gender": "female", "Last name": "Berg", "ID": "", "Email": "heliums@gmail.com"}, {"First name": "Karl", "Age": "77", "Gender": "male", "Last name": "Kelley", "ID": "", "Email": "replacing@hotmail.com"}, {"First name": "Randy", "Age": "45", "Gender": "male", "Last name": "Hurley", "ID": "", "Email": "yous@gmail.com"}, {"First name": "Lonnie", "Age": "74", "Gender": "", "Last name": "Lawrence", "ID": "", "Email": "victimizing@gmail.com"}, {"First name": "Ernest", "Age": "7", "Gender": "male", "Last name": "Gentry", "ID": "", "Email": "appositely@gmail.com"}, {"First name": "Helen", "Age": "5", "Gender": "female", "Last name": "Sullivan", "ID": "", "Email": "jainism@hotmail.com"}, {"First name": "Wendy", "Age": "44", "Gender": "female", "Last name": "Sampson", "ID": "", "Email": "moderns@hotmail.com"}, {"First name": "Cory", "Age": "42", "Gender": "male", "Last name": "Ferguson", "ID": "", "Email": "castrations@hotmail.com"}, {"First name": "Lorrie", "Age": "80", "Gender": "female", "Last name": "Harmon", "ID": "", "Email": "crick@hotmail.com"}, {"First name": "Rosalyn", "Age": "79", "Gender": "female", "Last name": "Slater", "ID": "", "Email": "kickoff@gmail.com"}, {"First name": "Jared", "Age": "84", "Gender": "male", "Last name": "Galloway", "ID": "", "Email": "sensibility@gmail.com"}, {"First name": "Tyrone", "Age": "41", "Gender": "male", "Last name": "Blanchard", "ID": "", "Email": "metastasized@gmail.com"}, {"First name": "Ronald", "Age": "62", "Gender": "male", "Last name": "Cash", "ID": "", "Email": "jackrabbits@hotmail.com"}, {"First name": "", "Age": "97", "Gender": "", "Last name": "", "ID": "", "Email": "exclusivitys@gmail.com"}, {"First name": "Eugenia", "Age": "75", "Gender": "female", "Last name": "Conway", "ID": "", "Email": "marauded@hotmail.com"}, {"First name": "Allan", "Age": "26", "Gender": "male", "Last name": "Underwood", "ID": "", "Email": "vanquishes@gmail.com"}, {"First name": "Maryann", "Age": "9", "Gender": "", "Last name": "Molina", "ID": "", "Email": "rosie@hotmail.com"}, {"First name": "Jesus", "Age": "26", "Gender": "male", "Last name": "Montoya", "ID": "", "Email": "postmarked@hotmail.com"}, {"First name": "Fanny", "Age": "35", "Gender": "female", "Last name": "Hudson", "ID": "", "Email": "offenders@gmail.com"}, {"First name": "Norman", "Age": "14", "Gender": "male", "Last name": "Goff", "ID": "", "Email": "bobsledded@hotmail.com"}, {"First name": "Marcella", "Age": "7", "Gender": "female", "Last name": "Wooten", "ID": "", "Email": "autobiographys@gmail.com"}, {"First name": "Letitia", "Age": "82", "Gender": "female", "Last name": "Beasley", "ID": "", "Email": "incompleteness@hotmail.com"}, {"First name": "Alberto", "Age": "82", "Gender": "male", "Last name": "Mccarty", "ID": "", "Email": "sedate@gmail.com"}, {"First name": "Andrea", "Age": "55", "Gender": "female", "Last name": "Hobbs", "ID": "", "Email": "pulsars@gmail.com"}, {"First name": "Alex", "Age": "86", "Gender": "male", "Last name": "Alvarez", "ID": "", "Email": "bobsledded@hotmail.com"}, {"First name": "Lorna", "Age": "50", "Gender": "female", "Last name": "Booker", "ID": "", "Email": "notebooks@gmail.com"}, {"First name": "Ian", "Age": "51", "Gender": "male", "Last name": "Robinson", "ID": "", "Email": "ragged@hotmail.com"}, {"First name": "Cody", "Age": "65", "Gender": "male", "Last name": "Floyd", "ID": "", "Email": "sol@hotmail.com"}, {"First name": "Stephen", "Age": "41", "Gender": "male", "Last name": "Wiggins", "ID": "", "Email": "municipal@gmail.com"}, {"First name": "Phillip", "Age": "56", "Gender": "male", "Last name": "Fitzpatrick", "ID": "", "Email": "jutted@gmail.com"}, {"First name": "Beverly", "Age": "14", "Gender": "female", "Last name": "Shepherd", "ID": "", "Email": "overshoes@gmail.com"}, {"First name": "Chelsea", "Age": "88", "Gender": "female", "Last name": "Carson", "ID": "", "Email": "samuel@gmail.com"}, {"First name": "Sheree", "Age": "67", "Gender": "female", "Last name": "Kaufman", "ID": "", "Email": "loggings@gmail.com"}, {"First name": "Patrick", "Age": "48", "Gender": "male", "Last name": "England", "ID": "", "Email": "scalawags@hotmail.com"}, {"First name": "Ashley", "Age": "75", "Gender": "female", "Last name": "Lee", "ID": "", "Email": "excellence@hotmail.com"}, {"First name": "Lois", "Age": "82", "Gender": "female", "Last name": "Case", "ID": "", "Email": "ernestine@gmail.com"}, {"First name": "Jorge", "Age": "82", "Gender": "male", "Last name": "Rasmussen", "ID": "", "Email": ""}, {"First name": "Corine", "Age": "86", "Gender": "female", "Last name": "Mckay", "ID": "", "Email": "mindedness@gmail.com"}, {"First name": "Wallace", "Age": "36", "Gender": "male", "Last name": "Guerrero", "ID": "", "Email": "concurrences@hotmail.com"}, {"First name": "Wayne", "Age": "6", "Gender": "male", "Last name": "Mcmillan", "ID": "", "Email": "obdurate@gmail.com"}, {"First name": "Reginald", "Age": "52", "Gender": "male", "Last name": "Lara", "ID": "", "Email": "bitterns@gmail.com"}, {"First name": "Jonathan", "Age": "46", "Gender": "male", "Last name": "Zamora", "ID": "", "Email": "internships@hotmail.com"}, {"First name": "", "Age": "35", "Gender": "female", "Last name": "Gentry", "ID": "", "Email": "flags@hotmail.com"}, {"First name": "Andy", "Age": "60", "Gender": "male", "Last name": "Morrow", "ID": "", "Email": "divests@hotmail.com"}, {"First name": "Guadalupe", "Age": "54", "Gender": "female", "Last name": "Farrell", "ID": "", "Email": ""}, {"First name": "Tonya", "Age": "56", "Gender": "female", "Last name": "Ware", "ID": "", "Email": "work@hotmail.com"}, {"First name": "Marcus", "Age": "57", "Gender": "male", "Last name": "Sherman", "ID": "", "Email": "conciliatory@gmail.com"}, {"First name": "Daryl", "Age": "82", "Gender": "male", "Last name": "Davenport", "ID": "", "Email": "stats@hotmail.com"}, {"First name": "Erik", "Age": "34", "Gender": "male", "Last name": "Mann", "ID": "", "Email": "jabber@hotmail.com"}, {"First name": "Darren", "Age": "42", "Gender": "male", "Last name": "", "ID": "", "Email": "crackerjacks@hotmail.com"}, {"First name": "Carolyn", "Age": "17", "Gender": "female", "Last name": "Petty", "ID": "", "Email": "supporters@hotmail.com"}, {"First name": "Darrell", "Age": "62", "Gender": "male", "Last name": "Adams", "ID": "", "Email": "craggiest@gmail.com"}, {"First name": "Maurice", "Age": "89", "Gender": "male", "Last name": "Kemp", "ID": "", "Email": "dallying@hotmail.com"}, {"First name": "Gertrude", "Age": "83", "Gender": "female", "Last name": "Gillespie", "ID": "", "Email": ""}, {"First name": "Molly", "Age": "37", "Gender": "female", "Last name": "Tate", "ID": "", "Email": "triples@hotmail.com"}, {"First name": "Clinton", "Age": "54", "Gender": "male", "Last name": "Payne", "ID": "", "Email": "thoroughly@hotmail.com"}, {"First name": "Lily", "Age": "31", "Gender": "female", "Last name": "Barrera", "ID": "", "Email": "cobols@gmail.com"}, {"First name": "Dollie", "Age": "77", "Gender": "female", "Last name": "Harrell", "ID": "", "Email": "gilds@gmail.com"}, {"First name": "Hattie", "Age": "20", "Gender": "female", "Last name": "Clayton", "ID": "", "Email": "timescales@gmail.com"}, {"First name": "Brooke", "Age": "65", "Gender": "female", "Last name": "Williamson", "ID": "", "Email": "grackles@hotmail.com"}, {"First name": "Neva", "Age": "48", "Gender": "female", "Last name": "Fitzpatrick", "ID": "", "Email": "complaint@gmail.com"}, {"First name": "Willard", "Age": "67", "Gender": "male", "Last name": "Rosa", "ID": "", "Email": "cardiologist@hotmail.com"}, {"First name": "Cristina", "Age": "13", "Gender": "female", "Last name": "Oneil", "ID": "", "Email": "quadruples@gmail.com"}, {"First name": "Jeffrey", "Age": "78", "Gender": "male", "Last name": "Hutchinson", "ID": "", "Email": "durantes@gmail.com"}, {"First name": "Eric", "Age": "17", "Gender": "male", "Last name": "Carr", "ID": "", "Email": "satisfy@gmail.com"}, {"First name": "Rosanne", "Age": "94", "Gender": "female", "Last name": "Dean", "ID": "", "Email": "bristled@hotmail.com"}, {"First name": "Marc", "Age": "61", "Gender": "male", "Last name": "Cox", "ID": "", "Email": "colt@hotmail.com"}, {"First name": "Sophie", "Age": "27", "Gender": "female", "Last name": "Charles", "ID": "", "Email": ""}, {"First name": "Meredith", "Age": "30", "Gender": "female", "Last name": "Moody", "ID": "", "Email": "embarrassingly@gmail.com"}, {"First name": "Liliana", "Age": "84", "Gender": "female", "Last name": "Cobb", "ID": "", "Email": "depositor@gmail.com"}, {"First name": "Mai", "Age": "57", "Gender": "female", "Last name": "Nash", "ID": "", "Email": "acrobatics@hotmail.com"}, {"First name": "Laura", "Age": "73", "Gender": "female", "Last name": "Sanders", "ID": "", "Email": ""}, {"First name": "Clinton", "Age": "63", "Gender": "male", "Last name": "Davis", "ID": "", "Email": "devastated@gmail.com"}, {"First name": "Angel", "Age": "68", "Gender": "female", "Last name": "Swanson", "ID": "", "Email": "tameka@gmail.com"}, {"First name": "Hattie", "Age": "27", "Gender": "female", "Last name": "Lyons", "ID": "", "Email": "odyssey@hotmail.com"}, {"First name": "Daniel", "Age": "51", "Gender": "male", "Last name": "Small", "ID": "", "Email": "rockier@hotmail.com"}, {"First name": "Carmella", "Age": "89", "Gender": "female", "Last name": "Levine", "ID": "", "Email": "july@hotmail.com"}, {"First name": "Aaron", "Age": "56", "Gender": "male", "Last name": "Perez", "ID": "", "Email": "decommissioned@hotmail.com"}, {"First name": "Leta", "Age": "33", "Gender": "female", "Last name": "Griffin", "ID": "", "Email": "usages@gmail.com"}, {"First name": "Lonnie", "Age": "17", "Gender": "male", "Last name": "Knox", "ID": "", "Email": "leukocyte@gmail.com"}, {"First name": "Harvey", "Age": "41", "Gender": "male", "Last name": "Lindsay", "ID": "", "Email": ""}, {"First name": "Stacey", "Age": "98", "Gender": "female", "Last name": "Ruiz", "ID": "", "Email": "typewriters@hotmail.com"}, {"First name": "Yesenia", "Age": "42", "Gender": "female", "Last name": "Contreras", "ID": "", "Email": "cation@hotmail.com"}, {"First name": "Ian", "Age": "32", "Gender": "male", "Last name": "Oneal", "ID": "", "Email": "disclosures@gmail.com"}, {"First name": "Hilary", "Age": "12", "Gender": "female", "Last name": "Delacruz", "ID": "", "Email": "solder@gmail.com"}, {"First name": "Vivian", "Age": "85", "Gender": "female", "Last name": "Miller", "ID": "", "Email": "weary@hotmail.com"}, {"First name": "Bryan", "Age": "76", "Gender": "male", "Last name": "Allison", "ID": "", "Email": "reconnoitering@hotmail.com"}, {"First name": "Claudine", "Age": "69", "Gender": "female", "Last name": "Bradford", "ID": "", "Email": "kiowas@hotmail.com"}, {"First name": "Harold", "Age": "90", "Gender": "male", "Last name": "Bush", "ID": "", "Email": "pothooks@gmail.com"}, {"First name": "Pearl", "Age": "39", "Gender": "female", "Last name": "Jones", "ID": "", "Email": "overtaking@gmail.com"}, {"First name": "Nathaniel", "Age": "33", "Gender": "male", "Last name": "Daniel", "ID": "", "Email": "undershirts@hotmail.com"}, {"First name": "Lee", "Age": "80", "Gender": "", "Last name": "Beasley", "ID": "", "Email": "rags@hotmail.com"}, {"First name": "Marion", "Age": "90", "Gender": "male", "Last name": "Noel", "ID": "", "Email": "mushrooms@hotmail.com"}, {"First name": "Sharlene", "Age": "76", "Gender": "female", "Last name": "Maxwell", "ID": "", "Email": "fledglings@hotmail.com"}, {"First name": "Harriet", "Age": "41", "Gender": "female", "Last name": "Elliott", "ID": "", "Email": "marchers@hotmail.com"}, {"First name": "Rhea", "Age": "50", "Gender": "female", "Last name": "Reeves", "ID": "", "Email": "bevel@gmail.com"}, {"First name": "Kasey", "Age": "33", "Gender": "female", "Last name": "Kramer", "ID": "", "Email": "egoist@hotmail.com"}, {"First name": "Katelyn", "Age": "10", "Gender": "female", "Last name": "Reynolds", "ID": "", "Email": "rosiest@hotmail.com"}, {"First name": "Deirdre", "Age": "71", "Gender": "female", "Last name": "Pittman", "ID": "", "Email": "rationalist@hotmail.com"}, {"First name": "Jerri", "Age": "50", "Gender": "female", "Last name": "", "ID": "", "Email": "scrutinys@gmail.com"}, {"First name": "Larry", "Age": "68", "Gender": "male", "Last name": "Whitney", "ID": "", "Email": "alar@hotmail.com"}, {"First name": "Sara", "Age": "8", "Gender": "female", "Last name": "Walsh", "ID": "", "Email": "counterpanes@gmail.com"}, {"First name": "Sean", "Age": "26", "Gender": "male", "Last name": "Keith", "ID": "", "Email": "cartels@gmail.com"}, {"First name": "Adriana", "Age": "60", "Gender": "female", "Last name": "Sellers", "ID": "", "Email": "union@hotmail.com"}, {"First name": "Dale", "Age": "69", "Gender": "female", "Last name": "Patrick", "ID": "", "Email": "detonations@gmail.com"}, {"First name": "Kenneth", "Age": "47", "Gender": "male", "Last name": "Richmond", "ID": "", "Email": "communicants@hotmail.com"}, {"First name": "Dionne", "Age": "85", "Gender": "female", "Last name": "Stewart", "ID": "", "Email": "soused@hotmail.com"}, {"First name": "Samuel", "Age": "12", "Gender": "male", "Last name": "Kirk", "ID": "", "Email": "photographing@gmail.com"}, {"First name": "Sonia", "Age": "14", "Gender": "female", "Last name": "Santana", "ID": "", "Email": "palmyra@gmail.com"}, {"First name": "Andre", "Age": "77", "Gender": "male", "Last name": "Collier", "ID": "", "Email": "lubrication@hotmail.com"}, {"First name": "Irma", "Age": "42", "Gender": "female", "Last name": "Garrett", "ID": "", "Email": "recommence@hotmail.com"}, {"First name": "Dina", "Age": "79", "Gender": "female", "Last name": "Boone", "ID": "", "Email": "sidestrokes@gmail.com"}, {"First name": "Kerry", "Age": "59", "Gender": "female", "Last name": "Welch", "ID": "", "Email": "viewing@hotmail.com"}, {"First name": "Mario", "Age": "52", "Gender": "male", "Last name": "Sanford", "ID": "", "Email": ""}, {"First name": "Celia", "Age": "47", "Gender": "female", "Last name": "Booth", "ID": "", "Email": "snub@gmail.com"}, {"First name": "Alexandra", "Age": "92", "Gender": "female", "Last name": "Mcleod", "ID": "", "Email": ""}, {"First name": "Andrea", "Age": "99", "Gender": "female", "Last name": "Jensen", "ID": "", "Email": "biscuits@hotmail.com"}, {"First name": "Penny", "Age": "64", "Gender": "", "Last name": "Shelton", "ID": "", "Email": "hipparchus@hotmail.com"}, {"First name": "Josephine", "Age": "36", "Gender": "female", "Last name": "Daniel", "ID": "", "Email": "cases@hotmail.com"}, {"First name": "Serena", "Age": "76", "Gender": "", "Last name": "Acosta", "ID": "", "Email": "outliving@gmail.com"}, {"First name": "Kerri", "Age": "76", "Gender": "female", "Last name": "Nash", "ID": "", "Email": "scholastic@gmail.com"}, {"First name": "Timothy", "Age": "84", "Gender": "male", "Last name": "Martin", "ID": "", "Email": "truncheon@hotmail.com"}, {"First name": "Megan", "Age": "12", "Gender": "female", "Last name": "Cunningham", "ID": "", "Email": "pigmys@gmail.com"}, {"First name": "Janna", "Age": "78", "Gender": "female", "Last name": "Mcfarland", "ID": "", "Email": "wool@gmail.com"}, {"First name": "Valerie", "Age": "53", "Gender": "female", "Last name": "Kelly", "ID": "", "Email": "rotarys@gmail.com"}, {"First name": "Suzanne", "Age": "39", "Gender": "female", "Last name": "Sanders", "ID": "", "Email": "reincarnate@gmail.com"}, {"First name": "Ila", "Age": "39", "Gender": "female", "Last name": "Christian", "ID": "", "Email": "defend@gmail.com"}, {"First name": "Francis", "Age": "45", "Gender": "male", "Last name": "Maddox", "ID": "", "Email": "irritably@hotmail.com"}, {"First name": "Derek", "Age": "77", "Gender": "male", "Last name": "Mason", "ID": "", "Email": "orvilles@hotmail.com"}, {"First name": "Earl", "Age": "97", "Gender": "male", "Last name": "Mccullough", "ID": "", "Email": "schooners@gmail.com"}, {"First name": "Rodney", "Age": "2", "Gender": "male", "Last name": "Carroll", "ID": "", "Email": "semiramis@hotmail.com"}, {"First name": "Lilian", "Age": "17", "Gender": "female", "Last name": "Shaffer", "ID": "", "Email": "amaru@gmail.com"}, {"First name": "Ronnie", "Age": "13", "Gender": "male", "Last name": "Trevino", "ID": "", "Email": "those@gmail.com"}, {"First name": "Ted", "Age": "0", "Gender": "male", "Last name": "Woods", "ID": "", "Email": "palefaces@hotmail.com"}, {"First name": "", "Age": "30", "Gender": "female", "Last name": "Newman", "ID": "", "Email": "copes@hotmail.com"}, {"First name": "Catherine", "Age": "51", "Gender": "female", "Last name": "Mathews", "ID": "", "Email": "arsenals@gmail.com"}, {"First name": "Allan", "Age": "75", "Gender": "male", "Last name": "Boyer", "ID": "", "Email": "freemasons@hotmail.com"}, {"First name": "Antonio", "Age": "63", "Gender": "male", "Last name": "Moore", "ID": "", "Email": "twirler@hotmail.com"}, {"First name": "", "Age": "43", "Gender": "female", "Last name": "Sandoval", "ID": "", "Email": "humanitys@hotmail.com"}, {"First name": "Ruby", "Age": "42", "Gender": "female", "Last name": "Roy", "ID": "", "Email": "crinkly@hotmail.com"}, {"First name": "Rochelle", "Age": "22", "Gender": "female", "Last name": "Ratliff", "ID": "", "Email": "mousetraps@hotmail.com"}, {"First name": "Adrian", "Age": "85", "Gender": "male", "Last name": "Ford", "ID": "", "Email": "posited@hotmail.com"}, {"First name": "", "Age": "30", "Gender": "male", "Last name": "", "ID": "", "Email": "prevalence@hotmail.com"}, {"First name": "Sam", "Age": "92", "Gender": "male", "Last name": "Garcia", "ID": "", "Email": "lighted@gmail.com"}, {"First name": "Mike", "Age": "45", "Gender": "male", "Last name": "Rios", "ID": "", "Email": "fathomed@hotmail.com"}, {"First name": "Wesley", "Age": "67", "Gender": "", "Last name": "Bowen", "ID": "", "Email": "jfks@gmail.com"}, {"First name": "Annabelle", "Age": "75", "Gender": "female", "Last name": "Chang", "ID": "", "Email": "contingency@hotmail.com"}, {"First name": "", "Age": "38", "Gender": "female", "Last name": "Dunn", "ID": "", "Email": "bequeaths@hotmail.com"}, {"First name": "Letha", "Age": "80", "Gender": "female", "Last name": "Sellers", "ID": "", "Email": "lodger@hotmail.com"}, {"First name": "", "Age": "15", "Gender": "male", "Last name": "Anderson", "ID": "", "Email": "accentuations@gmail.com"}, {"First name": "Lorna", "Age": "78", "Gender": "female", "Last name": "Livingston", "ID": "", "Email": ""}, {"First name": "Jerry", "Age": "88", "Gender": "female", "Last name": "Randolph", "ID": "", "Email": "knowles@hotmail.com"}, {"First name": "Milton", "Age": "45", "Gender": "male", "Last name": "Cox", "ID": "", "Email": "parboiling@gmail.com"}, {"First name": "Greg", "Age": "53", "Gender": "male", "Last name": "Raymond", "ID": "", "Email": "convoying@hotmail.com"}, {"First name": "Candace", "Age": "34", "Gender": "female", "Last name": "Shaw", "ID": "", "Email": "passel@gmail.com"}, {"First name": "Bill", "Age": "19", "Gender": "male", "Last name": "Woods", "ID": "", "Email": "tweeters@hotmail.com"}, {"First name": "Opal", "Age": "15", "Gender": "female", "Last name": "Ewing", "ID": "", "Email": "buds@hotmail.com"}, {"First name": "Leonor", "Age": "67", "Gender": "female", "Last name": "Patton", "ID": "", "Email": "pessimistic@gmail.com"}, {"First name": "", "Age": "77", "Gender": "male", "Last name": "Jefferson", "ID": "", "Email": "deteriorated@hotmail.com"}, {"First name": "Melvin", "Age": "75", "Gender": "male", "Last name": "Blake", "ID": "", "Email": "seans@gmail.com"}, {"First name": "Lindsay", "Age": "94", "Gender": "female", "Last name": "Bennett", "ID": "", "Email": "okras@hotmail.com"}, {"First name": "Susie", "Age": "96", "Gender": "female", "Last name": "Wilson", "ID": "", "Email": "bestir@hotmail.com"}, {"First name": "Luis", "Age": "92", "Gender": "male", "Last name": "Malone", "ID": "", "Email": "laos@hotmail.com"}, {"First name": "Elizabeth", "Age": "22", "Gender": "female", "Last name": "Acosta", "ID": "", "Email": "agonizing@hotmail.com"}, {"First name": "", "Age": "34", "Gender": "male", "Last name": "Bell", "ID": "", "Email": "god@gmail.com"}, {"First name": "Sonia", "Age": "76", "Gender": "female", "Last name": "", "ID": "", "Email": "elaboratenesss@hotmail.com"}, {"First name": "Antonio", "Age": "62", "Gender": "male", "Last name": "Marshall", "ID": "", "Email": "larding@hotmail.com"}, {"First name": "Marcia", "Age": "1", "Gender": "female", "Last name": "Gardner", "ID": "", "Email": "kochabs@gmail.com"}, {"First name": "Timothy", "Age": "58", "Gender": "male", "Last name": "Serrano", "ID": "", "Email": "mashs@gmail.com"}, {"First name": "Tamera", "Age": "45", "Gender": "female", "Last name": "Hyde", "ID": "", "Email": "tintinnabulations@hotmail.com"}, {"First name": "Glenn", "Age": "98", "Gender": "male", "Last name": "Mcneil", "ID": "", "Email": "substantives@gmail.com"}, {"First name": "Phoebe", "Age": "44", "Gender": "female", "Last name": "Roberts", "ID": "", "Email": "errors@hotmail.com"}, {"First name": "Francisco", "Age": "8", "Gender": "male", "Last name": "Collins", "ID": "", "Email": "objectives@gmail.com"}, {"First name": "Jacob", "Age": "96", "Gender": "male", "Last name": "Johnson", "ID": "", "Email": "burls@hotmail.com"}, {"First name": "Jodi", "Age": "82", "Gender": "female", "Last name": "Petty", "ID": "", "Email": "rubicund@hotmail.com"}, {"First name": "Marcus", "Age": "32", "Gender": "male", "Last name": "Anthony", "ID": "", "Email": "megalomaniacs@hotmail.com"}, {"First name": "Ted", "Age": "31", "Gender": "male", "Last name": "Olsen", "ID": "", "Email": "ruffled@hotmail.com"}, {"First name": "Carlos", "Age": "17", "Gender": "male", "Last name": "Ewing", "ID": "", "Email": "intonations@gmail.com"}, {"First name": "Janis", "Age": "80", "Gender": "female", "Last name": "Nunez", "ID": "", "Email": "assemblymans@gmail.com"}, {"First name": "Ladonna", "Age": "52", "Gender": "female", "Last name": "Moss", "ID": "", "Email": "tanagers@gmail.com"}, {"First name": "Jessie", "Age": "38", "Gender": "male", "Last name": "Saunders", "ID": "", "Email": "plexiglases@hotmail.com"}, {"First name": "Oscar", "Age": "13", "Gender": "male", "Last name": "Farmer", "ID": "", "Email": "boxers@gmail.com"}, {"First name": "Jaime", "Age": "76", "Gender": "male", "Last name": "Weber", "ID": "", "Email": "maalox@hotmail.com"}, {"First name": "Dee", "Age": "34", "Gender": "female", "Last name": "Goodman", "ID": "", "Email": "compacting@hotmail.com"}, {"First name": "Troy", "Age": "46", "Gender": "male", "Last name": "Chan", "ID": "", "Email": "remunerations@hotmail.com"}, {"First name": "Guy", "Age": "80", "Gender": "male", "Last name": "Craig", "ID": "", "Email": "bleated@hotmail.com"}, {"First name": "Marquita", "Age": "88", "Gender": "female", "Last name": "Marks", "ID": "", "Email": "logrollings@hotmail.com"}, {"First name": "Cecil", "Age": "69", "Gender": "male", "Last name": "Klein", "ID": "", "Email": "ozarkss@hotmail.com"}, {"First name": "", "Age": "28", "Gender": "male", "Last name": "Schultz", "ID": "", "Email": "execrates@gmail.com"}, {"First name": "Ann", "Age": "80", "Gender": "female", "Last name": "Webster", "ID": "", "Email": "thats@gmail.com"}, {"First name": "Trina", "Age": "69", "Gender": "female", "Last name": "Burch", "ID": "", "Email": "luiss@gmail.com"}, {"First name": "Howard", "Age": "73", "Gender": "male", "Last name": "Galloway", "ID": "", "Email": "polytechnics@gmail.com"}, {"First name": "Janelle", "Age": "21", "Gender": "female", "Last name": "Hopper", "ID": "", "Email": "judaisms@gmail.com"}, {"First name": "Todd", "Age": "9", "Gender": "male", "Last name": "Scott", "ID": "", "Email": "civics@gmail.com"}, {"First name": "Catherine", "Age": "96", "Gender": "female", "Last name": "Langley", "ID": "", "Email": "sage@hotmail.com"}, {"First name": "Phoebe", "Age": "30", "Gender": "female", "Last name": "Hutchinson", "ID": "", "Email": "reckon@gmail.com"}, {"First name": "", "Age": "74", "Gender": "female", "Last name": "Barry", "ID": "", "Email": "braced@hotmail.com"}, {"First name": "Clarence", "Age": "7", "Gender": "male", "Last name": "Harris", "ID": "", "Email": "digested@gmail.com"}, {"First name": "Alissa", "Age": "78", "Gender": "female", "Last name": "Moody", "ID": "", "Email": "barrings@hotmail.com"}, {"First name": "Darrell", "Age": "31", "Gender": "male", "Last name": "Rosales", "ID": "", "Email": "summers@gmail.com"}, {"First name": "Minerva", "Age": "51", "Gender": "female", "Last name": "Alvarado", "ID": "", "Email": "underpaid@gmail.com"}, {"First name": "Minerva", "Age": "88", "Gender": "female", "Last name": "Maxwell", "ID": "", "Email": "foreknowledges@hotmail.com"}, {"First name": "David", "Age": "14", "Gender": "male", "Last name": "Pierce", "ID": "", "Email": "parkas@hotmail.com"}, {"First name": "Meredith", "Age": "32", "Gender": "female", "Last name": "Cabrera", "ID": "", "Email": "customers@hotmail.com"}, {"First name": "Patrice", "Age": "75", "Gender": "female", "Last name": "Stevenson", "ID": "", "Email": "rationalizes@hotmail.com"}, {"First name": "Lloyd", "Age": "2", "Gender": "male", "Last name": "Cash", "ID": "", "Email": "dumfounding@hotmail.com"}, {"First name": "Beverley", "Age": "16", "Gender": "female", "Last name": "Bryan", "ID": "", "Email": "aquaplaning@hotmail.com"}, {"First name": "Krista", "Age": "9", "Gender": "female", "Last name": "Ortiz", "ID": "", "Email": "campanellas@hotmail.com"}, {"First name": "Roxanne", "Age": "10", "Gender": "female", "Last name": "Pratt", "ID": "", "Email": "naked@hotmail.com"}, {"First name": "Penny", "Age": "55", "Gender": "", "Last name": "Briggs", "ID": "", "Email": "delinquents@gmail.com"}, {"First name": "Vilma", "Age": "34", "Gender": "female", "Last name": "Martin", "ID": "", "Email": "southwests@hotmail.com"}, {"First name": "", "Age": "20", "Gender": "male", "Last name": "Chambers", "ID": "", "Email": "insensibility@gmail.com"}, {"First name": "Hugh", "Age": "6", "Gender": "male", "Last name": "Wright", "ID": "", "Email": "rapprochements@hotmail.com"}, {"First name": "Francisco", "Age": "16", "Gender": "male", "Last name": "Hull", "ID": "", "Email": ""}, {"First name": "Bridget", "Age": "32", "Gender": "female", "Last name": "Foley", "ID": "", "Email": "dimaggio@hotmail.com"}, {"First name": "", "Age": "57", "Gender": "female", "Last name": "Garcia", "ID": "", "Email": "chimps@hotmail.com"}, {"First name": "Casey", "Age": "26", "Gender": "male", "Last name": "", "ID": "", "Email": "beta@gmail.com"}, {"First name": "Muriel", "Age": "55", "Gender": "female", "Last name": "Hammond", "ID": "", "Email": "belabors@gmail.com"}, {"First name": "Bill", "Age": "55", "Gender": "male", "Last name": "Thomas", "ID": "", "Email": "lubricate@gmail.com"}, {"First name": "Andre", "Age": "84", "Gender": "male", "Last name": "Vincent", "ID": "", "Email": "sacrilegious@hotmail.com"}, {"First name": "Leo", "Age": "98", "Gender": "male", "Last name": "Peters", "ID": "", "Email": "sixpences@gmail.com"}, {"First name": "Johnny", "Age": "5", "Gender": "male", "Last name": "Travis", "ID": "", "Email": "receptivitys@hotmail.com"}, {"First name": "Essie", "Age": "73", "Gender": "female", "Last name": "Alexander", "ID": "", "Email": "flusters@hotmail.com"}, {"First name": "Leah", "Age": "78", "Gender": "female", "Last name": "Wolf", "ID": "", "Email": "misfeasances@hotmail.com"}, {"First name": "Mai", "Age": "7", "Gender": "female", "Last name": "Ryan", "ID": "", "Email": "latests@hotmail.com"}, {"First name": "Thomas", "Age": "64", "Gender": "male", "Last name": "Bolton", "ID": "", "Email": ""}, {"First name": "Alvin", "Age": "74", "Gender": "male", "Last name": "Payne", "ID": "", "Email": "inversions@gmail.com"}, {"First name": "Julian", "Age": "35", "Gender": "male", "Last name": "Best", "ID": "", "Email": "brick@hotmail.com"}, {"First name": "Ken", "Age": "86", "Gender": "male", "Last name": "Baldwin", "ID": "", "Email": "nimbus@gmail.com"}, {"First name": "", "Age": "64", "Gender": "female", "Last name": "Carlson", "ID": "", "Email": "swilling@gmail.com"}, {"First name": "Bette", "Age": "18", "Gender": "female", "Last name": "David", "ID": "", "Email": "stylus@hotmail.com"}, {"First name": "", "Age": "48", "Gender": "male", "Last name": "Cameron", "ID": "", "Email": "jamaicans@hotmail.com"}, {"First name": "Gene", "Age": "58", "Gender": "male", "Last name": "Becker", "ID": "", "Email": "candaces@hotmail.com"}, {"First name": "Marina", "Age": "53", "Gender": "female", "Last name": "Bridges", "ID": "", "Email": "inspires@gmail.com"}, {"First name": "Beatriz", "Age": "59", "Gender": "female", "Last name": "Rojas", "ID": "", "Email": "naphtha@gmail.com"}, {"First name": "Jimmie", "Age": "60", "Gender": "male", "Last name": "Sweeney", "ID": "", "Email": "twiggier@gmail.com"}, {"First name": "Janelle", "Age": "42", "Gender": "female", "Last name": "Hale", "ID": "", "Email": "starving@hotmail.com"}, {"First name": "Adrian", "Age": "7", "Gender": "male", "Last name": "Kirkland", "ID": "", "Email": "airsickness@hotmail.com"}, {"First name": "Ivy", "Age": "55", "Gender": "female", "Last name": "Puckett", "ID": "", "Email": "debauchery@gmail.com"}, {"First name": "Anna", "Age": "58", "Gender": "female", "Last name": "Palmer", "ID": "", "Email": "seminole@hotmail.com"}, {"First name": "Hugh", "Age": "18", "Gender": "male", "Last name": "Alvarado", "ID": "", "Email": "gospel@gmail.com"}, {"First name": "Bridget", "Age": "67", "Gender": "female", "Last name": "Hendricks", "ID": "", "Email": "sarcomata@hotmail.com"}, {"First name": "Dan", "Age": "52", "Gender": "male", "Last name": "Young", "ID": "", "Email": "temptresses@gmail.com"}, {"First name": "Bradley", "Age": "8", "Gender": "male", "Last name": "Franklin", "ID": "", "Email": "refund@hotmail.com"}, {"First name": "Hallie", "Age": "3", "Gender": "female", "Last name": "Galloway", "ID": "", "Email": "emus@hotmail.com"}, {"First name": "Mitchell", "Age": "40", "Gender": "male", "Last name": "Velez", "ID": "", "Email": "adventurers@gmail.com"}, {"First name": "Rosa", "Age": "74", "Gender": "female", "Last name": "Cash", "ID": "", "Email": "refrigerators@hotmail.com"}, {"First name": "Sherrie", "Age": "6", "Gender": "female", "Last name": "Mann", "ID": "", "Email": "mamboing@hotmail.com"}, {"First name": "Marion", "Age": "62", "Gender": "male", "Last name": "Gibbs", "ID": "", "Email": "fraudulently@hotmail.com"}, {"First name": "Kerry", "Age": "76", "Gender": "female", "Last name": "Weaver", "ID": "", "Email": "fulminations@hotmail.com"}, {"First name": "Harold", "Age": "26", "Gender": "male", "Last name": "Johnson", "ID": "", "Email": "quitting@gmail.com"}, {"First name": "Marci", "Age": "65", "Gender": "female", "Last name": "Craft", "ID": "", "Email": "circulations@gmail.com"}, {"First name": "Robin", "Age": "22", "Gender": "female", "Last name": "Kim", "ID": "", "Email": "unskilled@hotmail.com"}, {"First name": "Juliana", "Age": "22", "Gender": "female", "Last name": "Barron", "ID": "", "Email": ""}, {"First name": "Sybil", "Age": "3", "Gender": "female", "Last name": "Ewing", "ID": "", "Email": "attacker@gmail.com"}, {"First name": "Vernon", "Age": "84", "Gender": "male", "Last name": "Case", "ID": "", "Email": "flannels@hotmail.com"}, {"First name": "Dwayne", "Age": "35", "Gender": "male", "Last name": "Adams", "ID": "", "Email": "procedural@gmail.com"}, {"First name": "Alexander", "Age": "21", "Gender": "male", "Last name": "Martinez", "ID": "", "Email": "trances@gmail.com"}, {"First name": "Ellen", "Age": "97", "Gender": "female", "Last name": "Strickland", "ID": "", "Email": "hardened@gmail.com"}, {"First name": "Esperanza", "Age": "9", "Gender": "female", "Last name": "Cash", "ID": "", "Email": "counterrevolutions@hotmail.com"}, {"First name": "Ofelia", "Age": "75", "Gender": "female", "Last name": "Hunt", "ID": "", "Email": "tracheas@hotmail.com"}, {"First name": "Steve", "Age": "66", "Gender": "male", "Last name": "Cunningham", "ID": "", "Email": ""}, {"First name": "Judith", "Age": "35", "Gender": "female", "Last name": "York", "ID": "", "Email": "elegies@hotmail.com"}, {"First name": "Edwin", "Age": "81", "Gender": "male", "Last name": "Mercer", "ID": "", "Email": "balded@hotmail.com"}, {"First name": "", "Age": "31", "Gender": "male", "Last name": "Macias", "ID": "", "Email": "belittles@hotmail.com"}, {"First name": "Marc", "Age": "17", "Gender": "male", "Last name": "Leon", "ID": "", "Email": "cranmer@gmail.com"}, {"First name": "Lewis", "Age": "11", "Gender": "male", "Last name": "Daniel", "ID": "", "Email": "fleshlier@hotmail.com"}, {"First name": "Eugene", "Age": "40", "Gender": "male", "Last name": "Perkins", "ID": "", "Email": "whores@gmail.com"}, {"First name": "James", "Age": "43", "Gender": "female", "Last name": "Rivas", "ID": "", "Email": "complainers@gmail.com"}, {"First name": "Lee", "Age": "24", "Gender": "male", "Last name": "Caldwell", "ID": "", "Email": "wholes@hotmail.com"}, {"First name": "Kristen", "Age": "9", "Gender": "female", "Last name": "Andrews", "ID": "", "Email": "pallets@hotmail.com"}, {"First name": "Corinne", "Age": "49", "Gender": "female", "Last name": "Hodge", "ID": "", "Email": "sulky@gmail.com"}, {"First name": "Luis", "Age": "8", "Gender": "male", "Last name": "Savage", "ID": "", "Email": "playgoer@gmail.com"}, {"First name": "Bianca", "Age": "60", "Gender": "female", "Last name": "Mcguire", "ID": "", "Email": "thinks@hotmail.com"}, {"First name": "Earlene", "Age": "90", "Gender": "female", "Last name": "Patterson", "ID": "", "Email": "foregone@gmail.com"}, {"First name": "Terry", "Age": "96", "Gender": "male", "Last name": "Gill", "ID": "", "Email": "vickies@gmail.com"}, {"First name": "Curtis", "Age": "97", "Gender": "male", "Last name": "Mcguire", "ID": "", "Email": "derivations@hotmail.com"}, {"First name": "Jamie", "Age": "24", "Gender": "male", "Last name": "Burton", "ID": "", "Email": "turpitude@gmail.com"}, {"First name": "Simone", "Age": "21", "Gender": "female", "Last name": "Cain", "ID": "", "Email": "levelers@gmail.com"}, {"First name": "Kyle", "Age": "43", "Gender": "male", "Last name": "Puckett", "ID": "", "Email": ""}, {"First name": "Eddie", "Age": "10", "Gender": "female", "Last name": "West", "ID": "", "Email": "unwind@hotmail.com"}, {"First name": "Neil", "Age": "45", "Gender": "male", "Last name": "Mcdaniel", "ID": "", "Email": "contorts@hotmail.com"}, {"First name": "Kerri", "Age": "64", "Gender": "female", "Last name": "Warner", "ID": "", "Email": "alfonso@hotmail.com"}, {"First name": "Micheal", "Age": "59", "Gender": "male", "Last name": "Sawyer", "ID": "", "Email": "treatable@gmail.com"}, {"First name": "Charlie", "Age": "47", "Gender": "male", "Last name": "Figueroa", "ID": "", "Email": "regulars@gmail.com"}, {"First name": "Randy", "Age": "7", "Gender": "male", "Last name": "Stark", "ID": "", "Email": "mannishly@hotmail.com"}, {"First name": "Howard", "Age": "78", "Gender": "male", "Last name": "Doyle", "ID": "", "Email": "tapes@gmail.com"}, {"First name": "Henry", "Age": "91", "Gender": "male", "Last name": "Riley", "ID": "", "Email": "interminably@hotmail.com"}, {"First name": "Lacey", "Age": "14", "Gender": "female", "Last name": "Franks", "ID": "", "Email": "hensleys@hotmail.com"}, {"First name": "Samantha", "Age": "60", "Gender": "female", "Last name": "Branch", "ID": "", "Email": "burnout@gmail.com"}, {"First name": "Lawrence", "Age": "73", "Gender": "male", "Last name": "Munoz", "ID": "", "Email": "repulsively@hotmail.com"}, {"First name": "Daniel", "Age": "25", "Gender": "male", "Last name": "Lewis", "ID": "", "Email": "ushered@hotmail.com"}, {"First name": "Benjamin", "Age": "61", "Gender": "male", "Last name": "Houston", "ID": "", "Email": "outbalancing@hotmail.com"}, {"First name": "Stephanie", "Age": "20", "Gender": "female", "Last name": "Brown", "ID": "", "Email": "tranquilest@gmail.com"}, {"First name": "Carlos", "Age": "23", "Gender": "male", "Last name": "Erickson", "ID": "", "Email": "salines@hotmail.com"}, {"First name": "Angela", "Age": "9", "Gender": "female", "Last name": "Rhodes", "ID": "", "Email": "chatterboxes@gmail.com"}, {"First name": "Nelson", "Age": "54", "Gender": "male", "Last name": "Love", "ID": "", "Email": "footfalls@gmail.com"}, {"First name": "Lorena", "Age": "70", "Gender": "female", "Last name": "Hobbs", "ID": "", "Email": "untimely@gmail.com"}, {"First name": "Armando", "Age": "91", "Gender": "male", "Last name": "Boyer", "ID": "", "Email": "joshua@gmail.com"}, {"First name": "Calvin", "Age": "86", "Gender": "male", "Last name": "Cross", "ID": "", "Email": "teaspoonsful@gmail.com"}, {"First name": "Penny", "Age": "90", "Gender": "female", "Last name": "Blevins", "ID": "", "Email": "strives@hotmail.com"}, {"First name": "Abigail", "Age": "63", "Gender": "female", "Last name": "Burke", "ID": "", "Email": "imbalances@gmail.com"}, {"First name": "Billy", "Age": "75", "Gender": "male", "Last name": "Gordon", "ID": "", "Email": "classmate@hotmail.com"}, {"First name": "Clyde", "Age": "12", "Gender": "male", "Last name": "Jacobson", "ID": "", "Email": "iteration@gmail.com"}, {"First name": "Alexandra", "Age": "4", "Gender": "female", "Last name": "Terry", "ID": "", "Email": "trustworthiness@hotmail.com"}, {"First name": "Pamela", "Age": "2", "Gender": "female", "Last name": "Hyde", "ID": "", "Email": "planck@gmail.com"}, {"First name": "Christian", "Age": "41", "Gender": "male", "Last name": "Powers", "ID": "", "Email": "microphones@hotmail.com"}, {"First name": "Leslie", "Age": "15", "Gender": "male", "Last name": "Odom", "ID": "", "Email": "expletives@gmail.com"}, {"First name": "Lessie", "Age": "77", "Gender": "", "Last name": "Melton", "ID": "", "Email": "disburses@gmail.com"}, {"First name": "Nellie", "Age": "63", "Gender": "female", "Last name": "Moon", "ID": "", "Email": "downfalls@gmail.com"}, {"First name": "Wallace", "Age": "67", "Gender": "male", "Last name": "Finley", "ID": "", "Email": "claires@gmail.com"}, {"First name": "Lacy", "Age": "16", "Gender": "female", "Last name": "Sampson", "ID": "", "Email": "bidders@hotmail.com"}, {"First name": "Kirk", "Age": "50", "Gender": "male", "Last name": "Kaufman", "ID": "", "Email": "least@hotmail.com"}, {"First name": "Jorge", "Age": "39", "Gender": "male", "Last name": "Farmer", "ID": "", "Email": "jitterbugged@hotmail.com"}, {"First name": "Frank", "Age": "68", "Gender": "male", "Last name": "Hall", "ID": "", "Email": "dogfight@hotmail.com"}, {"First name": "Leonor", "Age": "34", "Gender": "female", "Last name": "Mercado", "ID": "", "Email": "elbowing@hotmail.com"}, {"First name": "Alex", "Age": "38", "Gender": "male", "Last name": "Casey", "ID": "", "Email": "fulminated@gmail.com"}, {"First name": "Tommy", "Age": "38", "Gender": "male", "Last name": "Curry", "ID": "", "Email": "kennels@hotmail.com"}, {"First name": "Enrique", "Age": "11", "Gender": "male", "Last name": "Howe", "ID": "", "Email": "makes@hotmail.com"}, {"First name": "Mathew", "Age": "16", "Gender": "male", "Last name": "Mccall", "ID": "", "Email": "temperaments@gmail.com"}, {"First name": "Bonita", "Age": "85", "Gender": "", "Last name": "Weaver", "ID": "", "Email": "fanaticism@gmail.com"}, {"First name": "Brandie", "Age": "78", "Gender": "female", "Last name": "Turner", "ID": "", "Email": "herringbone@hotmail.com"}, {"First name": "Mathew", "Age": "68", "Gender": "male", "Last name": "Duran", "ID": "", "Email": ""}, {"First name": "Felix", "Age": "20", "Gender": "male", "Last name": "Robbins", "ID": "", "Email": "pipit@hotmail.com"}, {"First name": "Imogene", "Age": "70", "Gender": "female", "Last name": "Rosario", "ID": "", "Email": "monogramming@hotmail.com"}, {"First name": "Adam", "Age": "27", "Gender": "male", "Last name": "Hamilton", "ID": "", "Email": "mohairs@hotmail.com"}, {"First name": "Leon", "Age": "7", "Gender": "male", "Last name": "Navarro", "ID": "", "Email": "swung@hotmail.com"}, {"First name": "Todd", "Age": "63", "Gender": "male", "Last name": "Drake", "ID": "", "Email": ""}, {"First name": "Antonia", "Age": "45", "Gender": "female", "Last name": "Noel", "ID": "", "Email": "shadiness@gmail.com"}, {"First name": "Eddie", "Age": "14", "Gender": "female", "Last name": "Mayo", "ID": "", "Email": "levy@gmail.com"}, {"First name": "", "Age": "73", "Gender": "male", "Last name": "Stafford", "ID": "", "Email": "skydove@gmail.com"}, {"First name": "Kurt", "Age": "55", "Gender": "male", "Last name": "Lara", "ID": "", "Email": "vijayawadas@gmail.com"}, {"First name": "Chasity", "Age": "28", "Gender": "female", "Last name": "Barlow", "ID": "", "Email": "stipulated@gmail.com"}, {"First name": "Floyd", "Age": "41", "Gender": "male", "Last name": "Allen", "ID": "", "Email": "equivalent@hotmail.com"}, {"First name": "Jose", "Age": "90", "Gender": "male", "Last name": "Hodges", "ID": "", "Email": "initials@gmail.com"}, {"First name": "Alison", "Age": "92", "Gender": "female", "Last name": "Phelps", "ID": "", "Email": "donkey@hotmail.com"}, {"First name": "Madeline", "Age": "19", "Gender": "female", "Last name": "Maxwell", "ID": "", "Email": "snafus@hotmail.com"}, {"First name": "Kyle", "Age": "37", "Gender": "male", "Last name": "Osborn", "ID": "", "Email": "ch\u00e2teau@gmail.com"}, {"First name": "Jennifer", "Age": "83", "Gender": "female", "Last name": "Lawson", "ID": "", "Email": "whiskers@gmail.com"}, {"First name": "Dwayne", "Age": "73", "Gender": "male", "Last name": "Tucker", "ID": "", "Email": "undetected@gmail.com"}, {"First name": "Jolene", "Age": "52", "Gender": "female", "Last name": "Clemons", "ID": "", "Email": "artwork@gmail.com"}, {"First name": "Fred", "Age": "58", "Gender": "male", "Last name": "Kline", "ID": "", "Email": "plaudit@gmail.com"}, {"First name": "Ted", "Age": "44", "Gender": "male", "Last name": "", "ID": "", "Email": "verisimilitude@gmail.com"}, {"First name": "Jeanne", "Age": "46", "Gender": "female", "Last name": "Wyatt", "ID": "", "Email": "submerging@hotmail.com"}, {"First name": "Martha", "Age": "69", "Gender": "female", "Last name": "Coffey", "ID": "", "Email": "mullahs@gmail.com"}, {"First name": "", "Age": "62", "Gender": "female", "Last name": "Paul", "ID": "", "Email": "souvenirs@gmail.com"}, {"First name": "Danny", "Age": "60", "Gender": "male", "Last name": "Forbes", "ID": "", "Email": "blackheads@gmail.com"}, {"First name": "Jimmie", "Age": "53", "Gender": "female", "Last name": "Holman", "ID": "", "Email": "whirls@hotmail.com"}, {"First name": "Alex", "Age": "37", "Gender": "male", "Last name": "Armstrong", "ID": "", "Email": "mantras@gmail.com"}, {"First name": "Felix", "Age": "60", "Gender": "male", "Last name": "Bowman", "ID": "", "Email": "coinciding@hotmail.com"}, {"First name": "Alexandria", "Age": "39", "Gender": "female", "Last name": "Soto", "ID": "", "Email": "mono@gmail.com"}, {"First name": "Ron", "Age": "6", "Gender": "male", "Last name": "Cooke", "ID": "", "Email": "illustrious@gmail.com"}, {"First name": "Clinton", "Age": "18", "Gender": "male", "Last name": "Burton", "ID": "", "Email": "retract@hotmail.com"}, {"First name": "Leslie", "Age": "1", "Gender": "male", "Last name": "Knowles", "ID": "", "Email": "vietnams@gmail.com"}, {"First name": "Douglas", "Age": "37", "Gender": "male", "Last name": "Dodson", "ID": "", "Email": "snowboards@hotmail.com"}, {"First name": "Erik", "Age": "3", "Gender": "male", "Last name": "Middleton", "ID": "", "Email": "beauregard@hotmail.com"}, {"First name": "Sonja", "Age": "32", "Gender": "female", "Last name": "Mcmillan", "ID": "", "Email": "kibitzes@gmail.com"}, {"First name": "Sidney", "Age": "71", "Gender": "male", "Last name": "Adams", "ID": "", "Email": "m\u00e9tiers@hotmail.com"}, {"First name": "Terri", "Age": "88", "Gender": "female", "Last name": "Hendricks", "ID": "", "Email": ""}, {"First name": "Jo", "Age": "99", "Gender": "female", "Last name": "Ramsey", "ID": "", "Email": "chirped@gmail.com"}, {"First name": "Diane", "Age": "80", "Gender": "female", "Last name": "Daniel", "ID": "", "Email": "senioritys@hotmail.com"}, {"First name": "Amber", "Age": "63", "Gender": "female", "Last name": "Dodson", "ID": "", "Email": "admirals@hotmail.com"}, {"First name": "Kara", "Age": "67", "Gender": "female", "Last name": "Hinton", "ID": "", "Email": "connive@gmail.com"}, {"First name": "Randy", "Age": "56", "Gender": "male", "Last name": "Beach", "ID": "", "Email": "almachs@gmail.com"}, {"First name": "Iris", "Age": "18", "Gender": "female", "Last name": "Dillon", "ID": "", "Email": "kipper@gmail.com"}, {"First name": "Jeannie", "Age": "34", "Gender": "female", "Last name": "Mckay", "ID": "", "Email": "spartans@gmail.com"}, {"First name": "Herman", "Age": "94", "Gender": "male", "Last name": "Kemp", "ID": "", "Email": "kiddo@hotmail.com"}, {"First name": "Terry", "Age": "36", "Gender": "female", "Last name": "Stuart", "ID": "", "Email": "gantlets@hotmail.com"}, {"First name": "Myrtle", "Age": "56", "Gender": "female", "Last name": "Hurst", "ID": "", "Email": "candidness@gmail.com"}, {"First name": "Alvin", "Age": "78", "Gender": "male", "Last name": "Ratliff", "ID": "", "Email": "grumpiest@gmail.com"}, {"First name": "Wanda", "Age": "86", "Gender": "female", "Last name": "Bass", "ID": "", "Email": "motormouths@gmail.com"}, {"First name": "Herman", "Age": "65", "Gender": "", "Last name": "Joseph", "ID": "", "Email": "hed@hotmail.com"}, {"First name": "Liliana", "Age": "53", "Gender": "female", "Last name": "Chang", "ID": "", "Email": ""}, {"First name": "Sarah", "Age": "91", "Gender": "female", "Last name": "Stevens", "ID": "", "Email": "gadded@hotmail.com"}, {"First name": "Natasha", "Age": "38", "Gender": "female", "Last name": "Hooper", "ID": "", "Email": "canberras@gmail.com"}, {"First name": "Gabriel", "Age": "75", "Gender": "male", "Last name": "Nguyen", "ID": "", "Email": "groping@gmail.com"}, {"First name": "Belinda", "Age": "91", "Gender": "female", "Last name": "Hart", "ID": "", "Email": "playhouses@gmail.com"}, {"First name": "Wallace", "Age": "97", "Gender": "male", "Last name": "Pace", "ID": "", "Email": "urinating@gmail.com"}, {"First name": "Bernice", "Age": "86", "Gender": "female", "Last name": "Armstrong", "ID": "", "Email": "reveries@gmail.com"}, {"First name": "Heather", "Age": "35", "Gender": "female", "Last name": "Estrada", "ID": "", "Email": "vertically@gmail.com"}, {"First name": "Carmella", "Age": "7", "Gender": "female", "Last name": "Justice", "ID": "", "Email": "jingoism@gmail.com"}, {"First name": "Lavonne", "Age": "21", "Gender": "female", "Last name": "Calderon", "ID": "", "Email": "aesthetics@gmail.com"}, {"First name": "Shawn", "Age": "59", "Gender": "male", "Last name": "Matthews", "ID": "", "Email": "beddings@gmail.com"}, {"First name": "Mara", "Age": "53", "Gender": "female", "Last name": "Davenport", "ID": "", "Email": "itinerant@gmail.com"}, {"First name": "Latasha", "Age": "90", "Gender": "female", "Last name": "Spears", "ID": "", "Email": "reimposes@gmail.com"}, {"First name": "Christopher", "Age": "39", "Gender": "male", "Last name": "Nichols", "ID": "", "Email": "berried@gmail.com"}, {"First name": "Lindsay", "Age": "11", "Gender": "female", "Last name": "Lewis", "ID": "", "Email": "pimpernels@gmail.com"}, {"First name": "Jim", "Age": "65", "Gender": "male", "Last name": "Fuller", "ID": "", "Email": "beheld@gmail.com"}, {"First name": "Bobby", "Age": "76", "Gender": "male", "Last name": "Giles", "ID": "", "Email": "eyelashes@hotmail.com"}, {"First name": "Terry", "Age": "91", "Gender": "female", "Last name": "Dalton", "ID": "", "Email": "maces@hotmail.com"}, {"First name": "Millicent", "Age": "40", "Gender": "female", "Last name": "Wilcox", "ID": "", "Email": "chivalrous@hotmail.com"}, {"First name": "Lana", "Age": "82", "Gender": "female", "Last name": "Mclean", "ID": "", "Email": "dodders@hotmail.com"}, {"First name": "Stacie", "Age": "61", "Gender": "female", "Last name": "Cardenas", "ID": "", "Email": "complemented@gmail.com"}, {"First name": "Jayne", "Age": "22", "Gender": "female", "Last name": "Lamb", "ID": "", "Email": "landladys@gmail.com"}, {"First name": "Rafael", "Age": "27", "Gender": "", "Last name": "Perkins", "ID": "", "Email": "warrior@hotmail.com"}, {"First name": "Michael", "Age": "20", "Gender": "male", "Last name": "Newton", "ID": "", "Email": "comings@gmail.com"}, {"First name": "Virgie", "Age": "66", "Gender": "female", "Last name": "Vasquez", "ID": "", "Email": "prettinesss@gmail.com"}, {"First name": "Agnes", "Age": "7", "Gender": "female", "Last name": "Bolton", "ID": "", "Email": "dissects@hotmail.com"}, {"First name": "Rowena", "Age": "75", "Gender": "female", "Last name": "Sosa", "ID": "", "Email": "ebbed@hotmail.com"}, {"First name": "Alba", "Age": "76", "Gender": "female", "Last name": "Lowery", "ID": "", "Email": "fires@hotmail.com"}, {"First name": "Elva", "Age": "93", "Gender": "female", "Last name": "Beck", "ID": "", "Email": "disembodies@hotmail.com"}, {"First name": "Joan", "Age": "67", "Gender": "female", "Last name": "Morin", "ID": "", "Email": "nailbrushs@gmail.com"}, {"First name": "Elena", "Age": "43", "Gender": "female", "Last name": "Sargent", "ID": "", "Email": "insomniac@gmail.com"}, {"First name": "Velma", "Age": "41", "Gender": "female", "Last name": "Valentine", "ID": "", "Email": "creeper@gmail.com"}, {"First name": "Betty", "Age": "19", "Gender": "female", "Last name": "Frost", "ID": "", "Email": "motivation@hotmail.com"}, {"First name": "Ian", "Age": "33", "Gender": "male", "Last name": "Nunez", "ID": "", "Email": ""}, {"First name": "Glenda", "Age": "75", "Gender": "female", "Last name": "Rowe", "ID": "", "Email": "computations@hotmail.com"}, {"First name": "Jose", "Age": "21", "Gender": "male", "Last name": "Hale", "ID": "", "Email": "single@gmail.com"}, {"First name": "Daniel", "Age": "61", "Gender": "male", "Last name": "Stein", "ID": "", "Email": "extemporaneous@gmail.com"}, {"First name": "Margie", "Age": "12", "Gender": "female", "Last name": "Gates", "ID": "", "Email": "gauze@hotmail.com"}, {"First name": "Sergio", "Age": "57", "Gender": "male", "Last name": "Cruz", "ID": "", "Email": "rugs@gmail.com"}, {"First name": "Shari", "Age": "16", "Gender": "female", "Last name": "Diaz", "ID": "", "Email": "proffer@hotmail.com"}, {"First name": "Darren", "Age": "38", "Gender": "male", "Last name": "Cobb", "ID": "", "Email": "laxer@hotmail.com"}, {"First name": "Mai", "Age": "48", "Gender": "female", "Last name": "Jarvis", "ID": "", "Email": "conspiracies@hotmail.com"}, {"First name": "", "Age": "31", "Gender": "male", "Last name": "Clemons", "ID": "", "Email": "desdemona@hotmail.com"}, {"First name": "", "Age": "31", "Gender": "female", "Last name": "Solomon", "ID": "", "Email": "unfairness@hotmail.com"}, {"First name": "Claude", "Age": "89", "Gender": "male", "Last name": "Pruitt", "ID": "", "Email": "slumbers@hotmail.com"}, {"First name": "Doris", "Age": "36", "Gender": "female", "Last name": "Mcmillan", "ID": "", "Email": "ashen@gmail.com"}, {"First name": "", "Age": "45", "Gender": "female", "Last name": "Campos", "ID": "", "Email": "contemporaneous@hotmail.com"}, {"First name": "Kirk", "Age": "35", "Gender": "male", "Last name": "Morrow", "ID": "", "Email": "brigantines@gmail.com"}, {"First name": "Erna", "Age": "51", "Gender": "female", "Last name": "Craft", "ID": "", "Email": "hashing@hotmail.com"}, {"First name": "Jimmy", "Age": "5", "Gender": "male", "Last name": "Kirby", "ID": "", "Email": "slackens@hotmail.com"}, {"First name": "Jonathan", "Age": "28", "Gender": "male", "Last name": "Cash", "ID": "", "Email": "jackknives@hotmail.com"}, {"First name": "Eric", "Age": "6", "Gender": "male", "Last name": "Merritt", "ID": "", "Email": "behalfs@gmail.com"}, {"First name": "Rosalinda", "Age": "14", "Gender": "female", "Last name": "Owen", "ID": "", "Email": "serendipitous@gmail.com"}, {"First name": "Gabrielle", "Age": "89", "Gender": "female", "Last name": "Kim", "ID": "", "Email": "lawfulnesss@gmail.com"}, {"First name": "Angela", "Age": "86", "Gender": "", "Last name": "Summers", "ID": "", "Email": "rebuild@gmail.com"}, {"First name": "Dale", "Age": "67", "Gender": "male", "Last name": "Jefferson", "ID": "", "Email": "stickups@gmail.com"}, {"First name": "Lynne", "Age": "68", "Gender": "female", "Last name": "Zamora", "ID": "", "Email": "cheapskate@gmail.com"}, {"First name": "Kyle", "Age": "67", "Gender": "male", "Last name": "Valencia", "ID": "", "Email": "ancestral@hotmail.com"}, {"First name": "Shawn", "Age": "11", "Gender": "male", "Last name": "Contreras", "ID": "", "Email": "finch@gmail.com"}, {"First name": "Jon", "Age": "57", "Gender": "male", "Last name": "Riggs", "ID": "", "Email": "estuarys@hotmail.com"}, {"First name": "Harvey", "Age": "97", "Gender": "male", "Last name": "Holman", "ID": "", "Email": "straights@hotmail.com"}, {"First name": "James", "Age": "32", "Gender": "male", "Last name": "Lowery", "ID": "", "Email": "jaywalk@hotmail.com"}, {"First name": "", "Age": "82", "Gender": "male", "Last name": "Bass", "ID": "", "Email": "nipples@gmail.com"}, {"First name": "Rose", "Age": "12", "Gender": "female", "Last name": "Horne", "ID": "", "Email": "flatfish@hotmail.com"}, {"First name": "Mabel", "Age": "65", "Gender": "female", "Last name": "Mccray", "ID": "", "Email": "bjorks@hotmail.com"}, {"First name": "", "Age": "24", "Gender": "male", "Last name": "Dickson", "ID": "", "Email": "epitomizes@hotmail.com"}, {"First name": "Isaac", "Age": "73", "Gender": "male", "Last name": "Reid", "ID": "", "Email": "flair@gmail.com"}, {"First name": "Cassie", "Age": "4", "Gender": "female", "Last name": "Hubbard", "ID": "", "Email": "karina@gmail.com"}, {"First name": "Ken", "Age": "75", "Gender": "male", "Last name": "Dennis", "ID": "", "Email": "konrad@gmail.com"}, {"First name": "Jana", "Age": "69", "Gender": "female", "Last name": "Powers", "ID": "", "Email": "margarita@gmail.com"}, {"First name": "Vivian", "Age": "75", "Gender": "female", "Last name": "Clements", "ID": "", "Email": "chandras@hotmail.com"}, {"First name": "Socorro", "Age": "29", "Gender": "female", "Last name": "Hickman", "ID": "", "Email": "blinds@hotmail.com"}, {"First name": "Tammi", "Age": "72", "Gender": "", "Last name": "Waters", "ID": "", "Email": "ills@hotmail.com"}, {"First name": "Virgil", "Age": "38", "Gender": "male", "Last name": "Mcintyre", "ID": "", "Email": "burglarized@hotmail.com"}, {"First name": "Glen", "Age": "66", "Gender": "male", "Last name": "Dudley", "ID": "", "Email": "mahogany@gmail.com"}, {"First name": "Amparo", "Age": "8", "Gender": "female", "Last name": "Sykes", "ID": "", "Email": "circuiting@gmail.com"}, {"First name": "Sonja", "Age": "86", "Gender": "female", "Last name": "Waller", "ID": "", "Email": "crocodiles@gmail.com"}, {"First name": "Steve", "Age": "76", "Gender": "male", "Last name": "Burks", "ID": "", "Email": "marquezs@hotmail.com"}, {"First name": "Loretta", "Age": "0", "Gender": "female", "Last name": "Perez", "ID": "", "Email": "ravens@hotmail.com"}, {"First name": "Jacklyn", "Age": "15", "Gender": "", "Last name": "Harper", "ID": "", "Email": "desperado@hotmail.com"}, {"First name": "Teri", "Age": "83", "Gender": "female", "Last name": "Cooke", "ID": "", "Email": "typography@gmail.com"}, {"First name": "Susanna", "Age": "74", "Gender": "female", "Last name": "Berger", "ID": "", "Email": "corralled@hotmail.com"}, {"First name": "Nellie", "Age": "81", "Gender": "female", "Last name": "Cantrell", "ID": "", "Email": "ms@gmail.com"}, {"First name": "Glen", "Age": "70", "Gender": "male", "Last name": "Weeks", "ID": "", "Email": "caduceus@gmail.com"}, {"First name": "Ross", "Age": "19", "Gender": "male", "Last name": "Torres", "ID": "", "Email": "margret@gmail.com"}, {"First name": "Byron", "Age": "17", "Gender": "male", "Last name": "Gallegos", "ID": "", "Email": "imperiously@gmail.com"}, {"First name": "Harvey", "Age": "6", "Gender": "male", "Last name": "Bryan", "ID": "", "Email": "tapping@gmail.com"}, {"First name": "Mamie", "Age": "93", "Gender": "female", "Last name": "Nguyen", "ID": "", "Email": "scissors@hotmail.com"}, {"First name": "", "Age": "87", "Gender": "female", "Last name": "Williamson", "ID": "", "Email": "thoughtfully@hotmail.com"}, {"First name": "Mario", "Age": "33", "Gender": "male", "Last name": "Black", "ID": "", "Email": ""}, {"First name": "Edgar", "Age": "13", "Gender": "male", "Last name": "Kramer", "ID": "", "Email": "lilacs@hotmail.com"}, {"First name": "Barry", "Age": "51", "Gender": "male", "Last name": "Roth", "ID": "", "Email": "cognate@hotmail.com"}, {"First name": "Marvin", "Age": "44", "Gender": "male", "Last name": "Cobb", "ID": "", "Email": "pickups@gmail.com"}, {"First name": "Ophelia", "Age": "4", "Gender": "", "Last name": "Delgado", "ID": "", "Email": "pregnant@gmail.com"}, {"First name": "Angeline", "Age": "91", "Gender": "female", "Last name": "Guzman", "ID": "", "Email": "bagpipes@gmail.com"}, {"First name": "Berta", "Age": "45", "Gender": "female", "Last name": "Salinas", "ID": "", "Email": "golds@gmail.com"}, {"First name": "Felecia", "Age": "30", "Gender": "female", "Last name": "Foster", "ID": "", "Email": "outrun@hotmail.com"}, {"First name": "Jack", "Age": "53", "Gender": "male", "Last name": "French", "ID": "", "Email": "disobeyed@gmail.com"}, {"First name": "Willa", "Age": "14", "Gender": "female", "Last name": "Gillespie", "ID": "", "Email": "viscounts@hotmail.com"}, {"First name": "Michael", "Age": "53", "Gender": "male", "Last name": "Frank", "ID": "", "Email": "chaparral@hotmail.com"}, {"First name": "Zachary", "Age": "69", "Gender": "male", "Last name": "Sawyer", "ID": "", "Email": "kingdoms@gmail.com"}, {"First name": "Kaitlin", "Age": "64", "Gender": "female", "Last name": "Taylor", "ID": "", "Email": ""}, {"First name": "Zachary", "Age": "64", "Gender": "male", "Last name": "Raymond", "ID": "", "Email": "coasting@hotmail.com"}, {"First name": "", "Age": "13", "Gender": "female", "Last name": "Taylor", "ID": "", "Email": "tried@hotmail.com"}, {"First name": "Rosalind", "Age": "13", "Gender": "female", "Last name": "Greer", "ID": "", "Email": "discoverys@hotmail.com"}, {"First name": "Mitchell", "Age": "57", "Gender": "male", "Last name": "Swanson", "ID": "", "Email": "prut@hotmail.com"}, {"First name": "Lonnie", "Age": "75", "Gender": "male", "Last name": "Beck", "ID": "", "Email": "etymologist@hotmail.com"}, {"First name": "Gilbert", "Age": "87", "Gender": "male", "Last name": "Fitzgerald", "ID": "", "Email": "peevishnesss@gmail.com"}, {"First name": "Lilian", "Age": "55", "Gender": "female", "Last name": "Guy", "ID": "", "Email": "rightfulnesss@gmail.com"}, {"First name": "Kristina", "Age": "88", "Gender": "female", "Last name": "Oneil", "ID": "", "Email": "pops@gmail.com"}, {"First name": "Angel", "Age": "64", "Gender": "male", "Last name": "Casey", "ID": "", "Email": "identification@gmail.com"}, {"First name": "Shawn", "Age": "64", "Gender": "female", "Last name": "Mckinney", "ID": "", "Email": "phoneticss@gmail.com"}, {"First name": "Franklin", "Age": "30", "Gender": "male", "Last name": "Dennis", "ID": "", "Email": "squatters@gmail.com"}, {"First name": "Rebecca", "Age": "22", "Gender": "female", "Last name": "Moses", "ID": "", "Email": "hosts@gmail.com"}, {"First name": "Armando", "Age": "33", "Gender": "male", "Last name": "Foley", "ID": "", "Email": "crotchets@gmail.com"}, {"First name": "Isabella", "Age": "57", "Gender": "female", "Last name": "Contreras", "ID": "", "Email": "shamans@gmail.com"}, {"First name": "Angelita", "Age": "95", "Gender": "female", "Last name": "Carpenter", "ID": "", "Email": ""}, {"First name": "Tracy", "Age": "46", "Gender": "female", "Last name": "Estes", "ID": "", "Email": "flying@gmail.com"}, {"First name": "Lisa", "Age": "43", "Gender": "female", "Last name": "Frederick", "ID": "", "Email": "cannier@hotmail.com"}, {"First name": "Henry", "Age": "9", "Gender": "male", "Last name": "Mejia", "ID": "", "Email": "illnesss@hotmail.com"}, {"First name": "Addie", "Age": "43", "Gender": "", "Last name": "Hays", "ID": "", "Email": "loci@hotmail.com"}, {"First name": "Micheal", "Age": "6", "Gender": "male", "Last name": "Wiggins", "ID": "", "Email": "benefactors@gmail.com"}, {"First name": "Morris", "Age": "10", "Gender": "male", "Last name": "Adkins", "ID": "", "Email": "bludgeons@hotmail.com"}, {"First name": "Georgina", "Age": "1", "Gender": "", "Last name": "Mcpherson", "ID": "", "Email": "wellands@gmail.com"}, {"First name": "Mariana", "Age": "10", "Gender": "female", "Last name": "Case", "ID": "", "Email": ""}, {"First name": "Nadia", "Age": "10", "Gender": "female", "Last name": "Trujillo", "ID": "", "Email": "unstopped@gmail.com"}, {"First name": "Kyle", "Age": "92", "Gender": "male", "Last name": "Molina", "ID": "", "Email": "percolations@hotmail.com"}, {"First name": "", "Age": "45", "Gender": "", "Last name": "Mclaughlin", "ID": "", "Email": "wilkersons@gmail.com"}, {"First name": "Benjamin", "Age": "52", "Gender": "male", "Last name": "Rivers", "ID": "", "Email": "catalyzes@hotmail.com"}, {"First name": "Doris", "Age": "76", "Gender": "female", "Last name": "Moreno", "ID": "", "Email": "acreages@gmail.com"}, {"First name": "Brittany", "Age": "50", "Gender": "female", "Last name": "Mullen", "ID": "", "Email": "geysers@gmail.com"}, {"First name": "Peter", "Age": "81", "Gender": "", "Last name": "Barber", "ID": "", "Email": "certifications@hotmail.com"}, {"First name": "Bruce", "Age": "53", "Gender": "male", "Last name": "Olsen", "ID": "", "Email": "gybing@hotmail.com"}, {"First name": "Marissa", "Age": "2", "Gender": "female", "Last name": "Summers", "ID": "", "Email": "cheep@hotmail.com"}, {"First name": "Annette", "Age": "2", "Gender": "female", "Last name": "Brock", "ID": "", "Email": "disavowals@gmail.com"}, {"First name": "Dwayne", "Age": "25", "Gender": "male", "Last name": "Daugherty", "ID": "", "Email": "upturning@gmail.com"}, {"First name": "Ramon", "Age": "60", "Gender": "male", "Last name": "Wiley", "ID": "", "Email": "retributions@gmail.com"}, {"First name": "Salvador", "Age": "92", "Gender": "male", "Last name": "Gilmore", "ID": "", "Email": "clunky@gmail.com"}, {"First name": "Phillip", "Age": "37", "Gender": "male", "Last name": "Reynolds", "ID": "", "Email": "honorary@hotmail.com"}, {"First name": "Madelyn", "Age": "27", "Gender": "female", "Last name": "Martin", "ID": "", "Email": "paran\u00e1@gmail.com"}, {"First name": "", "Age": "22", "Gender": "male", "Last name": "Buchanan", "ID": "", "Email": "rushmore@gmail.com"}, {"First name": "Sheri", "Age": "4", "Gender": "female", "Last name": "Montgomery", "ID": "", "Email": "stipple@hotmail.com"}, {"First name": "Vernon", "Age": "65", "Gender": "male", "Last name": "Compton", "ID": "", "Email": "observances@hotmail.com"}, {"First name": "Natasha", "Age": "0", "Gender": "female", "Last name": "Stanton", "ID": "", "Email": "reservists@hotmail.com"}, {"First name": "Ralph", "Age": "24", "Gender": "male", "Last name": "Valentine", "ID": "", "Email": "travestied@hotmail.com"}, {"First name": "Lourdes", "Age": "5", "Gender": "female", "Last name": "Cunningham", "ID": "", "Email": "spinnaker@gmail.com"}, {"First name": "Julio", "Age": "9", "Gender": "male", "Last name": "Herrera", "ID": "", "Email": "hassle@gmail.com"}, {"First name": "Jasmine", "Age": "48", "Gender": "female", "Last name": "Hughes", "ID": "", "Email": "cosmogonys@hotmail.com"}, {"First name": "Helena", "Age": "88", "Gender": "female", "Last name": "Hood", "ID": "", "Email": "leviathans@hotmail.com"}, {"First name": "Sonja", "Age": "41", "Gender": "", "Last name": "Andrews", "ID": "", "Email": ""}, {"First name": "Shannon", "Age": "0", "Gender": "female", "Last name": "Goodwin", "ID": "", "Email": ""}, {"First name": "Rene", "Age": "90", "Gender": "male", "Last name": "Russo", "ID": "", "Email": "palindromes@hotmail.com"}, {"First name": "Jay", "Age": "81", "Gender": "", "Last name": "Riley", "ID": "", "Email": "chattels@gmail.com"}, {"First name": "Alan", "Age": "65", "Gender": "male", "Last name": "Luna", "ID": "", "Email": "bosn@hotmail.com"}, {"First name": "Cara", "Age": "8", "Gender": "female", "Last name": "Lopez", "ID": "", "Email": "pantheists@hotmail.com"}, {"First name": "Isabella", "Age": "76", "Gender": "", "Last name": "Rojas", "ID": "", "Email": "torahs@hotmail.com"}, {"First name": "Beverley", "Age": "35", "Gender": "female", "Last name": "Mejia", "ID": "", "Email": "gambolling@gmail.com"}, {"First name": "Bobby", "Age": "43", "Gender": "male", "Last name": "Delacruz", "ID": "", "Email": "histrionicss@hotmail.com"}, {"First name": "Kristin", "Age": "47", "Gender": "female", "Last name": "Odom", "ID": "", "Email": "platoons@gmail.com"}, {"First name": "Tammi", "Age": "20", "Gender": "female", "Last name": "Waters", "ID": "", "Email": "vincent@hotmail.com"}, {"First name": "Shelby", "Age": "13", "Gender": "female", "Last name": "Myers", "ID": "", "Email": "prosecution@hotmail.com"}, {"First name": "Eduardo", "Age": "46", "Gender": "male", "Last name": "Wolf", "ID": "", "Email": "mademoiselles@gmail.com"}, {"First name": "Johnnie", "Age": "43", "Gender": "male", "Last name": "Ware", "ID": "", "Email": "livestock@hotmail.com"}, {"First name": "Reyna", "Age": "32", "Gender": "female", "Last name": "Miles", "ID": "", "Email": "expanses@gmail.com"}, {"First name": "Don", "Age": "19", "Gender": "male", "Last name": "Blanchard", "ID": "", "Email": "eminems@hotmail.com"}, {"First name": "Leigh", "Age": "53", "Gender": "female", "Last name": "Key", "ID": "", "Email": "consomm\u00e9s@hotmail.com"}, {"First name": "Rodney", "Age": "8", "Gender": "male", "Last name": "Kerr", "ID": "", "Email": ""}, {"First name": "Teresa", "Age": "91", "Gender": "female", "Last name": "Jacobs", "ID": "", "Email": "crape@hotmail.com"}, {"First name": "Meagan", "Age": "68", "Gender": "female", "Last name": "Vargas", "ID": "", "Email": "handicraft@hotmail.com"}, {"First name": "Imelda", "Age": "52", "Gender": "female", "Last name": "Cochran", "ID": "", "Email": "unescos@gmail.com"}, {"First name": "Perry", "Age": "2", "Gender": "male", "Last name": "Weeks", "ID": "", "Email": "triplicate@hotmail.com"}, {"First name": "Aida", "Age": "63", "Gender": "female", "Last name": "Castaneda", "ID": "", "Email": "assortment@gmail.com"}, {"First name": "Corey", "Age": "44", "Gender": "male", "Last name": "Bates", "ID": "", "Email": "starvation@gmail.com"}, {"First name": "Wanda", "Age": "12", "Gender": "female", "Last name": "Hammond", "ID": "", "Email": "bushelling@hotmail.com"}, {"First name": "Reginald", "Age": "4", "Gender": "", "Last name": "Talley", "ID": "", "Email": "charles@gmail.com"}, {"First name": "Clarice", "Age": "76", "Gender": "female", "Last name": "Wilkerson", "ID": "", "Email": "clifford@hotmail.com"}, {"First name": "Juan", "Age": "58", "Gender": "male", "Last name": "Glover", "ID": "", "Email": "seismically@hotmail.com"}, {"First name": "Salvador", "Age": "29", "Gender": "male", "Last name": "Diaz", "ID": "", "Email": "poled@gmail.com"}, {"First name": "Lisa", "Age": "74", "Gender": "female", "Last name": "Lyons", "ID": "", "Email": "distinguish@gmail.com"}, {"First name": "Rosalinda", "Age": "60", "Gender": "female", "Last name": "Dale", "ID": "", "Email": "acquisitiveness@hotmail.com"}, {"First name": "Jose", "Age": "72", "Gender": "male", "Last name": "Nichols", "ID": "", "Email": "durations@hotmail.com"}, {"First name": "Dollie", "Age": "91", "Gender": "female", "Last name": "Blackburn", "ID": "", "Email": "bait@gmail.com"}, {"First name": "Patrick", "Age": "31", "Gender": "", "Last name": "Melton", "ID": "", "Email": "franck@hotmail.com"}, {"First name": "Faith", "Age": "24", "Gender": "female", "Last name": "Little", "ID": "", "Email": "palmettos@gmail.com"}, {"First name": "Kari", "Age": "49", "Gender": "", "Last name": "Boyer", "ID": "", "Email": "bloggers@hotmail.com"}, {"First name": "Stephen", "Age": "33", "Gender": "male", "Last name": "Phelps", "ID": "", "Email": "senates@gmail.com"}, {"First name": "Vera", "Age": "84", "Gender": "female", "Last name": "Bonner", "ID": "", "Email": "ducats@hotmail.com"}, {"First name": "Perry", "Age": "86", "Gender": "male", "Last name": "Mcguire", "ID": "", "Email": "mott@gmail.com"}, {"First name": "Effie", "Age": "55", "Gender": "female", "Last name": "Cohen", "ID": "", "Email": "arenas@hotmail.com"}, {"First name": "Bob", "Age": "88", "Gender": "", "Last name": "Jennings", "ID": "", "Email": "stick@hotmail.com"}, {"First name": "Francine", "Age": "40", "Gender": "female", "Last name": "Walter", "ID": "", "Email": "murderous@hotmail.com"}, {"First name": "Terri", "Age": "99", "Gender": "female", "Last name": "Olson", "ID": "", "Email": "homeopathic@hotmail.com"}, {"First name": "Alvin", "Age": "57", "Gender": "male", "Last name": "Davidson", "ID": "", "Email": "undermines@hotmail.com"}, {"First name": "Manuel", "Age": "26", "Gender": "male", "Last name": "Glenn", "ID": "", "Email": "fastenings@gmail.com"}, {"First name": "Ian", "Age": "56", "Gender": "male", "Last name": "Roman", "ID": "", "Email": "ivs@hotmail.com"}, {"First name": "Troy", "Age": "58", "Gender": "male", "Last name": "Diaz", "ID": "", "Email": "ganders@hotmail.com"}, {"First name": "Connie", "Age": "9", "Gender": "female", "Last name": "Pennington", "ID": "", "Email": "ordures@hotmail.com"}, {"First name": "Celina", "Age": "9", "Gender": "female", "Last name": "Henderson", "ID": "", "Email": "iphone@gmail.com"}, {"First name": "Jewel", "Age": "57", "Gender": "female", "Last name": "Wiggins", "ID": "", "Email": "crustiest@gmail.com"}, {"First name": "Richard", "Age": "20", "Gender": "male", "Last name": "Garrison", "ID": "", "Email": "preordains@hotmail.com"}, {"First name": "Lorrie", "Age": "64", "Gender": "female", "Last name": "Mann", "ID": "", "Email": "woodlands@hotmail.com"}, {"First name": "Andre", "Age": "69", "Gender": "male", "Last name": "Weiss", "ID": "", "Email": "crabbier@gmail.com"}, {"First name": "Bob", "Age": "73", "Gender": "male", "Last name": "Weber", "ID": "", "Email": ""}, {"First name": "Everett", "Age": "78", "Gender": "male", "Last name": "Burnett", "ID": "", "Email": "sorbonnes@gmail.com"}, {"First name": "Claudia", "Age": "2", "Gender": "female", "Last name": "Thomas", "ID": "", "Email": "variegating@gmail.com"}, {"First name": "Richard", "Age": "49", "Gender": "male", "Last name": "Sampson", "ID": "", "Email": "bonnier@gmail.com"}, {"First name": "Muriel", "Age": "12", "Gender": "female", "Last name": "Cash", "ID": "", "Email": "p\u00e9tain@gmail.com"}, {"First name": "Clyde", "Age": "95", "Gender": "male", "Last name": "Meyer", "ID": "", "Email": "emulator@gmail.com"}, {"First name": "Lauri", "Age": "69", "Gender": "female", "Last name": "Vargas", "ID": "", "Email": "finenesss@hotmail.com"}, {"First name": "Byron", "Age": "65", "Gender": "male", "Last name": "Neal", "ID": "", "Email": "rulings@gmail.com"}, {"First name": "Vonda", "Age": "29", "Gender": "female", "Last name": "Carson", "ID": "", "Email": "midgets@gmail.com"}, {"First name": "Joel", "Age": "5", "Gender": "male", "Last name": "Hobbs", "ID": "", "Email": "psychs@hotmail.com"}, {"First name": "Kay", "Age": "81", "Gender": "female", "Last name": "Bauer", "ID": "", "Email": ""}, {"First name": "Rosemary", "Age": "85", "Gender": "female", "Last name": "Gomez", "ID": "", "Email": "compacts@gmail.com"}, {"First name": "Leann", "Age": "9", "Gender": "female", "Last name": "Byrd", "ID": "", "Email": "importantly@hotmail.com"}, {"First name": "Jorge", "Age": "47", "Gender": "male", "Last name": "Fleming", "ID": "", "Email": "pittances@hotmail.com"}, {"First name": "", "Age": "76", "Gender": "female", "Last name": "Johns", "ID": "", "Email": "stenches@gmail.com"}, {"First name": "Edwina", "Age": "41", "Gender": "female", "Last name": "Carr", "ID": "", "Email": "hustingss@gmail.com"}] \ No newline at end of file diff --git a/fstimer_mac.command b/fstimer_mac.command new file mode 100755 index 0000000..a2e2d10 --- /dev/null +++ b/fstimer_mac.command @@ -0,0 +1,4 @@ +#!/bin/sh +cd "$(dirname "$0")" +nohup /opt/local/bin/python3.5 fstimer.py & +killall Terminal \ No newline at end of file
    Place' + field + '
    '.join(data)+'
    '.join(row)+'
    ' + str(self.place) + '' + self.common_entry(bibid, timing_data, runner_data) + in the scratch results''' + result = '
    ' + str(self.place) + '' + self.common_entry(row) self.place += 1 return result - def cat_entry(self, bibid, category, timing_data, runner_data): + def cat_entry(self, category, row): '''Returns the printout of the entry of a given runner - in the divisional results - @type bibid: string - @param bibid: the bibid of the runner - @type category: string - @param category: name of the category for this runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - result = '
    ' + str(self.cat_place[category]) + '' + self.common_entry(bibid, timing_data, runner_data) + in the divisional results''' + result = '
    ' + str(self.cat_place[category]) + '' + self.common_entry(row) self.cat_place[category] += 1 return result diff --git a/fstimer/printhtmllaps.py b/fstimer/printhtmllaps.py index 696b404..f691b16 100644 --- a/fstimer/printhtmllaps.py +++ b/fstimer/printhtmllaps.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 #fsTimer - free, open source software for race timing. #Copyright 2012-14 Ben Letham @@ -33,30 +33,21 @@ def __init__(self, fields, categories): @param categories: existing categories''' super(HTMLPrinterLaps, self).__init__(fields, categories) - def common_entry(self, bibid, timing_data, runner_data): + def common_entry(self, row): '''Returns the common part of the printout of the entry of a given runner for scratch or by category results - @type bibid: string - @param bibid: the bibid of the runner - @type timing_data: timedelta|list - @param timing_data: timing data for the runner. May be his/her time - or a list of times for multi lap races - @type runner_data: dict - @param runner_data: data concerning the runner. A dictionnary - of field name / field value''' - # first line, with total time and first lap - data = [str(timing_data[0]), - '1 - ' + str(timing_data[1]), - runner_data['First name'] + ' '+ runner_data['Last name'], - bibid, - runner_data['Gender'], - str(runner_data['Age'])] - for field in self.fields[7:]: - data.append(runner_data[field]) - entry = ''.join(data)+'
    ' + ''.join(data) + '
    '.join(row_print)+'
    ' # extra for Place + row_print = ['' for j in range(len(row))] + row_print[idx_lap] = str(lap_times[i]) + entry += ''.join(row_print) + '