-
Notifications
You must be signed in to change notification settings - Fork 0
TurboVote
- Background / Context
- Problem
- Solution
- Record cleaning
- Status Translation Rules
- Status Hierarchy
- Counting
- Team Impact
- Gotchas/Open Questions
Users can register to vote on a third-party platform called TurboVote. We send our members to that site to register and in the link to the site we send some specifically formatted information like NSID, campaign ID, source, etc.
Here’s an example for an email send: https://testing-dosomething.turbovote.org/?r=user:{northstarID},campaignID:{campaignID},campaignRunID:{legacyRunID},source:email,source_details:newsletter_{newsletterID}
TurboVote then provides us a CSV of individual records that reflects what happened on their platform. Here's an example of the some of the columns available in that CSV -- columns with personal information have been left out and NS ID has been hidden! The highlighted pink rows will be addressed in the hierarchy section below.

We want to import this CSV into Rogue as signups and posts so that these users can be served experiences based on their registration status, they can be messaged, and we can accurately report this information in Looker (and do deeper data analysis when needed).
TurboVote records and Rogue posts do not have the same schema! We want to figure out how to store TV records in Rogue so that other DoSomething Apps can get this information via the Rogue API and be able to serve this data to internal teams and serve customized user experiences based on a user’s registration status.
So, how should TV records be imported into Rogue?
Admin Workflow

There are some records that might show up in the TV record that we want to ignore. The rules around that data cleaning are outlined below:
- If an
emailincludesthing.orgin the address, ignore it. - If a
hostnameincludes `testing, ignore it. - If an
emailincludes@dosomein the address, ignore it. - If a last name includes
Baloney, ignore it. - If an email includes
turbovote, ignore it.
Ultimately, there are 5 new post statuses we want to capture for voter-reg posts:
-
register-form- User completed the registration form -
register-OVR- User completed the registration form on their state's Online Voter Registration platform -
confirmed- User just confirmed that they are registered (self-reported) -
ineligible- User is ineligible to register for whatever reason -
uncertain- We can not be certain about this user registration status
This is the logic for how to determine what a post status should be.
switch($tvStatus)
{
case 'initiated':
$translatedStatus = 'register-form';
break;
case 'registered':
$translatedStatus = $tvMethod === 'online' ? 'register-OVR' : 'confirmed';
break;
case 'unknown':
case 'pending':
$translatedStatus = 'uncertain';
break;
case 'ineligible':
case 'not-required':
$translatedStatus = 'ineligible';
break;
default:
$translatedStatus = 'pending';
}
When a TV CSV has multiple records per user we use the following hierarchy to determine which status should be reported on the Rogue post
register-formregister-OVRconfirmedineligibleuncertain
We’ve established this hierarchy because each time a user interacts with the TurboVote form a new row is created in the CSV. In the above screenshot, the highlighted pink cells are an example of the same user interacting with the form more than once. The hierarchy is the simplest approach to dealing with varying statuses, but we anticipate some edge cases that we may need to deal with as they come up.
Here’s one example:
- User A completes the TurboVote form —>
register-formstatus - User A, for whatever reason, starts the TurboVote form again and drops off -->
uncertainstatus
In this case, we would want to count the form completion (register-form). It’s important to note that the hierarchy is for internal reporting and doesn’t prevent the user from interacting with the TurboVote form if they want to do so.
Based on the above 5 statuses, some should be counted as a RB and some should not.** This determination was made by the executive team and allows us to report internally progress towards the organization's report back goal. Here's what counts as a reportback:
register-formregister-OVRconfirmed
Note: register-form and register-OVR are the only statuses that count as registrations.
Rogue will NOT store this information, but will return a derived value in the JSON response when the voter registration post is created or read that holds this information. The logic to determine this is as follows:
if (in_array($rogueStatus, ['confirmed', 'register-form', 'register-OVR'])) {
$reportbackStatus = 'T';
} else {
$reportbackStatus = 'F';
}
- The
submission_created_atdate is when the importer ran. Details about when the registration was created/updated are in thesource_details. - All of these signups will have a
sourceofimporter-client(this is how messaging is suppressed in C.io) - All of these posts have a
type=voter-reg - The month that the registration came in is what informs the
actioncolumn (e.g., february-2018-turbovote)
Team Storm
- Will need to be able to accept JSON with new derived reportback value
- Looks will need to be updated
Team O'Doyle
- To know if a user is registered or not, updates will need to be made so that (Blink, Gambit, C.io) to accept new statuses (e.g.,
register-form). This includes any validation on the status field. - To know if a user has reported back, updates will need to be made (Blink, Gambit, C.io) to read the derived value returned in the JSON response and use it to act upon.
Team Rocket
- To know if a user is registered or not, updates will need to be made (Phoenix-next) to read the new statuses of a
voter-regpost (e.g.,register-form) - To know if a user has reported back, updates will need to be made (Phoenix-next) to read from the derived report back value
Team Bleed
- Build out this logic in the importer app
- Update validation on post status column to accept more statuses
- Test that voter reg posts are importing correctly and being sent to other systems
- There is a 1:1 relationship between a user (unique NSID) and a post.
- For now, we will attribute every
voter-regpost to the Grab the Mic campaign to maintain a 1:1 relationship - Quasar will be pushed these posts from Rogue but if Data needs to do a deeper analysis, they will use the raw TV CSV to do that work.
- If a user shares their UTM'ed URL with other people, there could be duplicate referral codes but associated with different registrants: See a screenshot of what this data looks like (note: the user depicted in this spreadsheet is fake.)
- The post created_at date will be when we imported the CSV; the actual registration created_at will be in the source_details.
- Should final registration status live on a users NS profile? If so should rogue be involved at all.
- We'll need to build the logic to create NS IDs for non-members - should we only create these accounts for the statuses we care about (
register-formandregister-OVR)?