├── runtime.txt ├── Procfile ├── .gitignore ├── uwsgi.ini ├── tests.py ├── requirements.txt ├── cache_handler.py ├── static ├── js │ ├── live-calls.js │ ├── main.js │ ├── jquery.timeago.js │ └── lodash.min.js ├── css │ └── styles.css └── demo-roll-call.min.json ├── config.py ├── templates ├── live.html └── demo.html ├── access_control_decorator.py ├── throttle.py ├── fftf_leaderboard.py ├── models.py ├── README.md ├── newrelic.ini ├── old └── test_server.py ├── political_data.py └── app.py /runtime.txt: -------------------------------------------------------------------------------- 1 | python-2.7.12 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: newrelic-admin run-program uwsgi uwsgi.ini 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | *.db 4 | .env 5 | src 6 | throttle.py 7 | -------------------------------------------------------------------------------- /uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | die-on-term = true 3 | gevent = 100 4 | http-socket = :$(PORT) 5 | lazy = true 6 | master = true 7 | memory-report = true 8 | module = app:app 9 | processes = 4 10 | buffer-size = 65535 11 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | from political_data import PoliticalData 2 | 3 | class TestData(): 4 | def setUp(self): 5 | self.data = PoliticalData() 6 | 7 | def test_legislators(self): 8 | assert self.data.legislators is not None 9 | 10 | legislator = self.data.legislators[0] 11 | 12 | assert legislator['chamber'] == 'house' 13 | 14 | def test_locate_member_ids(self): 15 | ids = self.data.locate_member_ids( 16 | '98004', self.data.campaigns['stop-fast-track']) 17 | 18 | assert len(ids) == 4 19 | assert ids[0]['bioguide_id'] == 'C000127' 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.3 2 | Flask==0.10.1 3 | Flask-Cache==0.12 4 | Flask-Jsonpify==1.1 5 | Flask-SQLAlchemy==2.0 6 | gevent==1.1.2 7 | greenlet==0.4.2 8 | grequests==0.2.0 9 | httplib2==0.8 10 | itsdangerous==0.23 11 | Jinja2==2.7.2 12 | MarkupSafe==0.18 13 | MySQL-python==1.2.5 14 | newrelic==2.12.0.10 15 | nose==1.3.0 16 | psycopg2==2.5.4 17 | pystache==0.5.3 18 | python-dateutil==2.6.0 19 | pytz==2013.9 20 | PyYAML==3.10 21 | raven==4.0.4 22 | redis==2.10.3 23 | requests==2.4.2 24 | setupfiles==0.0.13 25 | six==1.10.0 26 | SQLAlchemy==0.9.2 27 | twilio==3.6.5 28 | unittest2==0.5.1 29 | uWSGI==2.0.13.1 30 | Werkzeug==0.9.4 31 | -------------------------------------------------------------------------------- /cache_handler.py: -------------------------------------------------------------------------------- 1 | from redis import Redis 2 | 3 | class CacheHandler(): 4 | 5 | redis_conn = None 6 | 7 | def __init__(self, redis_url): 8 | 9 | if redis_url: 10 | self.redis_conn = Redis.from_url(redis_url) 11 | 12 | def get(self, key, default): 13 | 14 | if self.redis_conn == None: 15 | return default 16 | 17 | return self.redis_conn.get(key) or default 18 | 19 | def set(self, key, val, expire=None): 20 | 21 | if self.redis_conn == None: 22 | return 23 | 24 | if expire == None: 25 | self.redis_conn.set(key, val) 26 | else: 27 | self.redis_conn.setex(key, val, expire) 28 | -------------------------------------------------------------------------------- /static/js/live-calls.js: -------------------------------------------------------------------------------- 1 | // simple param extract from location 2 | function getQueryVariable(variable) { 3 | var query = window.location.search.substring(1); 4 | var vars = query.split("&"); 5 | for (var i=0;i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Campaign Stats 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |

Recent Calls

35 | 38 |
39 |
40 |
    41 |
42 |
43 | 44 | 50 | 51 | -------------------------------------------------------------------------------- /access_control_decorator.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | from flask import make_response, request, current_app, Response 3 | from functools import update_wrapper, wraps 4 | 5 | 6 | def crossdomain(origin=None, methods=None, headers=None, 7 | max_age=21600, attach_to_all=True, 8 | automatic_options=True): 9 | if methods is not None: 10 | methods = ', '.join(sorted(x.upper() for x in methods)) 11 | if headers is not None and not isinstance(headers, basestring): 12 | headers = ', '.join(x.upper() for x in headers) 13 | if not isinstance(origin, basestring): 14 | origin = ', '.join(origin) 15 | if isinstance(max_age, timedelta): 16 | max_age = max_age.total_seconds() 17 | 18 | def get_methods(): 19 | if methods is not None: 20 | return methods 21 | 22 | options_resp = current_app.make_default_options_response() 23 | return options_resp.headers['allow'] 24 | 25 | def decorator(f): 26 | def wrapped_function(*args, **kwargs): 27 | if automatic_options and request.method == 'OPTIONS': 28 | resp = current_app.make_default_options_response() 29 | else: 30 | resp = make_response(f(*args, **kwargs)) 31 | if not attach_to_all and request.method != 'OPTIONS': 32 | return resp 33 | 34 | h = resp.headers 35 | 36 | h['Access-Control-Allow-Origin'] = origin 37 | h['Access-Control-Allow-Methods'] = get_methods() 38 | h['Access-Control-Max-Age'] = str(max_age) 39 | if headers is not None: 40 | h['Access-Control-Allow-Headers'] = headers 41 | return resp 42 | 43 | f.provide_automatic_options = False 44 | return update_wrapper(wrapped_function, f) 45 | return decorator 46 | 47 | 48 | def check_auth(username, password): 49 | """This function is called to check if a username / 50 | password combination is valid. 51 | """ 52 | return username == 'admin' and password == current_app.config['SECRET_KEY'] 53 | 54 | def authenticate(): 55 | """Sends a 401 response that enables basic auth""" 56 | return Response( 57 | 'Could not verify your access level for that URL.\n' 58 | 'You have to login with proper credentials', 401, 59 | {'WWW-Authenticate': 'Basic realm="Login Required"'}) 60 | 61 | def requires_auth(f): 62 | @wraps(f) 63 | def decorated(*args, **kwargs): 64 | auth = request.authorization 65 | if not auth or not check_auth(auth.username, auth.password): 66 | return authenticate() 67 | return f(*args, **kwargs) 68 | return decorated 69 | -------------------------------------------------------------------------------- /throttle.py: -------------------------------------------------------------------------------- 1 | import os, psycopg2, hashlib 2 | 3 | class Throttle(): 4 | 5 | conn = None 6 | 7 | def __init__(self): 8 | 9 | url = os.environ.get('HEROKU_POSTGRESQL_AMBER_URL') 10 | self.conn = psycopg2.connect(url) 11 | 12 | def throttle(self, campaign_id, from_phone_number, ip_address, override): 13 | """Records call info in the log""" 14 | 15 | if from_phone_number == '' or len(from_phone_number) != 10: 16 | print "THROTTLE TRIP! --- Bad from_phone_number!" 17 | 18 | from_phone_number = format_phone_number(from_phone_number) 19 | 20 | conn = self.conn 21 | cur = conn.cursor() 22 | 23 | flag_number = 0 24 | flag_ip = 0 25 | blacklist = 0 26 | is_whitelisted = 0 27 | 28 | hashed_ip_address = hashlib.sha256(ip_address).hexdigest() 29 | 30 | qry = ("SELECT count(id) FROM _ms_call_throttle WHERE " 31 | "create_date >= NOW() - '1 day'::INTERVAL " 32 | " AND campaign_id=%s AND from_phone_number=%s") 33 | cur.execute(qry, (campaign_id, from_phone_number)) 34 | recent_from_phone_number = cur.fetchone() 35 | 36 | if recent_from_phone_number[0] > 1: 37 | flag_number = 1 38 | 39 | qry = ("SELECT count(id) FROM _ms_call_throttle WHERE " 40 | "create_date >= NOW() - '1 day'::INTERVAL " 41 | " AND campaign_id=%s AND (ip_address=%s OR ip_address=%s)") 42 | cur.execute(qry, (campaign_id, ip_address, hashed_ip_address)) 43 | recent_ip_address = cur.fetchone() 44 | 45 | if recent_ip_address[0] > 1: 46 | flag_ip = 1 47 | 48 | qry = "SELECT count(id) FROM _ms_call_blacklist WHERE phone_number=%s" 49 | cur.execute(qry, (from_phone_number,)) 50 | is_blacklisted = cur.fetchone() 51 | 52 | if is_blacklisted[0] > 0: 53 | blacklist = 1 54 | 55 | if override ==os.environ.get('THROTTLE_OVERRIDE_KEY') and not blacklist: 56 | flag_ip = 0 57 | flag_number = 0 58 | is_whitelisted = 1 59 | 60 | if flag_number == 0 and flag_ip == 0: 61 | ip_address = hashed_ip_address 62 | 63 | cur.execute(("INSERT INTO _ms_call_throttle " 64 | " (campaign_id, from_phone_number, is_whitelisted, " 65 | " is_blacklisted, ip_address, flag_number, flag_ip, " 66 | " create_date) " 67 | "VALUES " 68 | " (%s, %s, %s, %s, %s, %s, %s, NOW())"), 69 | (campaign_id, from_phone_number, is_whitelisted, blacklist, 70 | ip_address, flag_number, flag_ip)) 71 | 72 | conn.commit() 73 | cur.close() 74 | 75 | if flag_number: 76 | print "THROTTLE TRIP! --- from_phone_number %s / %s" % \ 77 | (from_phone_number, recent_from_phone_number[0]) 78 | return True 79 | elif flag_ip: 80 | print "THROTTLE TRIP! --- ip_address %s / %s" % \ 81 | (ip_address, recent_ip_address[0]) 82 | return True 83 | elif blacklist: 84 | print "THROTTLE TRIP! --- BLACKLISTED %s" % \ 85 | (from_phone_number,) 86 | return True 87 | 88 | return False 89 | 90 | def format_phone_number(phone_number): 91 | return phone_number[:3] + "-" + phone_number[3:6] + "-" + phone_number[6:10] 92 | -------------------------------------------------------------------------------- /fftf_leaderboard.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import grequests 4 | import string 5 | 6 | class FFTFLeaderboard(): 7 | 8 | debug_mode = False 9 | pool_size = 1 10 | api_key = None 11 | 12 | def __init__(self, debug_mode, pool_size, api_key): 13 | 14 | self.debug_mode = debug_mode 15 | self.pool_size = pool_size 16 | self.api_key = api_key 17 | 18 | def log_call(self, params, campaign, request): 19 | 20 | if params['fftfCampaign'] == None or params['fftfReferer'] == None: 21 | return 22 | 23 | i = int(request.values.get('call_index')) 24 | 25 | kwds = { 26 | 'campaign_id': campaign['id'], 27 | 'member_id': params['repIds'][i], 28 | 'zipcode': params['zipcode'], 29 | 'phone_number': hashlib.sha256(params['userPhone']).hexdigest(), 30 | 'call_id': request.values.get('CallSid', None), 31 | 'status': request.values.get('DialCallStatus', 'unknown'), 32 | 'duration': request.values.get('DialCallDuration', 0) 33 | } 34 | data = json.dumps(kwds) 35 | 36 | self.post_to_leaderboard( 37 | params['fftfCampaign'], 38 | 'call', 39 | data, 40 | params['fftfReferer'], 41 | params['fftfSession']) 42 | 43 | def log_complete(self, params, campaign, request): 44 | 45 | if params['fftfCampaign'] == None or params['fftfReferer'] == None: 46 | return 47 | 48 | self.post_to_leaderboard( 49 | params['fftfCampaign'], 50 | 'calls_complete', 51 | 'yay', 52 | params['fftfReferer'], 53 | params['fftfSession']) 54 | 55 | def post_to_leaderboard(self, fftf_campaign, stat, data, host, session): 56 | 57 | debug_mode = self.debug_mode 58 | 59 | def finished(res, **kwargs): 60 | if debug_mode: 61 | print "FFTF Leaderboard call complete: %s" % res 62 | 63 | data = { 64 | 'campaign': fftf_campaign, 65 | 'stat': stat, 66 | 'data': data, 67 | 'host': host, 68 | 'session': session 69 | } 70 | 71 | if self.debug_mode: 72 | print "FFTF Leaderboard sending: %s" % data 73 | 74 | url = 'https://leaderboard.fightforthefuture.org/log' 75 | req = grequests.post(url, data=data, hooks=dict(response=finished)) 76 | job = grequests.send(req, grequests.Pool(self.pool_size)) 77 | 78 | return 79 | 80 | def log_extra_data(self, params, campaign, request, to_phone, call_index): 81 | 82 | debug_mode = self.debug_mode 83 | 84 | def finished(res, **kwargs): 85 | if debug_mode: 86 | print "FFTF Extra Data log call complete: %s" % res 87 | 88 | ip = hashlib.sha256(request.values.get("ip_address", "")).hexdigest() 89 | 90 | user_phone = params.get('userPhone', None) 91 | org = params.get('org', 'fftf') 92 | 93 | if not user_phone: 94 | user_phone = request.values.get('From', '+15555555555')[-10:] 95 | 96 | data = { 97 | 'key': self.api_key, 98 | 'campaign_id': campaign['id'], 99 | 'from_phone_number': string.replace(user_phone, "-", ""), 100 | 'to_phone_number': string.replace(to_phone, "-", ""), 101 | 'ip_address': ip, 102 | 'call_index': call_index, 103 | 'org': org 104 | } 105 | 106 | if self.debug_mode: 107 | print "FFTF Log Extra Data sending: %s" % data 108 | 109 | url = 'https://queue.fightforthefuture.org/log_phone_call' 110 | req = grequests.post(url, data=data, hooks=dict(response=finished)) 111 | job = grequests.send(req, grequests.Pool(self.pool_size)) 112 | 113 | return -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import logging 3 | 4 | from datetime import datetime 5 | from flask_sqlalchemy import SQLAlchemy 6 | from sqlalchemy import func, Column, Integer, String, DateTime 7 | from sqlalchemy.exc import SQLAlchemyError 8 | 9 | db = SQLAlchemy() 10 | 11 | 12 | class Call(db.Model): 13 | __tablename__ = 'calls' 14 | 15 | id = Column(Integer, primary_key=True) 16 | timestamp = Column(DateTime) 17 | campaign_id = Column(String(32)) 18 | member_id = Column(String(10)) # congress member sunlight identifier 19 | 20 | # user attributes 21 | user_id = Column(String(64)) # hashed phone number 22 | zipcode = Column(String(5)) 23 | areacode = Column(String(3)) # first 3 digits of phone number 24 | exchange = Column(String(3)) # next 3 digits of phone number 25 | 26 | # twilio attributes 27 | call_id = Column(String(40)) # twilio call ID 28 | status = Column(String(25)) # twilio call status 29 | duration = Column(Integer) # twilio call time in seconds 30 | 31 | @classmethod 32 | def hash_phone(cls, number): 33 | """ 34 | Takes a phone number and returns a 64 character string 35 | """ 36 | return hashlib.sha256(number).hexdigest() 37 | 38 | def __init__(self, campaign_id, member_id, zipcode=None, phone_number=None, 39 | call_id=None, status='unknown', duration=0): 40 | self.timestamp = datetime.now() 41 | self.status = status 42 | self.duration = duration 43 | self.campaign_id = campaign_id 44 | self.member_id = member_id 45 | self.call_id = call_id 46 | 47 | if phone_number: 48 | phone_number = phone_number.replace('-', '').replace('.', '') 49 | self.user_id = self.hash_phone(phone_number) 50 | self.areacode = phone_number[:3] 51 | self.exchange = phone_number[3:6] 52 | 53 | self.zipcode = zipcode 54 | 55 | def __repr__(self): 56 | return ''.format( 57 | self.areacode, self.exchange, self.member_id) 58 | 59 | 60 | def log_call(params, campaign, request): 61 | try: 62 | i = int(request.values.get('call_index')) 63 | 64 | kwds = { 65 | 'campaign_id': campaign['id'], 66 | 'member_id': params['repIds'][i], 67 | 'zipcode': params['zipcode'], 68 | 'phone_number': params['userPhone'], 69 | 'call_id': request.values.get('CallSid', None), 70 | 'status': request.values.get('DialCallStatus', 'unknown'), 71 | 'duration': request.values.get('DialCallDuration', 0) 72 | } 73 | 74 | db.session.add(Call(**kwds)) 75 | db.session.commit() 76 | except SQLAlchemyError: 77 | logging.error('Failed to log call:', exc_info=True) 78 | 79 | 80 | def call_count(campaign_id): 81 | try: 82 | return (db.session.query(func.Count(Call.zipcode)) 83 | .filter(Call.campaign_id == campaign_id).all())[0][0] 84 | except SQLAlchemyError: 85 | logging.error('Failed to get call_count:', exc_info=True) 86 | 87 | return 0 88 | 89 | def call_list(campaign_id, since, limit=50): 90 | try: 91 | calls = (db.session.query(Call) 92 | .order_by(Call.timestamp) 93 | .filter(Call.campaign_id == campaign_id) 94 | .filter(Call.timestamp >= since) 95 | .limit(limit).all()) 96 | return calls 97 | 98 | except SQLAlchemyError: 99 | logging.error('Failed to get call_list:', exc_info=True) 100 | 101 | return 0 102 | 103 | 104 | def aggregate_stats(campaign_id): 105 | zipcodes = (db.session.query(Call.zipcode, func.Count(Call.zipcode)) 106 | .filter(Call.campaign_id == campaign_id) 107 | .group_by(Call.zipcode).all()) 108 | 109 | reps = (db.session.query(Call.member_id, func.Count(Call.member_id)) 110 | .filter(Call.campaign_id == campaign_id) 111 | .group_by(Call.member_id).all()) 112 | 113 | return { 114 | 'campaign': campaign_id, 115 | 'calls': { 116 | 'zipcodes': dict(tuple(z) for z in zipcodes), 117 | 'reps': dict(tuple(r) for r in reps) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | var CONGRESS_URL = 'https://congress.api.sunlightfoundation.com', 2 | API_KEY = '8d0caa0295254038a5b61878a06d80ec', 3 | campaignId = 'defund-the-nsa', 4 | callServer = '/'; 5 | // 'https://call.taskforce.is/'; 6 | 7 | var BAD_TWITTER_HANDLES = [ 8 | 'S000510', 9 | 'P000598', 10 | 'O000170', 11 | 'J000294' 12 | ]; 13 | 14 | function getLegislators(zip, cb) { 15 | $.getJSON(CONGRESS_URL + '/legislators/locate?apikey=' + API_KEY + '&zip=' + 16 | zip, function (legislators) { 17 | cb(legislators.results); 18 | }); 19 | } 20 | 21 | function submitZipcode() { 22 | getLegislators($('#zipcode').val(), function (legislators) { 23 | $('[data-bio-id]').hide(); 24 | legislators.filter(function (legislator) { 25 | return legislator.chamber === 'house'; 26 | }).forEach(function (legislator) { 27 | console.log(legislator); 28 | 29 | $('[data-bio-id="' + legislator.bioguide_id + '"]').show(); 30 | }); 31 | }); 32 | } 33 | 34 | $(function () { 35 | 36 | 37 | var contactTemplate = $('#contact-template').html(); 38 | 39 | $('body').on('click', '.contact-button', function (ev) { 40 | var twitter = $(ev.currentTarget).attr('data-twitter-id'); 41 | var phone = $(ev.currentTarget).attr('data-phone-number'); 42 | var vote = $(ev.currentTarget).attr('data-vote'); 43 | // _gaq.push(['_trackEvent', 'action', 'contact-button-clicked']); 44 | 45 | var message; 46 | 47 | if (vote !== 'Aye') { 48 | message = "It's shameful that you voted for unconstitutional record " + 49 | "collection instead of #privacy! #defundNSA http://defundthensa.com/"; 50 | } else { 51 | message = "Thank you for supporting #privacy! You're earning my vote, " + 52 | "keep up the good work! #defundNSA http://defundthensa.com/"; 53 | } 54 | 55 | $(ev.currentTarget).hide(); 56 | 57 | $('.number-and-twitter', $(ev.currentTarget).parents('.leg-contact')).show(); 58 | $('.number-and-twitter', $(ev.currentTarget).parents('.leg-contact')) 59 | .html(_.template(contactTemplate, 60 | { message: message, twitter: twitter, phone: phone })); 61 | 62 | // $.getScript("http://platform.twitter.com/widgets.js"); 63 | }); 64 | 65 | $('body').on('submit', 'form.zipcodeform', function () { 66 | submitZipcode(); 67 | // _gaq.push(['_trackEvent', 'action', 'zipcode-lookup']); 68 | return false; 69 | }); 70 | 71 | var callTemplate = $('#call-template').html(); 72 | 73 | $.getJSON('static/demo-roll-call.min.json', function (legislators) { 74 | // Some legislators have Twitter handles specified that don't actually 75 | // exist; here we filter them out. 76 | BAD_TWITTER_HANDLES.forEach(function (id) { 77 | var legislator = _.find(legislators.votes, 78 | { details: { bioguide_id: id } }); 79 | 80 | legislator.details.twitter_id = null; 81 | }); 82 | 83 | legislators.votes = _.sortBy(legislators.votes, function (legislator) { 84 | return legislator.details.last_name + ' ' + legislator.details.first_name; 85 | }); 86 | 87 | var yes = _.filter(legislators.votes, function (vote) { 88 | return vote.vote[0] === 'Aye'; 89 | }); 90 | 91 | var no = _.filter(legislators.votes, function (vote) { 92 | return vote.vote[0] === 'No'; 93 | }); 94 | 95 | $('.vote-table').html(_.template(callTemplate, {votes: {yes: yes, no: no}})); 96 | 97 | console.log(yes, no.length); 98 | }); 99 | 100 | 101 | // on click of call member button 102 | $('body').on('click', '.caller-button-legislator', function (ev) { 103 | ev.preventDefault(); 104 | var repId = $(ev.currentTarget).data('bioguide_id'), 105 | user_phone_number = $('.user-phone-number').val(); 106 | 107 | alert(user_phone_number); 108 | 109 | $.ajax({ 110 | url: callServer + 'create', 111 | type: 'POST', 112 | data: { 113 | campaignId: campaignId, 114 | repIds: repId, 115 | userPhone: user_phone_number} 116 | }).done(function(data){ 117 | console.log(data); 118 | }).fail(function(data){ 119 | console.log(data); 120 | }); 121 | }); 122 | 123 | // on click of a general call button (no congress member info) 124 | $('body').on('click', '.caller-button-general', function (ev) { 125 | ev.preventDefault(); 126 | var user_phone_number = $('.user-phone-number-general').val(); 127 | $.ajax({ 128 | url: callServer + 'create', 129 | type: 'POST', 130 | data: { 131 | campaignId: campaignId, 132 | userPhone: user_phone_number} 133 | }).done(function(data){ 134 | console.log(data); 135 | $('.call-now').append('

Thank you for calling.

') 136 | }).fail(function(data){ 137 | console.log(data); 138 | $('.call-now').append('

Sorry something went wrong. Please try again.

') 139 | }); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | margin: 0px; 3 | padding: 0px; 4 | } 5 | body { 6 | font-family:"ff-dagny-web-pro",sans-serif; 7 | color: #484848; 8 | padding: 25px 0 0 0; 9 | background: #ecf0f1; 10 | line-height: 1.5; 11 | } 12 | .content-container { 13 | width: 700px; 14 | margin: 0 auto; 15 | } 16 | strong { 17 | font-weight: bold; 18 | } 19 | h1 { 20 | font-size: 32px; 21 | line-height: 40px; 22 | margin-bottom: 15px; 23 | } 24 | p { 25 | padding: 10px 0 10px 0; 26 | } 27 | h1 { 28 | text-align: center; 29 | } 30 | h2 { 31 | font-weight: bold; 32 | font-size: 1.6em; 33 | text-align: center; 34 | } 35 | .important { 36 | font-weight: bold; 37 | } 38 | .box { 39 | background-color:white; 40 | -moz-box-shadow: 0 0 3px 1px rgba(0,0,0,.05), 0 1px 2px 0px rgba(0,0,0,.1); 41 | -webkit-box-shadow: 0 0 3px 1px rgba(0,0,0,.05), 0 1px 2px 0px rgba(0,0,0,.1); 42 | box-shadow: 0 0 3px 1px rgba(0,0,0,.05), 0 1px 2px 0px rgba(0,0,0,.1); 43 | -moz-border-radius: 3px; 44 | -webkit-border-radius: 3px; 45 | border-radius: 3px; 46 | margin-left:auto; 47 | margin-right:auto; 48 | margin-bottom:20px; 49 | padding:20px; 50 | } 51 | .box.last { 52 | margin-bottom: 0; 53 | } 54 | .zipcode-search { 55 | width: 440px; 56 | margin: auto; 57 | } 58 | .zipcode-search input, 59 | .zipcode-search label, 60 | .zipcode-search button { 61 | float: left; 62 | margin: 0; 63 | padding: 0; 64 | border: 0; 65 | margin-right: 15px; 66 | } 67 | .zipcode-search label { 68 | font-size: 24px; 69 | display: block; 70 | height: 40px; 71 | line-height: 40px; 72 | } 73 | .zipcode-search input.search { 74 | height: 40px; 75 | padding: 0; 76 | font-size: 22px; 77 | border: 1px solid #ebebeb; 78 | color: #444; 79 | padding-left: 10px; 80 | width: 100px; 81 | padding-right: 10px; 82 | } 83 | blockquote { 84 | font-size: 95%; 85 | color: #777; 86 | font-variant: italic; 87 | padding-left: 20px; 88 | } 89 | .user-phone-number-general{ 90 | height: 40px; 91 | width: 12em; 92 | font-size: 14px; 93 | } 94 | .btn { 95 | height : 40px; 96 | border-radius: 3px; 97 | background-color: rgb(52, 152, 219); 98 | border: 0px; 99 | border-image-source: initial; 100 | border-image-slice: initial; 101 | border-image-width: initial; 102 | border-image-outset: initial; 103 | border-image-repeat: initial; 104 | color: rgb(255, 255, 255); 105 | font-weight: 400; 106 | letter-spacing: 1px; 107 | text-transform: uppercase; 108 | font-size: 12px; 109 | width: 100%; 110 | padding : 12px 0px; 111 | max-width: 160px; 112 | display: block; 113 | cursor: pointer; 114 | } 115 | .contact-button { 116 | border-radius: 3px; 117 | background-color: #FF6666; 118 | border: 0px; 119 | border-image-source: initial; 120 | border-image-slice: initial; 121 | border-image-width: initial; 122 | border-image-outset: initial; 123 | border-image-repeat: initial; 124 | color: rgb(255, 255, 255); 125 | font-weight: 400; 126 | letter-spacing: 1px; 127 | text-transform: uppercase; 128 | font-size: 12px; 129 | padding : 4px 8px; 130 | display: block; 131 | cursor: hand; 132 | box-shadow: 0px 1px 2px 0px #AFAFAF, -1px -1px 3px 0px rgba(197, 197, 197, 0.19) inset; 133 | } 134 | 135 | #submit-zipcode.btn { 136 | max-width: 200px !important; 137 | } 138 | .btn:hover { 139 | background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1)); 140 | background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1)); 141 | background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1)); 142 | background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1)); 143 | background-image: linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1)); 144 | } 145 | 146 | #legislators.loading { 147 | padding: 20px; 148 | text-align:center; 149 | content: 'Loading...'; 150 | background: url(../images/ajax-loader.gif) no-repeat center center; 151 | } 152 | #tweets, #phones { 153 | display: none; 154 | } 155 | .footer { 156 | padding: 5px; 157 | text-align: right; 158 | font-size: 12px; 159 | } 160 | 161 | #tweets { 162 | margin-top: 20px; 163 | } 164 | 165 | table th { 166 | font-weight: bold; 167 | text-align: left; 168 | } 169 | 170 | .vote-table-data td { 171 | padding: 10px; 172 | margin: 5px; 173 | } 174 | 175 | .leg-name { 176 | margin-left: 5px; 177 | } 178 | 179 | .leg-contact { 180 | margin-left: 5px; 181 | font-size: 90%; 182 | float: left; 183 | } 184 | 185 | .number-and-twitter { 186 | float: left; 187 | } 188 | 189 | .voted-for, 190 | .voted-against { 191 | font-weight: bold; 192 | text-align: center; 193 | padding-right: 3em; 194 | } 195 | 196 | .voted-against { 197 | color: #ff6666; 198 | } 199 | 200 | .voted-for { 201 | color: #66cc00; 202 | } 203 | 204 | .yes-table .contact-button { 205 | background-color: #66cc00; 206 | } 207 | 208 | .vote-table-data { 209 | width: 100%; 210 | } 211 | 212 | .against-column, 213 | .for-column { 214 | width: 310px; 215 | float: left; 216 | } 217 | -------------------------------------------------------------------------------- /templates/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Demo for Call Congress 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |

Demo for Call Congress

37 |
38 |
39 |
40 |

Tell your representative what you think about how they voted.

41 | 42 | 43 |

44 | or dial 415-123-1234 45 |

46 |
47 | 48 |
49 |

The roll call

50 |
51 | 52 | 56 | 57 | 120 | 121 | 127 |
128 | 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Call Congress 2 | ============== 3 | 4 | A simple flask server that connects calls between citizens and their congress person using the Twilio API. 5 | 6 | 7 | The server handles two cases: 8 | 9 | * A phone call made to the campaign number 10 | * A web-initiated call to connect a user's phone number to congress 11 | * can specify congress person(s) in the api call 12 | * can have the user punch in their zip code and look up their congressional members 13 | 14 | ### Incoming phone calls 15 | Each new campaign's Twilio phone number needs to be [configured](http://twilio.com/user/account/phone-numbers/incoming) to point to: 16 | 17 | /incoming_call?campaignId=abc-1234 18 | 19 | The user will be prompted to punch in their zip code, the server will locate their members of congress using the [Sunlight Labs locate data](http://sunlightlabs.github.io/congress/index.html#bulk-data/zip-codes-to-congressional-districts), and dial them. 20 | 21 | ### Web-initiated connection calls 22 | These calls are made from a web form where the user enters their phone number to be connected to Congress (will be prompted for zip code): 23 | 24 | /create?campaignId=abc-123&userPhone=1234567890 25 | 26 | or a specific member of congress: 27 | 28 | /create?campaignId=abc-123&userPhone=1234567890&repIds=P000197 29 | 30 | or to member(s) based on zip code: 31 | 32 | /create?campaignId=abc-123&userPhone=1234567890&zipcode=98102 33 | 34 | Required Params: 35 | 36 | * **userPhone** 37 | * **campaignId** 38 | 39 | Optional Params: *(either or)* 40 | 41 | * **zipcode** 42 | * **repIds**: identifiers (can be more than one) from the Sunlight API's [**bioguide_id**](http://sunlightlabs.github.io/congress/legislators.html#fields/identifiers) field 43 | 44 | 45 | Campaign Configuration 46 | ---------------------- 47 | Currently stored in ``/data/campaigns.yaml``, each campaign has the following optional fields. Defaults are given by the ``default`` campaign. 48 | 49 | * **id** 50 | * **number** (Twilio phone number) 51 | * **target_house** include house members in lookups by location 52 | * **target_senate** include senators in lookups by location 53 | * **target_house_first** allows the campaign to target house members before senate members (default: target senate first) 54 | * **only_call_1_sen** (optional, default false) only call one senator 55 | * **only_call_1_rep** (optional, default false) only call one representative 56 | * **repIds** (optional) list of rep. IDs to target 57 | * **randomize_order** (optional, default false) randomize the order of the phone calls 58 | * **overrides_google_spreadsheet_id** (optional) ID of publicly published Google Spreadsheet which can override the default campaign behaviors on a per-state basis (see [**section below**](#overriding-the-default-behaviors-with-a-google-spreadsheet)) 59 | * **skip_star_confirm** (optional, default false) Whether to skip the "press star to confirm" step for campaigns which don't gather zipcode 60 | * **call_human_check** (optional, default false) Whether to check the recipient is not an answering machine. Note, will add a 3 second delay before your call begins. 61 | 62 | Messages: Can be urls for recorded message to play or text for the robot to read. Text can be rendered as a mustache template. The following messages are the defaults and will be inherited by new campaigns unless overwritten. 63 | 64 | * msg_intro: Hi. Welcome to call congress. 65 | * msg_ask_zip: Please enter your zip code so we can lookup your Congress person. 66 | * msg_invalid_zip: "Sorry, that zip code didn't work. Please try again." 67 | * msg_call_block_intro: "We'll now connect you to {{n_reps}} representatives. Press # for next rep." 68 | * msg_rep_intro: "We're now connecting you to {{name}}" 69 | * msg_special_call_intro: Optional: if an extra first call number is specified in the remote Google Spreadsheet, this text can be used to introduce the extra call. It's optional, and if not specified, we'll fall back to _msg_rep_intro_. 70 | * msg_between_thanks: You're doing great - here's the next call. 71 | * msg_final_thanks: Thank you! 72 | 73 | 74 | Overriding the default behaviors with a Google Spreadsheet 75 | ---------------------------------------------------------- 76 | Using the optional _overrides_google_spreadsheet_id_, each campaign can specify 77 | a remote Google Spreadsheet to pull in for state-specific overrides. This allows 78 | you to change the campaign's default behaviors on a per-state basis. Currently 79 | the following features are supported: 80 | 81 | * Change the order priority of the calls (House vs. Senate) 82 | * Always call a particular legislator first (specified by last name) 83 | * Call an arbitrary name/number before connecting to Congress. For example, you 84 | could use this to call a specific public-works department for a given state. 85 | 86 | To get an idea of how this works, [**see this example spreadsheet.**](https://docs.google.com/spreadsheets/d/1SxJWmzjNAnpkcKrMDbbnUJjx4qBX6vsF5MiyOXwf-NM/edit?usp=sharing) 87 | 88 | Specifically, the Google Spreadsheet must be public, and published (note the 89 | distinction) and the first row must contain column labels in bold-faced and with 90 | exactly the precise text as specified here: 91 | 92 | 1. **State** 93 | 2. **Target Senate** 94 | 3. **Target House** 95 | 4. **Target House First** 96 | 5. **Optional Target Individual first (lastname)** 97 | 6. **Optional Extra First Call Name** 98 | 7. **Optional Extra First Call Number** 99 | 100 | 101 | Account Keys 102 | ------------ 103 | 104 | The app uses environment variables to store account keys. For development you will need to set: 105 | 106 | * SUNLIGHTLABS_KEY 107 | * TWILIO_DEV_ACCOUNT_SID 108 | * TWILIO_DEV_AUTH_TOKEN 109 | * TW_NUMBER 110 | * REDISTOGO_URL (optional Redis URL for caching the Google Spreadsheet data) 111 | 112 | and for production: 113 | 114 | * SUNLIGHTLABS_KEY 115 | * TWILIO_ACCOUNT_SID 116 | * TWILIO_AUTH_TOKEN 117 | * APPLICATION_ROOT (url for application server) 118 | * TASKFORCE_KEY (used for querying statistics) 119 | 120 | Development mode 121 | ------------------- 122 | To install locally and run in debug mode use: 123 | 124 | # create ENV variables 125 | virtualenv venv 126 | source venv/bin/activate 127 | pip install -r requirements.txt 128 | 129 | python app.py 130 | # for testing twilio, need internet-visible urls to do call handling 131 | # cd to whatever folder you have ngrok's binary installed and run: 132 | ./ngrok http -subdomain="1cf55a5a" 5000 133 | 134 | When the dev server is running, the demo front-end will be accessible at [http://localhost:5000/demo](http://localhost:5000/demo). 135 | 136 | Unit tests can also be run, using: 137 | 138 | python test_server.py 139 | 140 | Production server 141 | ------------------ 142 | To run in production: 143 | 144 | # create ENV variables 145 | # open correct port 146 | iptables -A INPUT -p tcp --dport 80 -j ACCEPT 147 | # initialize the database 148 | python models.py 149 | # run server - will charge real $ and connect real calls 150 | foreman start 151 | 152 | Updating for changes in congress 153 | -------------------------------- 154 | Follow instructions here to update legislators.csv from legislators-current.csv generated by alternate_bulk_formats.py script: https://github.com/unitedstates/congress-legislators 155 | 156 | Since Sunlight Foundation no longer maintains their data we will need a new source for districts.csv -------------------------------------------------------------------------------- /static/js/jquery.timeago.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Timeago is a jQuery plugin that makes it easy to support automatically 3 | * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). 4 | * 5 | * @name timeago 6 | * @version 1.4.1 7 | * @requires jQuery v1.2.3+ 8 | * @author Ryan McGeary 9 | * @license MIT License - http://www.opensource.org/licenses/mit-license.php 10 | * 11 | * For usage and examples, visit: 12 | * http://timeago.yarp.com/ 13 | * 14 | * Copyright (c) 2008-2015, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) 15 | */ 16 | 17 | (function (factory) { 18 | if (typeof define === 'function' && define.amd) { 19 | // AMD. Register as an anonymous module. 20 | define(['jquery'], factory); 21 | } else { 22 | // Browser globals 23 | factory(jQuery); 24 | } 25 | }(function ($) { 26 | $.timeago = function(timestamp) { 27 | if (timestamp instanceof Date) { 28 | return inWords(timestamp); 29 | } else if (typeof timestamp === "string") { 30 | return inWords($.timeago.parse(timestamp)); 31 | } else if (typeof timestamp === "number") { 32 | return inWords(new Date(timestamp)); 33 | } else { 34 | return inWords($.timeago.datetime(timestamp)); 35 | } 36 | }; 37 | var $t = $.timeago; 38 | 39 | $.extend($.timeago, { 40 | settings: { 41 | refreshMillis: 60000, 42 | allowPast: true, 43 | allowFuture: false, 44 | localeTitle: false, 45 | cutoff: 0, 46 | strings: { 47 | prefixAgo: null, 48 | prefixFromNow: null, 49 | suffixAgo: "ago", 50 | suffixFromNow: "from now", 51 | inPast: 'any moment now', 52 | seconds: "less than a minute", 53 | minute: "about a minute", 54 | minutes: "%d minutes", 55 | hour: "about an hour", 56 | hours: "about %d hours", 57 | day: "a day", 58 | days: "%d days", 59 | month: "about a month", 60 | months: "%d months", 61 | year: "about a year", 62 | years: "%d years", 63 | wordSeparator: " ", 64 | numbers: [] 65 | } 66 | }, 67 | 68 | inWords: function(distanceMillis) { 69 | if(!this.settings.allowPast && ! this.settings.allowFuture) { 70 | throw 'timeago allowPast and allowFuture settings can not both be set to false.'; 71 | } 72 | 73 | var $l = this.settings.strings; 74 | var prefix = $l.prefixAgo; 75 | var suffix = $l.suffixAgo; 76 | if (this.settings.allowFuture) { 77 | if (distanceMillis < 0) { 78 | prefix = $l.prefixFromNow; 79 | suffix = $l.suffixFromNow; 80 | } 81 | } 82 | 83 | if(!this.settings.allowPast && distanceMillis >= 0) { 84 | return this.settings.strings.inPast; 85 | } 86 | 87 | var seconds = Math.abs(distanceMillis) / 1000; 88 | var minutes = seconds / 60; 89 | var hours = minutes / 60; 90 | var days = hours / 24; 91 | var years = days / 365; 92 | 93 | function substitute(stringOrFunction, number) { 94 | var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; 95 | var value = ($l.numbers && $l.numbers[number]) || number; 96 | return string.replace(/%d/i, value); 97 | } 98 | 99 | var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || 100 | seconds < 90 && substitute($l.minute, 1) || 101 | minutes < 45 && substitute($l.minutes, Math.round(minutes)) || 102 | minutes < 90 && substitute($l.hour, 1) || 103 | hours < 24 && substitute($l.hours, Math.round(hours)) || 104 | hours < 42 && substitute($l.day, 1) || 105 | days < 30 && substitute($l.days, Math.round(days)) || 106 | days < 45 && substitute($l.month, 1) || 107 | days < 365 && substitute($l.months, Math.round(days / 30)) || 108 | years < 1.5 && substitute($l.year, 1) || 109 | substitute($l.years, Math.round(years)); 110 | 111 | var separator = $l.wordSeparator || ""; 112 | if ($l.wordSeparator === undefined) { separator = " "; } 113 | return $.trim([prefix, words, suffix].join(separator)); 114 | }, 115 | 116 | parse: function(iso8601) { 117 | var s = $.trim(iso8601); 118 | s = s.replace(/\.\d+/,""); // remove milliseconds 119 | s = s.replace(/-/,"/").replace(/-/,"/"); 120 | s = s.replace(/T/," ").replace(/Z/," UTC"); 121 | s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 122 | s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900 123 | return new Date(s); 124 | }, 125 | datetime: function(elem) { 126 | var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); 127 | return $t.parse(iso8601); 128 | }, 129 | isTime: function(elem) { 130 | // jQuery's `is()` doesn't play well with HTML5 in IE 131 | return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); 132 | } 133 | }); 134 | 135 | // functions that can be called via $(el).timeago('action') 136 | // init is default when no action is given 137 | // functions are called with context of a single element 138 | var functions = { 139 | init: function(){ 140 | var refresh_el = $.proxy(refresh, this); 141 | refresh_el(); 142 | var $s = $t.settings; 143 | if ($s.refreshMillis > 0) { 144 | this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis); 145 | } 146 | }, 147 | update: function(time){ 148 | var parsedTime = $t.parse(time); 149 | $(this).data('timeago', { datetime: parsedTime }); 150 | if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString()); 151 | refresh.apply(this); 152 | }, 153 | updateFromDOM: function(){ 154 | $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); 155 | refresh.apply(this); 156 | }, 157 | dispose: function () { 158 | if (this._timeagoInterval) { 159 | window.clearInterval(this._timeagoInterval); 160 | this._timeagoInterval = null; 161 | } 162 | } 163 | }; 164 | 165 | $.fn.timeago = function(action, options) { 166 | var fn = action ? functions[action] : functions.init; 167 | if(!fn){ 168 | throw new Error("Unknown function name '"+ action +"' for timeago"); 169 | } 170 | // each over objects here and call the requested function 171 | this.each(function(){ 172 | fn.call(this, options); 173 | }); 174 | return this; 175 | }; 176 | 177 | function refresh() { 178 | //check if it's still visible 179 | if(!$.contains(document.documentElement,this)){ 180 | //stop if it has been removed 181 | $(this).timeago("dispose"); 182 | return this; 183 | } 184 | 185 | var data = prepareData(this); 186 | var $s = $t.settings; 187 | 188 | if (!isNaN(data.datetime)) { 189 | if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) { 190 | $(this).text(inWords(data.datetime)); 191 | } 192 | } 193 | return this; 194 | } 195 | 196 | function prepareData(element) { 197 | element = $(element); 198 | if (!element.data("timeago")) { 199 | element.data("timeago", { datetime: $t.datetime(element) }); 200 | var text = $.trim(element.text()); 201 | if ($t.settings.localeTitle) { 202 | element.attr("title", element.data('timeago').datetime.toLocaleString()); 203 | } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { 204 | element.attr("title", text); 205 | } 206 | } 207 | return element.data("timeago"); 208 | } 209 | 210 | function inWords(date) { 211 | return $t.inWords(distance(date)); 212 | } 213 | 214 | function distance(date) { 215 | return (new Date().getTime() - date.getTime()); 216 | } 217 | 218 | // fix for IE6 suckage 219 | document.createElement("abbr"); 220 | document.createElement("time"); 221 | })); 222 | -------------------------------------------------------------------------------- /newrelic.ini: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------- 2 | 3 | # 4 | # This file configures the New Relic Python Agent. 5 | # 6 | # The path to the configuration file should be supplied to the function 7 | # newrelic.agent.initialize() when the agent is being initialized. 8 | # 9 | # The configuration file follows a structure similar to what you would 10 | # find for Microsoft Windows INI files. For further information on the 11 | # configuration file format see the Python ConfigParser documentation at: 12 | # 13 | # http://docs.python.org/library/configparser.html 14 | # 15 | # For further discussion on the behaviour of the Python agent that can 16 | # be configured via this configuration file see: 17 | # 18 | # http://newrelic.com/docs/python/python-agent-configuration 19 | # 20 | 21 | # --------------------------------------------------------------------------- 22 | 23 | # Here are the settings that are common to all environments. 24 | 25 | [newrelic] 26 | 27 | # You must specify the license key associated with your New 28 | # Relic account. This key binds the Python Agent's data to your 29 | # account in the New Relic service. 30 | license_key = 17a8bd453f46ed0d18ebbc95f058e66c1e60b6f8 31 | 32 | # The appplication name. Set this to be the name of your 33 | # application as you would like it to show up in New Relic UI. 34 | # The UI will then auto-map instances of your application into a 35 | # entry on your home dashboard page. 36 | app_name = Python Application 37 | 38 | # When "true", the agent collects performance data about your 39 | # application and reports this data to the New Relic UI at 40 | # newrelic.com. This global switch is normally overridden for 41 | # each environment below. 42 | monitor_mode = true 43 | 44 | # Sets the name of a file to log agent messages to. Useful for 45 | # debugging any issues with the agent. This is not set by 46 | # default as it is not known in advance what user your web 47 | # application processes will run as and where they have 48 | # permission to write to. Whatever you set this to you must 49 | # ensure that the permissions for the containing directory and 50 | # the file itself are correct, and that the user that your web 51 | # application runs as can write to the file. If not able to 52 | # write out a log file, it is also possible to say "stderr" and 53 | # output to standard error output. This would normally result in 54 | # output appearing in your web server log. 55 | #log_file = /tmp/newrelic-python-agent.log 56 | 57 | # Sets the level of detail of messages sent to the log file, if 58 | # a log file location has been provided. Possible values, in 59 | # increasing order of detail, are: "critical", "error", "warning", 60 | # "info" and "debug". When reporting any agent issues to New 61 | # Relic technical support, the most useful setting for the 62 | # support engineers is "debug". However, this can generate a lot 63 | # of information very quickly, so it is best not to keep the 64 | # agent at this level for longer than it takes to reproduce the 65 | # problem you are experiencing. 66 | log_level = info 67 | 68 | # The Python Agent communicates with the New Relic service using 69 | # SSL by default. Note that this does result in an increase in 70 | # CPU overhead, over and above what would occur for a non SSL 71 | # connection, to perform the encryption involved in the SSL 72 | # communication. This work is though done in a distinct thread 73 | # to those handling your web requests, so it should not impact 74 | # response times. You can if you wish revert to using a non SSL 75 | # connection, but this will result in information being sent 76 | # over a plain socket connection and will not be as secure. 77 | ssl = true 78 | 79 | # The Python Agent will attempt to connect directly to the New 80 | # Relic service. If there is an intermediate firewall between 81 | # your host and the New Relic service that requires you to use a 82 | # HTTP proxy, then you should set both the "proxy_host" and 83 | # "proxy_port" settings to the required values for the HTTP 84 | # proxy. The "proxy_user" and "proxy_pass" settings should 85 | # additionally be set if proxy authentication is implemented by 86 | # the HTTP proxy. The "proxy_scheme" setting dictates what 87 | # protocol scheme is used in talking to the HTTP protocol. This 88 | # would normally always be set as "http" which will result in the 89 | # agent then using a SSL tunnel through the HTTP proxy for end to 90 | # end encryption. 91 | # proxy_scheme = http 92 | # proxy_host = hostname 93 | # proxy_port = 8080 94 | # proxy_user = 95 | # proxy_pass = 96 | 97 | # Tells the transaction tracer and error collector (when 98 | # enabled) whether or not to capture the query string for the 99 | # URL and send it as the request parameters for display in the 100 | # UI. When "true", it is still possible to exclude specific 101 | # values from being captured using the "ignored_params" setting. 102 | capture_params = false 103 | 104 | # Space separated list of variables that should be removed from 105 | # the query string captured for display as the request 106 | # parameters in the UI. 107 | ignored_params = 108 | 109 | # The transaction tracer captures deep information about slow 110 | # transactions and sends this to the UI on a periodic basis. The 111 | # transaction tracer is enabled by default. Set this to "false" 112 | # to turn it off. 113 | transaction_tracer.enabled = true 114 | 115 | # Threshold in seconds for when to collect a transaction trace. 116 | # When the response time of a controller action exceeds this 117 | # threshold, a transaction trace will be recorded and sent to 118 | # the UI. Valid values are any positive float value, or (default) 119 | # "apdex_f", which will use the threshold for a dissatisfying 120 | # Apdex controller action - four times the Apdex T value. 121 | transaction_tracer.transaction_threshold = apdex_f 122 | 123 | # When the transaction tracer is on, SQL statements can 124 | # optionally be recorded. The recorder has three modes, "off" 125 | # which sends no SQL, "raw" which sends the SQL statement in its 126 | # original form, and "obfuscated", which strips out numeric and 127 | # string literals. 128 | transaction_tracer.record_sql = obfuscated 129 | 130 | # Threshold in seconds for when to collect stack trace for a SQL 131 | # call. In other words, when SQL statements exceed this 132 | # threshold, then capture and send to the UI the current stack 133 | # trace. This is helpful for pinpointing where long SQL calls 134 | # originate from in an application. 135 | transaction_tracer.stack_trace_threshold = 0.5 136 | 137 | # Determines whether the agent will capture query plans for slow 138 | # SQL queries. Only supported in MySQL and PostgreSQL. Set this 139 | # to "false" to turn it off. 140 | transaction_tracer.explain_enabled = true 141 | 142 | # Threshold for query execution time below which query plans 143 | # will not not be captured. Relevant only when "explain_enabled" 144 | # is true. 145 | transaction_tracer.explain_threshold = 0.5 146 | 147 | # Space separated list of function or method names in form 148 | # 'module:function' or 'module:class.function' for which 149 | # additional function timing instrumentation will be added. 150 | transaction_tracer.function_trace = 151 | 152 | # The error collector captures information about uncaught 153 | # exceptions or logged exceptions and sends them to UI for 154 | # viewing. The error collector is enabled by default. Set this 155 | # to "false" to turn it off. 156 | error_collector.enabled = true 157 | 158 | # To stop specific errors from reporting to the UI, set this to 159 | # a space separated list of the Python exception type names to 160 | # ignore. The exception name should be of the form 'module:class'. 161 | error_collector.ignore_errors = 162 | 163 | # Browser monitoring is the Real User Monitoring feature of the UI. 164 | # For those Python web frameworks that are supported, this 165 | # setting enables the auto-insertion of the browser monitoring 166 | # JavaScript fragments. 167 | browser_monitoring.auto_instrument = true 168 | 169 | # A thread profiling session can be scheduled via the UI when 170 | # this option is enabled. The thread profiler will periodically 171 | # capture a snapshot of the call stack for each active thread in 172 | # the application to construct a statistically representative 173 | # call tree. 174 | thread_profiler.enabled = true 175 | 176 | # --------------------------------------------------------------------------- 177 | 178 | # 179 | # The application environments. These are specific settings which 180 | # override the common environment settings. The settings related to a 181 | # specific environment will be used when the environment argument to the 182 | # newrelic.agent.initialize() function has been defined to be either 183 | # "development", "test", "staging" or "production". 184 | # 185 | 186 | [newrelic:development] 187 | monitor_mode = false 188 | 189 | [newrelic:test] 190 | monitor_mode = false 191 | 192 | [newrelic:staging] 193 | app_name = Python Application (Staging) 194 | monitor_mode = true 195 | 196 | [newrelic:production] 197 | monitor_mode = true 198 | 199 | # --------------------------------------------------------------------------- 200 | -------------------------------------------------------------------------------- /old/test_server.py: -------------------------------------------------------------------------------- 1 | import app as server 2 | from utils import load_data, locate_member_ids, SQLAlchemy 3 | import models 4 | import unittest 5 | from urlparse import parse_qs 6 | from requests.compat import urlencode, urlparse 7 | import lxml 8 | 9 | def url_for(action, **params): 10 | url = ('/' if action[0] != '/' else '') + action 11 | if params: 12 | url += '?' + urlencode(params, doseq=True) 13 | return url 14 | 15 | # hack for testing 16 | server.url_for = url_for 17 | 18 | id_pelosi = 'P000197' 19 | id_boxer = 'B000711' 20 | id_cardenas = 'C001097' 21 | 22 | 23 | class FlaskrTestCase(unittest.TestCase): 24 | 25 | def setUp(self): 26 | server.app.config.from_object('config.ConfigTesting') 27 | self.db = SQLAlchemy() 28 | self.db.app = server.app 29 | models.setUp(server.app) 30 | self.app = server.app.test_client() 31 | self.campaigns, self.legislators, self.districts = load_data() 32 | 33 | # a default example where just Rep. Nacy Pelosi is called 34 | self.example_params = dict( 35 | campaignId='default', 36 | repIds=[id_pelosi], 37 | userPhone='415-000-1111', 38 | zipcode='95110') 39 | 40 | def post_tree(self, path, **params): 41 | req = self.app.post(url_for(path, **params)) 42 | tree = lxml.etree.fromstring(req.data) 43 | return tree 44 | 45 | 46 | def parse_url(self, urlstring): 47 | url = urlparse(urlstring) 48 | return url, dict((k, v if len(v)>1 else v[0]) 49 | for k, v in parse_qs(url.query).iteritems()) 50 | 51 | def assert_is_xml(self, req): 52 | assert(req.data.startswith('A<=rdenas (C001097) has bad data, unicode warning 203 | if target_individual != None and target_individual != "": 204 | for l in self.legislators: 205 | if l['last_name'] == target_individual and l['state'] == state: 206 | if l['bioguide_id'] in member_ids: 207 | member_ids.remove(l['bioguide_id']) # janky 208 | member_ids.insert(0, l['bioguide_id']) # lol 209 | 210 | if campaign.get('max_calls_to_congress', False): 211 | member_ids = member_ids[0:campaign.get('max_calls_to_congress')] 212 | 213 | # Now handle any exclusions lol 214 | if campaign.get('exclusions_google_spreadsheet_id'): 215 | exclusions = self.get_exclusions(campaign) 216 | for exclusion in exclusions: 217 | exclusion = exclusion.encode('ascii', errors='backslashreplace') 218 | if exclusion in member_ids: 219 | print "Politician %s is on exclusion list!" % exclusion 220 | member_ids = filter(lambda a: a != exclusion, member_ids) 221 | 222 | if campaign.get('extra_first_calls'): 223 | member_ids = self.pick_lucky_recipients(member_ids, campaign, 224 | 'first', campaign.get('number_of_extra_first_calls')) 225 | 226 | if campaign.get('extra_first_call_name') and \ 227 | campaign.get('extra_first_call_num'): 228 | first_call = self.format_special_call( 229 | campaign.get('extra_first_call_name'), 230 | "%d" % campaign.get('extra_first_call_num')) 231 | member_ids.insert(0, first_call) 232 | 233 | if first_call_number and first_call_name: 234 | first_call = self.format_special_call(first_call_name, 235 | first_call_number) 236 | member_ids.insert(0, first_call) 237 | 238 | if campaign.get('extra_last_calls'): 239 | member_ids = self.pick_lucky_recipients(member_ids, campaign, 240 | 'last', campaign.get('number_of_extra_last_calls')) 241 | 242 | if campaign.get('extra_last_call_name') and \ 243 | campaign.get('extra_last_call_num'): 244 | last_call = self.format_special_call( 245 | campaign.get('extra_last_call_name'), 246 | "%d" % campaign.get('extra_last_call_num'), 247 | '', 248 | campaign.get('extra_last_call_intro')) 249 | member_ids.extend([last_call]) 250 | 251 | print member_ids 252 | 253 | return member_ids 254 | 255 | def get_override_values(self, local_districts, campaign): 256 | 257 | overrides = self.get_overrides(campaign) 258 | 259 | states = [d['state'] for d in local_districts] 260 | 261 | for state in states: 262 | override = overrides.get(state) 263 | if override: 264 | override['_STATE_ABBREV'] = state 265 | if self.debug_mode: 266 | print "Found overrides: %s / %s" % (state, str(override)) 267 | return overrides.get(state) 268 | 269 | return None 270 | 271 | def has_special_overrides(self, local_districts, campaign): 272 | 273 | spreadsheet_id = campaign.get('overrides_google_spreadsheet_id', None) 274 | 275 | if spreadsheet_id == None: 276 | return False 277 | 278 | overrides = self.get_overrides(campaign) 279 | 280 | states = [d['state'] for d in local_districts] 281 | 282 | for state in states: 283 | if overrides.get(state): 284 | return True 285 | 286 | return False 287 | 288 | def get_exclusions(self, campaign): 289 | 290 | last_scraped = time.time()-self.exclusion_scrapes.get(campaign['id'], 0) 291 | expired = last_scraped > self.SPREADSHEET_CACHE_TIMEOUT 292 | 293 | if self.exclusions.get(campaign.get('id')) == None or expired: 294 | self.populate_exclusions(campaign) 295 | 296 | return self.exclusions[campaign.get('id')] 297 | 298 | def populate_exclusions(self, campaign): 299 | 300 | spreadsheet_id = campaign.get('exclusions_google_spreadsheet_id', None) 301 | spreadsheet_key = '%s-exclusions-list' % campaign.get('id') 302 | 303 | exclusions_data = self.cache_handler.get(spreadsheet_key, None) 304 | 305 | if exclusions_data == None: 306 | exclusions = self.grab_exclusions_from_google( 307 | spreadsheet_id, 308 | campaign.get('exclusions_spreadsheet_match_field'), 309 | campaign.get('exclusions_spreadsheet_match_value'), 310 | campaign.get('exclusions_spreadsheet_bioguide_col')) 311 | if self.debug_mode: 312 | print "GOT DATA FROM GOOGLE: %s" % str(exclusions) 313 | 314 | self.cache_handler.set( 315 | spreadsheet_key, 316 | json.dumps(exclusions), 317 | self.SPREADSHEET_CACHE_TIMEOUT) 318 | 319 | self.exclusion_scrapes[campaign.get('id')] = time.time() 320 | else: 321 | exclusions = json.loads(exclusions_data) 322 | if self.debug_mode: 323 | print "GOT DATA FROM CACHE: %s" % str(exclusions) 324 | 325 | self.exclusions[campaign.get('id')] = exclusions 326 | 327 | def grab_exclusions_from_google(self, spreadsheet_id, field, val, bioguide): 328 | 329 | url = ('https://spreadsheets.google.com/feeds/list/' 330 | '%s/default/public/values?alt=json') % spreadsheet_id 331 | 332 | response = urllib2.urlopen(url).read() 333 | data = json.loads(response) 334 | 335 | exclusions = [] 336 | 337 | for row in data['feed']['entry']: 338 | if row.get('gsx$%s' % field) and row['gsx$%s' % field].get('$t') \ 339 | and row['gsx$%s' % field]['$t'] == val \ 340 | and row.get('gsx$%s' % bioguide) \ 341 | and row['gsx$%s' % bioguide].get('$t'): 342 | exclusions.append(str(row['gsx$%s' % bioguide]['$t']).encode( 343 | 'ascii', errors='backslashreplace')) 344 | 345 | return exclusions 346 | 347 | 348 | def get_overrides(self, campaign): 349 | 350 | # we expire whatever we're holding in memory after the timeout 351 | last_scraped = time.time()-self.scrape_times.get(campaign.get('id'), 0) 352 | expired = last_scraped > self.SPREADSHEET_CACHE_TIMEOUT 353 | 354 | if self.overrides_data.get(campaign.get('id')) == None or expired: 355 | self.populate_overrides(campaign) 356 | 357 | return self.overrides_data[campaign.get('id')] 358 | 359 | def populate_overrides(self, campaign): 360 | 361 | spreadsheet_id = campaign.get('overrides_google_spreadsheet_id', None) 362 | spreadsheet_key = '%s-spreadsheet-data' % campaign.get('id') 363 | 364 | overrides_data = self.cache_handler.get(spreadsheet_key, None) 365 | 366 | if overrides_data == None: 367 | overrides = self.grab_overrides_from_google(spreadsheet_id) 368 | if self.debug_mode: 369 | print "GOT DATA FROM GOOGLE: %s" % str(overrides) 370 | 371 | self.cache_handler.set( 372 | spreadsheet_key, 373 | json.dumps(overrides), 374 | self.SPREADSHEET_CACHE_TIMEOUT) 375 | 376 | self.scrape_times[campaign.get('id')] = time.time() 377 | else: 378 | overrides = json.loads(overrides_data) 379 | if self.debug_mode: 380 | print "GOT DATA FROM CACHE: %s" % str(overrides) 381 | 382 | self.overrides_data[campaign.get('id')] = overrides 383 | 384 | def grab_overrides_from_google(self, spreadsheet_id): 385 | 386 | url = ('https://spreadsheets.google.com/feeds/list/' 387 | '%s/default/public/values?alt=json') % spreadsheet_id 388 | 389 | response = urllib2.urlopen(url).read() 390 | data = json.loads(response) 391 | 392 | def is_true(val): 393 | return True if val == "TRUE" else False 394 | 395 | overrides = {} 396 | 397 | for row in data['feed']['entry']: 398 | 399 | state = row['gsx$state']['$t'] 400 | target_senate = is_true(row['gsx$targetsenate']['$t']) 401 | target_house = is_true(row['gsx$targethouse']['$t']) 402 | target_house_first = is_true(row['gsx$targethousefirst']['$t']) 403 | individual = row['gsx$optionaltargetindividualfirstlastname']['$t'] 404 | first_call_name = row['gsx$optionalextrafirstcallname']['$t'] 405 | first_call_number = row['gsx$optionalextrafirstcallnumber']['$t'] 406 | 407 | overrides[state] = { 408 | 'target_senate': target_senate, 409 | 'target_house': target_house, 410 | 'target_house_first': target_house_first, 411 | 'target_individual': individual, 412 | 'first_call_name': first_call_name, 413 | 'first_call_number': first_call_number 414 | } 415 | 416 | return overrides 417 | 418 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # JL HACK ~ CRITICAL: Add missing _sslwrap to Python to maintain gevent compat 2 | # Please see https://github.com/gevent/gevent/issues/477 3 | # ------------------------------------------------------------------------------ 4 | # Re-add sslwrap to Python 2.7.9 5 | import inspect 6 | __ssl__ = __import__('ssl') 7 | 8 | try: 9 | _ssl = __ssl__._ssl 10 | except AttributeError: 11 | _ssl = __ssl__._ssl2 12 | 13 | 14 | def new_sslwrap(sock, server_side=False, keyfile=None, certfile=None, cert_reqs=__ssl__.CERT_NONE, ssl_version=__ssl__.PROTOCOL_SSLv23, ca_certs=None, ciphers=None): 15 | context = __ssl__.SSLContext(ssl_version) 16 | context.verify_mode = cert_reqs or __ssl__.CERT_NONE 17 | if ca_certs: 18 | context.load_verify_locations(ca_certs) 19 | if certfile: 20 | context.load_cert_chain(certfile, keyfile) 21 | if ciphers: 22 | context.set_ciphers(ciphers) 23 | 24 | caller_self = inspect.currentframe().f_back.f_locals['self'] 25 | return context._wrap_socket(sock, server_side=server_side, ssl_sock=caller_self) 26 | 27 | if not hasattr(_ssl, 'sslwrap'): 28 | _ssl.sslwrap = new_sslwrap 29 | # ------------------------------------------------------------------------------ 30 | 31 | from gevent.monkey import patch_all 32 | patch_all() 33 | 34 | import random 35 | import urlparse 36 | import json 37 | 38 | from datetime import datetime, timedelta 39 | 40 | import pystache 41 | import twilio.twiml 42 | 43 | import urllib2 44 | import requests 45 | 46 | from flask import (abort, after_this_request, Flask, request, render_template, 47 | url_for) 48 | from flask_cache import Cache 49 | from flask_jsonpify import jsonify 50 | from raven.contrib.flask import Sentry 51 | from twilio import TwilioRestException 52 | 53 | from models import db, aggregate_stats, log_call, call_count, call_list 54 | from political_data import PoliticalData 55 | from cache_handler import CacheHandler 56 | from fftf_leaderboard import FFTFLeaderboard 57 | from access_control_decorator import crossdomain, requires_auth 58 | 59 | try: 60 | from throttle import Throttle 61 | throttle = Throttle() 62 | except ImportError: 63 | throttle = None 64 | 65 | app = Flask(__name__) 66 | 67 | app.config.from_object('config.ConfigProduction') 68 | 69 | cache = Cache(app, config={'CACHE_TYPE': 'simple'}) 70 | sentry = Sentry(app) 71 | 72 | # db.init_app(app) # JL HACK ~ disable mysql 73 | 74 | # Optional Redis cache, for caching Google spreadsheet campaign overrides 75 | cache_handler = CacheHandler(app.config['REDIS_URL']) 76 | 77 | # FFTF Leaderboard handler. Only used if FFTF Leadboard params are passed in 78 | leaderboard = FFTFLeaderboard(app.debug, app.config['FFTF_LB_ASYNC_POOL_SIZE'], 79 | app.config['FFTF_CALL_LOG_API_KEY']) 80 | 81 | call_methods = ['GET', 'POST'] 82 | 83 | data = PoliticalData(cache_handler, app.debug) 84 | 85 | print "Call Congress is starting up!" 86 | 87 | def make_cache_key(*args, **kwargs): 88 | path = request.path 89 | args = str(hash(frozenset(request.args.items()))) 90 | 91 | return (path + args).encode('utf-8') 92 | 93 | 94 | def play_or_say(resp_or_gather, msg_template, **kwds): 95 | # take twilio response and play or say a mesage 96 | # can use mustache templates to render keyword arguments 97 | msg = pystache.render(msg_template, kwds) 98 | 99 | if msg.startswith('http'): 100 | resp_or_gather.play(msg) 101 | elif msg: 102 | resp_or_gather.say(msg) 103 | 104 | 105 | def full_url_for(route, **kwds): 106 | return urlparse.urljoin(app.config['APPLICATION_ROOT'], 107 | url_for(route, **kwds)) 108 | 109 | 110 | def parse_params(r): 111 | 112 | params = { 113 | 'userPhone': r.values.get('userPhone', r.values.get('From')), 114 | 'campaignId': r.values.get('campaignId', 'default'), 115 | 'zipcode': r.values.get('zipcode', None), 116 | 'repIds': r.values.getlist('repIds'), 117 | 118 | # only used for campaigns of infinite_loop 119 | 'saved_zipcode': r.values.get('saved_zipcode', None), 120 | 121 | 'ip_address': r.values.get('ip_address', None), 122 | 123 | # optional values for Fight for the Future org tracking 124 | 'org': r.values.getlist('org'), 125 | 126 | # optional values for Fight for the Future Leaderboards 127 | # if present, these add extra logging functionality in call_complete 128 | 'fftfCampaign': r.values.get('fftfCampaign'), 129 | 'fftfReferer': r.values.get('fftfReferer'), 130 | 'fftfSession': r.values.get('fftfSession') 131 | } 132 | 133 | # lookup campaign by ID 134 | campaign = data.get_campaign(params['campaignId']) 135 | 136 | if not campaign: 137 | return None, None 138 | 139 | # add repIds to the parameter set, if spec. by the campaign 140 | if campaign.get('repIds', None): 141 | if isinstance(campaign['repIds'], basestring): 142 | params['repIds'] = [campaign['repIds']] 143 | else: 144 | params['repIds'] = campaign['repIds'] 145 | 146 | if campaign.get('randomize_order', False): 147 | random.shuffle(params['repIds']) 148 | 149 | if params['userPhone']: 150 | params['userPhone'] = params['userPhone'].replace('-', '') 151 | 152 | # get representative's id by zip code 153 | if params['zipcode']: 154 | params['repIds'] = data.locate_member_ids( 155 | params['zipcode'], campaign) 156 | 157 | if campaign.get('infinite_loop', False) == True: 158 | print "Saving zipcode for the future lol" 159 | params['saved_zipcode'] = params['zipcode'] 160 | 161 | # delete the zipcode, since the repIds are in a particular order and 162 | # will be passed around from endpoint to endpoint hereafter anyway. 163 | del params['zipcode'] 164 | 165 | if params['ip_address'] == None: 166 | params['ip_address'] = r.headers.get('x-forwarded-for', r.remote_addr) 167 | 168 | if "," in params['ip_address']: 169 | ips = params['ip_address'].split(", ") 170 | params['ip_address'] = ips[0] 171 | 172 | if 'random_choice' in campaign: 173 | # pick a single random choice among a selected set of members 174 | params['repIds'] = [random.choice(campaign['random_choice'])] 175 | 176 | if app.debug: 177 | print params 178 | 179 | return params, campaign 180 | 181 | 182 | def intro_zip_gather(params, campaign): 183 | resp = twilio.twiml.Response() 184 | 185 | play_or_say(resp, campaign['msg_intro']) 186 | 187 | return zip_gather(resp, params, campaign) 188 | 189 | 190 | def zip_gather(resp, params, campaign): 191 | with resp.gather(numDigits=5, method="POST", timeout=30, 192 | action=url_for("zip_parse", **params)) as g: 193 | play_or_say(g, campaign['msg_ask_zip']) 194 | 195 | return str(resp) 196 | 197 | 198 | def make_calls(params, campaign): 199 | """ 200 | Connect a user to a sequence of congress members. 201 | Required params: campaignId, repIds 202 | Optional params: zipcode, fftfCampaign, fftfReferer, fftfSession 203 | """ 204 | resp = twilio.twiml.Response() 205 | 206 | selection = request.values.get('Digits', '') 207 | 208 | if selection == "1" and campaign.get('press_1_callback'): 209 | 210 | url = campaign.get('press_1_callback').replace("{phone}", 211 | params['userPhone']) 212 | 213 | callback_response = get_external_url(url) 214 | print "--- EXTERNAL CALLBACK RESPONSE: %s" % callback_response 215 | 216 | play_or_say(resp, campaign['msg_press_1']) 217 | 218 | resp.redirect(url_for('_make_calls', **params)) 219 | return str(resp) 220 | 221 | if selection == "9" and campaign.get('press_9_optout'): 222 | 223 | url = pystache.render(campaign.get('press_9_optout'), 224 | phone=params['userPhone']) 225 | 226 | callback_response = get_external_url(url) 227 | print "--- OPT OUT RESPONSE: %s" % callback_response 228 | 229 | play_or_say(resp, campaign['msg_opt_out']) 230 | 231 | return str(resp) 232 | 233 | n_reps = len(params['repIds']) 234 | 235 | play_or_say(resp, campaign['msg_call_block_intro'], 236 | n_reps=n_reps, many_reps=n_reps > 1) 237 | 238 | resp.redirect(url_for('make_single_call', call_index=0, **params)) 239 | 240 | return str(resp) 241 | 242 | def get_external_url(url_or_json): 243 | """ 244 | Used to call an external URL callback, used for the press_1_callback or 245 | press_9_optout to issue some kind of remote web request around the call, 246 | typically used to schedule a recurring phone call at a later date, outside 247 | of this app. 248 | """ 249 | try: 250 | data = json.loads(url_or_json) 251 | r = requests.post(data["url"], json=data) 252 | response = r.status_code 253 | except: 254 | user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' 255 | headers = { 'User-Agent' : user_agent } 256 | request = urllib2.Request(url_or_json, '', headers) 257 | response = urllib2.urlopen(request).read() 258 | return response 259 | 260 | 261 | @app.route('/make_calls', methods=call_methods) 262 | def _make_calls(): 263 | params, campaign = parse_params(request) 264 | 265 | if not params or not campaign: 266 | abort(404) 267 | 268 | return make_calls(params, campaign) 269 | 270 | 271 | @app.route('/create', methods=call_methods) 272 | @crossdomain(origin='*') 273 | def call_user(): 274 | """ 275 | Makes a phone call to a user. 276 | Required Params: 277 | userPhone 278 | campaignId 279 | Optional Params: 280 | zipcode 281 | repIds 282 | fftfCampaign 283 | fftfReferer 284 | fftfSession 285 | """ 286 | # parse the info needed to make the call 287 | params, campaign = parse_params(request) 288 | 289 | """ 290 | if throttle and throttle.throttle(campaign.get('id'), params['userPhone'], 291 | params['ip_address'], request.values.get('throttle_key')): 292 | abort(429) # Too Many Requests 293 | """ 294 | 295 | if not params or not campaign: 296 | abort(404) 297 | 298 | # initiate the call 299 | try: 300 | call = app.config['TW_CLIENT'].calls.create( 301 | to=params['userPhone'], 302 | from_=random.choice(campaign['numbers']), 303 | url=full_url_for("connection", **params), 304 | if_machine='Hangup' if campaign.get('call_human_check') else None, 305 | timeLimit=app.config['TW_TIME_LIMIT'], 306 | timeout=app.config['TW_TIMEOUT'], 307 | status_callback=full_url_for("call_complete_status", **params)) 308 | 309 | result = jsonify(message=call.status, debugMode=app.debug) 310 | result.status_code = 200 if call.status != 'failed' else 500 311 | except TwilioRestException, err: 312 | print err.msg 313 | result = jsonify(message=err.msg.split(':')[1].strip()) 314 | result.status_code = 200 315 | 316 | return result 317 | 318 | 319 | @app.route('/connection', methods=call_methods) 320 | @crossdomain(origin='*') 321 | def connection(): 322 | """ 323 | Call handler to connect a user with their congress person(s). 324 | Required Params: 325 | campaignId 326 | Optional Params: 327 | zipcode 328 | repIds (if not present go to incoming_call flow and asked for zipcode) 329 | fftfCampaign 330 | fftfReferer 331 | fftfSession 332 | """ 333 | params, campaign = parse_params(request) 334 | 335 | if not params or not campaign: 336 | abort(404) 337 | 338 | if params['repIds']: 339 | resp = twilio.twiml.Response() 340 | 341 | play_or_say(resp, campaign['msg_intro']) 342 | 343 | if campaign.get('skip_star_confirm'): 344 | resp.redirect(url_for('_make_calls', **params)) 345 | 346 | return str(resp) 347 | 348 | action = url_for("_make_calls", **params) 349 | 350 | with resp.gather(numDigits=1, method="POST", timeout=30, 351 | action=action) as g: 352 | play_or_say(g, campaign['msg_intro_confirm']) 353 | 354 | return str(resp) 355 | else: 356 | return intro_zip_gather(params, campaign) 357 | 358 | 359 | @app.route('/incoming_call', methods=call_methods) 360 | def incoming_call(): 361 | """ 362 | Handles incoming calls to the twilio numbers. 363 | Required Params: campaignId 364 | Optional Params: fftfCampaign, fftfReferer, fftfSession 365 | 366 | Each Twilio phone number needs to be configured to point to: 367 | server.com/incoming_call?campaignId=12345 368 | from twilio.com/user/account/phone-numbers/incoming 369 | """ 370 | params, campaign = parse_params(request) 371 | 372 | if not params or not campaign: 373 | abort(404) 374 | 375 | if params['repIds']: 376 | return connection() 377 | else: 378 | return intro_zip_gather(params, campaign) 379 | 380 | 381 | @app.route("/zip_parse", methods=call_methods) 382 | def zip_parse(): 383 | """ 384 | Handle a zip code entered by the user. 385 | Required Params: campaignId, Digits 386 | """ 387 | params, campaign = parse_params(request) 388 | 389 | if not params or not campaign: 390 | abort(404) 391 | 392 | zipcode = request.values.get('Digits', '') 393 | rep_ids = data.locate_member_ids(zipcode, campaign) 394 | 395 | if app.debug: 396 | print 'DEBUG: zipcode = {}'.format(zipcode) 397 | 398 | if not rep_ids: 399 | resp = twilio.twiml.Response() 400 | play_or_say(resp, campaign['msg_invalid_zip']) 401 | 402 | return zip_gather(resp, params, campaign) 403 | 404 | params['zipcode'] = zipcode 405 | params['repIds'] = rep_ids 406 | 407 | return make_calls(params, campaign) 408 | 409 | 410 | @app.route('/make_single_call', methods=call_methods) 411 | def make_single_call(): 412 | params, campaign = parse_params(request) 413 | 414 | if not params or not campaign: 415 | abort(404) 416 | 417 | resp = twilio.twiml.Response() 418 | 419 | # return str(resp) # JL HACK ~ disable calls 420 | 421 | i = int(request.values.get('call_index', 0)) 422 | params['call_index'] = i 423 | 424 | if "S_" in params['repIds'][i]: 425 | 426 | special = json.loads(params['repIds'][i].replace("S_", "")) 427 | to_phone = special['n'] # "n" is for "number" 428 | full_name = special['p'] # "p" is for "politician" 429 | 430 | if full_name == 'SKIP': 431 | pass 432 | elif special.get('i'): # "i" is for "intro" 433 | play_or_say(resp, special.get('i')) 434 | else: 435 | office = special.get('o', '') # "o" is for "office" 436 | play_or_say(resp, campaign.get('msg_special_call_intro', 437 | campaign['msg_rep_intro']), name=full_name, office=office) 438 | 439 | else: 440 | 441 | member = [l for l in data.legislators 442 | if l['bioguide_id'] == params['repIds'][i]][0] 443 | to_phone = member['phone'] 444 | title = "Representative" if member['title'] == 'Rep' else 'Senator' 445 | full_name = unicode("{} {} {}".format( 446 | title, member['first_name'], member['last_name']), 'utf8') 447 | title = member['title'] 448 | state = member['state'] 449 | 450 | if 'voted_with_list' in campaign and \ 451 | params['repIds'][i] in campaign['voted_with_list']: 452 | play_or_say( 453 | resp, campaign['msg_rep_intro_voted_with'], name=full_name, title=title, state=state) 454 | else: 455 | play_or_say(resp, campaign['msg_rep_intro'], name=full_name, title=title, state=state) 456 | 457 | if campaign.get('fftf_log_extra_data'): 458 | leaderboard.log_extra_data(params, campaign, request, to_phone, i) 459 | 460 | if app.debug: 461 | print u'DEBUG: Call #{}, {} ({}) from {} : make_single_call()'.format(i, 462 | full_name.encode('ascii', 'ignore'), to_phone, params['userPhone']) 463 | 464 | if not full_name == "SKIP": 465 | resp.dial(to_phone, callerId=params['userPhone'], 466 | timeLimit=app.config['TW_TIME_LIMIT'], 467 | timeout=app.config['TW_TIMEOUT'], hangupOnStar=True, 468 | action=url_for('call_complete', **params)) 469 | else: 470 | resp.redirect(url_for('call_complete', **params)) 471 | 472 | return str(resp) 473 | 474 | 475 | @app.route('/call_complete', methods=call_methods) 476 | def call_complete(): 477 | params, campaign = parse_params(request) 478 | 479 | if not params or not campaign: 480 | abort(404) 481 | 482 | # log_call(params, campaign, request) 483 | 484 | # If FFTF Leaderboard params are present, log this call 485 | if params['fftfCampaign'] and params['fftfReferer']: 486 | leaderboard.log_call(params, campaign, request) 487 | 488 | resp = twilio.twiml.Response() 489 | 490 | i = int(request.values.get('call_index', 0)) 491 | 492 | if campaign.get('infinite_loop') and params['saved_zipcode']: 493 | params['zipcode'] = params['saved_zipcode'] 494 | del params['saved_zipcode'] 495 | del params['repIds'] 496 | resp.redirect(url_for('make_single_call', **params)) 497 | 498 | elif i == len(params['repIds']) - 1: 499 | # thank you for calling message 500 | play_or_say(resp, campaign['msg_final_thanks']) 501 | 502 | # If FFTF Leaderboard params are present, log the call completion status 503 | if params['fftfCampaign'] and params['fftfReferer']: 504 | leaderboard.log_complete(params, campaign, request) 505 | else: 506 | # call the next representative 507 | params['call_index'] = i + 1 # increment the call counter 508 | 509 | play_or_say(resp, campaign['msg_between_thanks']) 510 | 511 | resp.redirect(url_for('make_single_call', **params)) 512 | 513 | return str(resp) 514 | 515 | 516 | @app.route('/call_complete_status', methods=call_methods) 517 | def call_complete_status(): 518 | # asynch callback from twilio on call complete 519 | params, _ = parse_params(request) 520 | 521 | if not params: 522 | abort(404) 523 | 524 | return jsonify({ 525 | 'phoneNumber': request.values.get('To', ''), 526 | 'callStatus': request.values.get('CallStatus', 'unknown'), 527 | 'repIds': params['repIds'], 528 | 'campaignId': params['campaignId'], 529 | 'fftfCampaign': params['fftfCampaign'], 530 | 'fftfReferer': params['fftfReferer'], 531 | 'fftfSession': params['fftfSession'] 532 | }) 533 | 534 | @app.route('/hello') 535 | def hello(): 536 | return "OHAI" 537 | 538 | 539 | @app.route('/demo') 540 | def demo(): 541 | return render_template('demo.html') 542 | 543 | 544 | @cache.cached(timeout=60) 545 | @app.route('/count') 546 | def count(): 547 | @after_this_request 548 | def add_expires_header(response): 549 | expires = datetime.utcnow() 550 | expires = expires + timedelta(seconds=60) 551 | expires = datetime.strftime(expires, "%a, %d %b %Y %H:%M:%S GMT") 552 | 553 | response.headers['Expires'] = expires 554 | 555 | return response 556 | 557 | campaign = request.values.get('campaign', 'default') 558 | 559 | # return jsonify(campaign=campaign, count=call_count(campaign)) 560 | return jsonify('DISABLED') # JL HACK ~ disable mysql 561 | 562 | 563 | @cache.cached(timeout=60, key_prefix=make_cache_key) 564 | @app.route('/recent_calls') 565 | def recent_calls(): 566 | @after_this_request 567 | def add_expires_header(response): 568 | expires = datetime.utcnow() 569 | expires = expires + timedelta(seconds=60) 570 | expires = datetime.strftime(expires, "%a, %d %b %Y %H:%M:%S GMT") 571 | 572 | response.headers['Expires'] = expires 573 | 574 | return response 575 | 576 | campaign = request.values.get('campaign', 'default') 577 | since = request.values.get('since', datetime.utcnow() - timedelta(days=1)) 578 | limit = request.values.get('limit', 50) 579 | 580 | # calls = call_list(campaign, since, limit) 581 | # serialized_calls = [] 582 | # if not calls: 583 | # return jsonify(campaign=campaign, calls=[], count=0) 584 | # for c in calls: 585 | # s = dict(timestamp = c.timestamp.isoformat(), 586 | # number = '%s-%s-XXXX' % (c.areacode, c.exchange)) 587 | # member = data.get_legislator_by_id(c.member_id) 588 | # if member: 589 | # s['member'] = dict( 590 | # title=member['title'], 591 | # first_name=member['first_name'], 592 | # last_name=member['last_name'] 593 | # ) 594 | # serialized_calls.append(s) 595 | 596 | # return jsonify(campaign=campaign, calls=serialized_calls, count=len(serialized_calls)) 597 | return jsonify('DISABLED') # JL NOTE ~ disable db 598 | 599 | @app.route('/live') 600 | @requires_auth 601 | def live(): 602 | campaign = request.values.get('campaign', 'default') 603 | return render_template('live.html') 604 | 605 | 606 | @cache.cached(timeout=60, key_prefix=make_cache_key) 607 | @app.route('/stats') 608 | def stats(): 609 | password = request.values.get('password', None) 610 | campaign = request.values.get('campaign', 'default') 611 | 612 | # if password == app.config['SECRET_KEY']: 613 | # return jsonify(aggregate_stats(campaign)) 614 | # else: 615 | # return jsonify(error="access denied") 616 | 617 | return jsonify(error="access denied") # JL HACK ~ disable mysql 618 | 619 | 620 | if __name__ == '__main__': 621 | # load the debugger config 622 | app.config.from_object('config.Config') 623 | app.run(host='0.0.0.0') 624 | -------------------------------------------------------------------------------- /static/js/lodash.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Lo-Dash 1.3.1 (Custom Build) lodash.com/license 4 | * Build: `lodash modern -o ./dist/lodash.js` 5 | * Underscore.js 1.4.4 underscorejs.org/LICENSE 6 | */ 7 | ;!function(n){function t(n,t,e){e=(e||0)-1;for(var r=n.length;++et||typeof n=="undefined")return 1;if(ne?0:e);++re?_e(0,a+e):e)||0,a&&typeof a=="number"?o=-1<(ht(n)?n.indexOf(t,e):u(n,t,e)):d(n,function(n){return++ra&&(a=i) 21 | }}else t=!t&&ht(n)?u:tt.createCallback(t,e),wt(n,function(n,e,u){e=t(n,e,u),e>r&&(r=e,a=n)});return a}function Ot(n,t){var e=-1,r=n?n.length:0;if(typeof r=="number")for(var u=Mt(r);++earguments.length;t=tt.createCallback(t,r,4);var a=-1,o=n.length;if(typeof o=="number")for(u&&(e=n[++a]);++aarguments.length; 22 | if(typeof u!="number")var o=Se(n),u=o.length;return t=tt.createCallback(t,r,4),wt(n,function(r,i,f){i=o?o[--u]:--u,e=a?(a=b,n[i]):t(e,n[i],i,f)}),e}function It(n,t,e){var r;t=tt.createCallback(t,e),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e=w&&u===t;if(l){var c=o(i);c?(u=e,i=c):l=b}for(;++ru(i,c)&&f.push(c); 23 | return l&&p(i),f}function Nt(n,t,e){if(n){var r=0,u=n.length;if(typeof t!="number"&&t!=h){var a=-1;for(t=tt.createCallback(t,e);++ar?_e(0,u+r):r||0}else if(r)return r=Ft(n,e),n[r]===e?r:-1;return n?t(n,e,r):-1}function Bt(n,t,e){if(typeof t!="number"&&t!=h){var r=0,u=-1,a=n?n.length:0;for(t=tt.createCallback(t,e);++u>>1,e(n[r])e?0:e);++tl&&(i=n.apply(f,o));else{var e=new Vt;!s&&!m&&(c=e);var r=p-(e-c);0/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:tt}};var Ee=he,Se=de?function(n){return gt(n)?de(n):[]}:Z,Ie={"&":"&","<":"<",">":">",'"':""","'":"'"},Ae=pt(Ie),Ut=ot(function $e(n,t,e){for(var r=-1,u=n?n.length:0,a=[];++r=w&&i===t,g=u||v?f():s;if(v){var y=o(g);y?(i=e,g=y):(v=b,g=u?g:(c(g),s))}for(;++ai(g,h))&&((u||v)&&g.push(h),s.push(y))}return v?(c(g.b),p(g)):u&&c(g),s});return Ht&&Y&&typeof pe=="function"&&(zt=qt(pe,r)),pe=8==je(B+"08")?je:function(n,t){return je(ht(n)?n.replace(F,""):n,t||0)},tt.after=function(n,t){return 1>n?t():function(){return 1>--n?t.apply(this,arguments):void 0 29 | }},tt.assign=X,tt.at=function(n){for(var t=-1,e=ae.apply(Zt,Ce.call(arguments,1)),r=e.length,u=Mt(r);++t=w&&o(a?r[a]:y)}n:for(;++l(b?e(b,h):s(y,h))){for(a=u,(b||y).push(h);--a;)if(b=i[a],0>(b?e(b,h):s(r[a],h)))continue n;g.push(h)}}for(;u--;)(b=i[u])&&p(b);return c(i),c(y),g 33 | },tt.invert=pt,tt.invoke=function(n,t){var e=Ce.call(arguments,2),r=-1,u=typeof t=="function",a=n?n.length:0,o=Mt(typeof a=="number"?a:0);return wt(n,function(n){o[++r]=(u?t:n[t]).apply(n,e)}),o},tt.keys=Se,tt.map=Ct,tt.max=xt,tt.memoize=function(n,t){function e(){var r=e.cache,u=j+(t?t.apply(this,arguments):arguments[0]);return le.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}return e.cache={},e},tt.merge=bt,tt.min=function(n,t,e){var r=1/0,a=r;if(!t&&Ee(n)){e=-1;for(var o=n.length;++er(o,e))&&(a[e]=n)}),a},tt.once=function(n){var t,e;return function(){return t?e:(t=y,e=n.apply(this,arguments),n=h,e)}},tt.pairs=function(n){for(var t=-1,e=Se(n),r=e.length,u=Mt(r);++te?_e(0,r+e):ke(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},tt.mixin=Pt,tt.noConflict=function(){return r._=te,this},tt.parseInt=pe,tt.random=function(n,t){n==h&&t==h&&(t=1),n=+n||0,t==h?(t=n,n=0):t=+t||0; 42 | var e=we();return n%1||t%1?n+ke(e*(t-n+parseFloat("1e-"+((e+"").length-1))),t):n+oe(e*(t-n+1))},tt.reduce=Et,tt.reduceRight=St,tt.result=function(n,t){var e=n?n[t]:g;return vt(e)?n[t]():e},tt.runInContext=v,tt.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Se(n).length},tt.some=It,tt.sortedIndex=Ft,tt.template=function(n,t,e){var r=tt.templateSettings;n||(n=""),e=Q({},e,r);var u,a=Q({},e.imports,r.imports),r=Se(a),a=mt(a),o=0,f=e.interpolate||R,l="__p+='",f=Qt((e.escape||R).source+"|"+f.source+"|"+(f===N?I:R).source+"|"+(e.evaluate||R).source+"|$","g"); 43 | n.replace(f,function(t,e,r,a,f,c){return r||(r=a),l+=n.slice(o,c).replace(q,i),e&&(l+="'+__e("+e+")+'"),f&&(u=y,l+="';"+f+";__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),o=c+t.length,t}),l+="';\n",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(x,""):l).replace(O,"$1").replace(E,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=Gt(r,"return "+l).apply(g,a) 44 | }catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},tt.unescape=function(n){return n==h?"":Xt(n).replace(S,ft)},tt.uniqueId=function(n){var t=++_;return Xt(n==h?"":n)+t},tt.all=_t,tt.any=It,tt.detect=jt,tt.findWhere=jt,tt.foldl=Et,tt.foldr=St,tt.include=dt,tt.inject=Et,d(tt,function(n,t){tt.prototype[t]||(tt.prototype[t]=function(){var t=[this.__wrapped__];return ce.apply(t,arguments),n.apply(tt,t)})}),tt.first=Nt,tt.last=function(n,t,e){if(n){var r=0,u=n.length;if(typeof t!="number"&&t!=h){var a=u; 45 | for(t=tt.createCallback(t,e);a--&&t(n[a],a,n);)r++}else if(r=t,r==h||e)return n[u-1];return s(n,_e(0,u-r))}},tt.take=Nt,tt.head=Nt,d(tt,function(n,t){tt.prototype[t]||(tt.prototype[t]=function(t,e){var r=n(this.__wrapped__,t,e);return t==h||e&&typeof t!="function"?r:new et(r)})}),tt.VERSION="1.3.1",tt.prototype.toString=function(){return Xt(this.__wrapped__)},tt.prototype.value=Kt,tt.prototype.valueOf=Kt,wt(["join","pop","shift"],function(n){var t=Zt[n];tt.prototype[n]=function(){return t.apply(this.__wrapped__,arguments) 46 | }}),wt(["push","reverse","sort","unshift"],function(n){var t=Zt[n];tt.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),wt(["concat","slice","splice"],function(n){var t=Zt[n];tt.prototype[n]=function(){return new et(t.apply(this.__wrapped__,arguments))}}),tt}var g,y=!0,h=null,b=!1,m=[],d=[],_=0,k={},j=+new Date+"",w=75,C=40,x=/\b__p\+='';/g,O=/\b(__p\+=)''\+/g,E=/(__e\(.*?\)|\b__t\))\+'';/g,S=/&(?:amp|lt|gt|quot|#39);/g,I=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,A=/\w*$/,N=/<%=([\s\S]+?)%>/g,$=($=/\bthis\b/)&&$.test(v)&&$,B=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",F=RegExp("^["+B+"]*0+(?=.$)"),R=/($^)/,T=/[&<>"']/g,q=/['\n\r\t\u2028\u2029\\]/g,D="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setImmediate setTimeout".split(" "),z="[object Arguments]",W="[object Array]",P="[object Boolean]",K="[object Date]",M="[object Function]",U="[object Number]",V="[object Object]",G="[object RegExp]",H="[object String]",J={}; 47 | J[M]=b,J[z]=J[W]=J[P]=J[K]=J[U]=J[V]=J[G]=J[H]=y;var L={"boolean":b,"function":y,object:y,number:b,string:b,undefined:b},Q={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},X=L[typeof exports]&&exports,Y=L[typeof module]&&module&&module.exports==X&&module,Z=L[typeof global]&&global;!Z||Z.global!==Z&&Z.window!==Z||(n=Z);var nt=v();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(n._=nt, define(function(){return nt})):X&&!X.nodeType?Y?(Y.exports=nt)._=nt:X._=nt:n._=nt 48 | }(this); -------------------------------------------------------------------------------- /static/demo-roll-call.min.json: -------------------------------------------------------------------------------- 1 | {"votes":[{"details":{"bioguide_id":"Y000064","first_name":"Todd","last_name":"Young","facebook_id":"186203844738421","twitter_id":"RepToddYoung","phone":"202-225-5315"},"vote":["No"]},{"details":{"bioguide_id":"Y000031","first_name":"C.","last_name":"Young","facebook_id":null,"twitter_id":null,"phone":"202-225-5961"},"vote":["No"]},{"details":{"bioguide_id":"Y000033","first_name":"Don","last_name":"Young","facebook_id":"7476269851","twitter_id":"repdonyoung","phone":"202-225-5765"},"vote":["Aye"]},{"details":{"bioguide_id":"Y000065","first_name":"Ted","last_name":"Yoho","facebook_id":"563532937006022","twitter_id":"RepTedYoho","phone":"202-225-5744"},"vote":["Aye"]},{"details":{"bioguide_id":"Y000063","first_name":"Kevin","last_name":"Yoder","facebook_id":"154026694650252","twitter_id":"RepKevinYoder","phone":"202-225-2865"},"vote":["Aye"]},{"details":{"bioguide_id":"Y000062","first_name":"John","last_name":"Yarmuth","facebook_id":"214258646163","twitter_id":"RepJohnYarmuth","phone":"202-225-5401"},"vote":["Aye"]},{"details":{"bioguide_id":"W000810","first_name":"Rob","last_name":"Woodall","facebook_id":"172573036140374","twitter_id":null,"phone":"202-225-4272"},"vote":["No"]},{"details":{"bioguide_id":"W000809","first_name":"Steve","last_name":"Womack","facebook_id":"135898413136490","twitter_id":"Rep_SteveWomack","phone":"202-225-4301"},"vote":["No"]},{"details":{"bioguide_id":"W000672","first_name":"Frank","last_name":"Wolf","facebook_id":"335759964938","twitter_id":"RepWOLFPress","phone":"202-225-5136"},"vote":["No"]},{"details":{"bioguide_id":"W000804","first_name":"Robert","last_name":"Wittman","facebook_id":"38932542633","twitter_id":"RobWittman","phone":"202-225-4261"},"vote":["No"]},{"details":{"bioguide_id":"W000795","first_name":"Joe","last_name":"Wilson","facebook_id":"70150469414","twitter_id":"USRepJoeWilson","phone":"202-225-2452"},"vote":["Aye"]},{"details":{"bioguide_id":"W000808","first_name":"Frederica","last_name":"Wilson","facebook_id":"162327487151626","twitter_id":"RepWilson","phone":"202-225-4506"},"vote":["No"]},{"details":{"bioguide_id":"W000816","first_name":"Roger","last_name":"Williams","facebook_id":"322383274535068","twitter_id":"RepRWilliams","phone":"202-225-9896"},"vote":["Aye"]},{"details":{"bioguide_id":"W000413","first_name":"Ed","last_name":"Whitfield","facebook_id":"91115765425","twitter_id":"repedwhitfield","phone":"202-225-3115"},"vote":["No"]},{"details":{"bioguide_id":"W000796","first_name":"Lynn","last_name":"Westmoreland","facebook_id":"71389451419","twitter_id":"repwestmoreland","phone":"202-225-5901"},"vote":["No"]},{"details":{"bioguide_id":"W000815","first_name":"Brad","last_name":"Wenstrup","facebook_id":"124462944390458","twitter_id":"RepBradWenstrup","phone":"202-225-3164"},"vote":["No"]},{"details":{"bioguide_id":"W000800","first_name":"Peter","last_name":"Welch","facebook_id":"72680720883","twitter_id":"PeterWelch","phone":"202-225-4115"},"vote":["Aye"]},{"details":{"bioguide_id":"W000806","first_name":"Daniel","last_name":"Webster","facebook_id":"188572961157305","twitter_id":"RepWebster","phone":"202-225-2176"},"vote":["No"]},{"details":{"bioguide_id":"W000814","first_name":"Randy","last_name":"Weber","facebook_id":"128891177274584","twitter_id":"TXRandy14","phone":"202-225-2831"},"vote":["Aye"]},{"details":{"bioguide_id":"W000215","first_name":"Henry","last_name":"Waxman","facebook_id":"129514917081997","twitter_id":"WaxmanClimate","phone":"202-225-3976"},"vote":["Aye"]},{"details":{"bioguide_id":"W000207","first_name":"Melvin","last_name":"Watt","facebook_id":"191136754250537","twitter_id":"MelWattNC12","phone":"202-225-1510"},"vote":["Aye"]},{"details":{"bioguide_id":"W000187","first_name":"Maxine","last_name":"Waters","facebook_id":"117483585008","twitter_id":"MaxineWaters","phone":"202-225-2201"},"vote":["Aye"]},{"details":{"bioguide_id":"W000797","first_name":"Debbie","last_name":"Wasserman Schultz","facebook_id":"88904724121","twitter_id":"RepDWStweets","phone":"202-225-7931"},"vote":["No"]},{"details":{"bioguide_id":"W000799","first_name":"Timothy","last_name":"Walz","facebook_id":null,"twitter_id":"RepTimWalz","phone":"202-225-2472"},"vote":["Aye"]},{"details":{"bioguide_id":"W000813","first_name":"Jackie","last_name":"Walorski","facebook_id":"466876036704525","twitter_id":"RepWalorski","phone":"202-225-3915"},"vote":["No"]},{"details":{"bioguide_id":"W000791","first_name":"Greg","last_name":"Walden","facebook_id":"313301365382225","twitter_id":"repgregwalden","phone":"202-225-6730"},"vote":["No"]},{"details":{"bioguide_id":"W000798","first_name":"Tim","last_name":"Walberg","facebook_id":"187654104587692","twitter_id":"RepWalberg","phone":"202-225-6276"},"vote":["No"]},{"details":{"bioguide_id":"W000812","first_name":"Ann","last_name":"Wagner","facebook_id":"215485388588143","twitter_id":"RepAnnWagner","phone":"202-225-1621"},"vote":["No"]},{"details":{"bioguide_id":"V000108","first_name":"Peter","last_name":"Visclosky","facebook_id":"118723661514709","twitter_id":"repvisclosky","phone":"202-225-2461"},"vote":["No"]},{"details":{"bioguide_id":"V000081","first_name":"Nydia","last_name":"Velázquez","facebook_id":"8037068318","twitter_id":"NydiaVelazquez","phone":"202-225-2361"},"vote":["Aye"]},{"details":{"bioguide_id":"V000132","first_name":"Filemon","last_name":"Vela","facebook_id":"510462622331477","twitter_id":"RepFilemonVela","phone":"202-225-9901"},"vote":["Aye"]},{"details":{"bioguide_id":"V000131","first_name":"Marc","last_name":"Veasey","facebook_id":"394849110600016","twitter_id":"RepVeasey","phone":"202-225-9897"},"vote":["No"]},{"details":{"bioguide_id":"V000130","first_name":"Juan","last_name":"Vargas","facebook_id":"176942192453747","twitter_id":"RepJuanVargas","phone":"202-225-8045"},"vote":["No"]},{"details":{"bioguide_id":"V000128","first_name":"Chris","last_name":"Van Hollen","facebook_id":"109304033877","twitter_id":"chrisvanhollen","phone":"202-225-5341"},"vote":["No"]},{"details":{"bioguide_id":"V000129","first_name":"David","last_name":"Valadao","facebook_id":"105596689621089","twitter_id":"RepDavidValadao","phone":"202-225-4695"},"vote":["No"]},{"details":{"bioguide_id":"U000031","first_name":"Fred","last_name":"Upton","facebook_id":"212027388827797","twitter_id":"RepFredUpton","phone":"202-225-3761"},"vote":["No"]},{"details":{"bioguide_id":"T000463","first_name":"Michael","last_name":"Turner","facebook_id":"9275036647","twitter_id":"RepMikeTurner","phone":"202-225-6465"},"vote":["No"]},{"details":{"bioguide_id":"T000465","first_name":"Niki","last_name":"Tsongas","facebook_id":"131079823624873","twitter_id":"nikiinthehouse","phone":"202-225-3411"},"vote":["Aye"]},{"details":{"bioguide_id":"T000469","first_name":"Paul","last_name":"Tonko","facebook_id":"30671144824","twitter_id":"PaulTonko","phone":"202-225-5076"},"vote":["Aye"]},{"details":{"bioguide_id":"T000468","first_name":"Dina","last_name":"Titus","facebook_id":"120660834778561","twitter_id":"repdinatitus","phone":"202-225-5965"},"vote":["No"]},{"details":{"bioguide_id":"T000470","first_name":"Scott","last_name":"Tipton","facebook_id":"191428784201721","twitter_id":"RepTipton","phone":"202-225-4761"},"vote":["Aye"]},{"details":{"bioguide_id":"T000266","first_name":"John","last_name":"Tierney","facebook_id":"203760666318846","twitter_id":"RepTierney","phone":"202-225-8020"},"vote":["Aye"]},{"details":{"bioguide_id":"T000462","first_name":"Patrick","last_name":"Tiberi","facebook_id":"90452932937","twitter_id":"tiberipress","phone":"202-225-5355"},"vote":["No"]},{"details":{"bioguide_id":"T000238","first_name":"Mac","last_name":"Thornberry","facebook_id":"7760165627","twitter_id":"MacTXPress","phone":"202-225-3706"},"vote":["No"]},{"details":{"bioguide_id":"T000467","first_name":"Glenn","last_name":"Thompson","facebook_id":"14463006747","twitter_id":"CongressmanGT","phone":"202-225-5121"},"vote":["Aye"]},{"details":{"bioguide_id":"T000193","first_name":"Bennie","last_name":"Thompson","facebook_id":"7259193379","twitter_id":"BennieGThompson","phone":"202-225-5876"},"vote":["Aye"]},{"details":{"bioguide_id":"T000460","first_name":"Mike","last_name":"Thompson","facebook_id":"7109195747","twitter_id":"RepThompson","phone":"202-225-3311"},"vote":["No"]},{"details":{"bioguide_id":"T000459","first_name":"Lee","last_name":"Terry","facebook_id":"393886975960","twitter_id":"LEETERRYNE","phone":"202-225-4155"},"vote":["No"]},{"details":{"bioguide_id":"T000472","first_name":"Mark","last_name":"Takano","facebook_id":"262447300551014","twitter_id":"RepMarkTakano","phone":"202-225-2305"},"vote":["Aye"]},{"details":{"bioguide_id":"S001193","first_name":"Eric","last_name":"Swalwell","facebook_id":"450130878375355","twitter_id":"repswalwell","phone":"202-225-5065"},"vote":["Aye"]},{"details":{"bioguide_id":"S001188","first_name":"Marlin","last_name":"Stutzman","facebook_id":"109067049164902","twitter_id":"RepStutzman","phone":"202-225-4436"},"vote":["No"]},{"details":{"bioguide_id":"S000937","first_name":"Steve","last_name":"Stockman","facebook_id":"316293171806077","twitter_id":"SteveStockmanTX","phone":"202-225-1555"},"vote":["Aye"]},{"details":{"bioguide_id":"S001187","first_name":"Steve","last_name":"Stivers","facebook_id":"116058275133542","twitter_id":"RepSteveStivers","phone":"202-225-2015"},"vote":["No"]},{"details":{"bioguide_id":"S001192","first_name":"Chris","last_name":"Stewart","facebook_id":"242042855928904","twitter_id":"repchrisstewart","phone":"202-225-9730"},"vote":["Aye"]},{"details":{"bioguide_id":"S001175","first_name":"Jackie","last_name":"Speier","facebook_id":"99332606976","twitter_id":"repspeier","phone":"202-225-3531"},"vote":["Aye"]},{"details":{"bioguide_id":"S001186","first_name":"Steve","last_name":"Southerland","facebook_id":"156234611092438","twitter_id":"Rep_Southerland","phone":"202-225-5235"},"vote":["Aye"]},{"details":{"bioguide_id":"S000510","first_name":"Adam","last_name":"Smith","facebook_id":"288586617834523","twitter_id":"Rep_Adam_Smith","phone":"202-225-8901"},"vote":["No"]},{"details":{"bioguide_id":"S000583","first_name":"Lamar","last_name":"Smith","facebook_id":"107736785195","twitter_id":"LamarSmithTX21","phone":"202-225-4236"},"vote":["No"]},{"details":{"bioguide_id":"S000522","first_name":"Christopher","last_name":"Smith","facebook_id":null,"twitter_id":null,"phone":"202-225-3765"},"vote":["Aye"]},{"details":{"bioguide_id":"S001172","first_name":"Adrian","last_name":"Smith","facebook_id":null,"twitter_id":"RepAdrianSmith","phone":"202-225-6435"},"vote":["No"]},{"details":{"bioguide_id":"S001195","first_name":"Jason","last_name":"Smith","facebook_id":"544741092252764","phone":"202-225-4404"},"vote":["Aye"]},{"details":{"bioguide_id":"S000480","first_name":"Louise","last_name":"Slaughter","facebook_id":"82424647700","twitter_id":"louiseslaughter","phone":"202-225-3615"},"vote":["No"]},{"details":{"bioguide_id":"S001165","first_name":"Albio","last_name":"Sires","facebook_id":"81058818750","twitter_id":"Rep_Albio_Sires","phone":"202-225-7919"},"vote":["No"]},{"details":{"bioguide_id":"S001191","first_name":"Kyrsten","last_name":"Sinema","facebook_id":"233846963416149","twitter_id":"RepSinema","phone":"202-225-9888"},"vote":["No"]},{"details":{"bioguide_id":"S001148","first_name":"Michael","last_name":"Simpson","facebook_id":"96007744606","twitter_id":"CongMikeSimpson","phone":"202-225-5531"},"vote":["No"]},{"details":{"bioguide_id":"S001154","first_name":"Bill","last_name":"Shuster","facebook_id":"54386677806","twitter_id":"RepBillShuster","phone":"202-225-2431"},"vote":["No"]},{"details":{"bioguide_id":"S000364","first_name":"John","last_name":"Shimkus","facebook_id":"123916254317516","twitter_id":"RepShimkus","phone":"202-225-5271"},"vote":["No"]},{"details":{"bioguide_id":"S000344","first_name":"Brad","last_name":"Sherman","facebook_id":"63158229861","twitter_id":"BradSherman","phone":"202-225-5911"},"vote":["Aye"]},{"details":{"bioguide_id":"S001170","first_name":"Carol","last_name":"Shea-Porter","facebook_id":"422460677824474","twitter_id":"RepSheaPorter","phone":"202-225-5456"},"vote":["Aye"]},{"details":{"bioguide_id":"S001185","first_name":"Terri","last_name":"Sewell","facebook_id":"117758198297225","twitter_id":"RepTerriSewell","phone":"202-225-2665"},"vote":["No"]},{"details":{"bioguide_id":"S000250","first_name":"Pete","last_name":"Sessions","facebook_id":"367963843082","twitter_id":"petesessions","phone":"202-225-2231"},"vote":["No"]},{"details":{"bioguide_id":"S000248","first_name":"José","last_name":"Serrano","facebook_id":"273446508512","twitter_id":"repjoseserrano","phone":"202-225-4361"},"vote":["Aye"]},{"details":{"bioguide_id":"S000244","first_name":"F.","last_name":"Sensenbrenner","facebook_id":"RepSensenbrenner","twitter_id":"JimPressOffice","phone":"202-225-5101"},"vote":["Aye"]},{"details":{"bioguide_id":"S001157","first_name":"David","last_name":"Scott","facebook_id":"113303673339","twitter_id":"repdavidscott","phone":"202-225-2939"},"vote":["No"]},{"details":{"bioguide_id":"S001189","first_name":"Austin","last_name":"Scott","facebook_id":"131177916946914","twitter_id":"AustinScottGA08","phone":"202-225-6531"},"vote":["No"]},{"details":{"bioguide_id":"S000185","first_name":"Robert","last_name":"Scott","facebook_id":"123839200978190","twitter_id":"repbobbyscott","phone":"202-225-8351"},"vote":["Aye"]},{"details":{"bioguide_id":"S001183","first_name":"David","last_name":"Schweikert","facebook_id":"150338151681908","twitter_id":"RepDavid","phone":"202-225-2190"},"vote":["Aye"]},{"details":{"bioguide_id":"S001162","first_name":"Allyson","last_name":"Schwartz","facebook_id":"244000962363116","twitter_id":null,"phone":"202-225-6111"},"vote":["No"]},{"details":{"bioguide_id":"S001180","first_name":"Kurt","last_name":"Schrader","facebook_id":"94978896695","twitter_id":"repschrader","phone":"202-225-5711"},"vote":["Aye"]},{"details":{"bioguide_id":"S001179","first_name":"Aaron","last_name":"Schock","facebook_id":"70882853544","twitter_id":"repaaronschock","phone":"202-225-6201"},"vote":["Not Voting"]},{"details":{"bioguide_id":"S001190","first_name":"Bradley","last_name":"Schneider","facebook_id":"401029529980053","twitter_id":"RepSchneider","phone":"202-225-4835"},"vote":["No"]},{"details":{"bioguide_id":"S001150","first_name":"Adam","last_name":"Schiff","facebook_id":"9086721830","twitter_id":"RepAdamSchiff","phone":"202-225-4176"},"vote":["Aye"]},{"details":{"bioguide_id":"S001145","first_name":"Janice","last_name":"Schakowsky","facebook_id":"160143957118","twitter_id":"janschakowsky","phone":"202-225-2111"},"vote":["No"]},{"details":{"bioguide_id":"S001176","first_name":"Steve","last_name":"Scalise","facebook_id":"50936151681","twitter_id":"SteveScalise","phone":"202-225-3015"},"vote":["Aye"]},{"details":{"bioguide_id":"S001168","first_name":"John","last_name":"Sarbanes","facebook_id":null,"twitter_id":"RepJohnSarbanes","phone":"202-225-4016"},"vote":["Aye"]},{"details":{"bioguide_id":"S000051","first_name":"Marshall","last_name":"Sanford","facebook_id":"118387985037658","twitter_id":"RepSanfordSC","phone":"202-225-3176"},"vote":["Aye"]},{"details":{"bioguide_id":"S000030","first_name":"Loretta","last_name":"Sanchez","facebook_id":"90966961167","twitter_id":"lorettasanchez","phone":"202-225-2965"},"vote":["Aye"]},{"details":{"bioguide_id":"S001156","first_name":"Linda","last_name":"Sánchez","facebook_id":"110685735673141","twitter_id":"replindasanchez","phone":"202-225-6676"},"vote":["Aye"]},{"details":{"bioguide_id":"S000018","first_name":"Matt","last_name":"Salmon","facebook_id":"149561218527414","twitter_id":"RepMattSalmon","phone":"202-225-2635"},"vote":["Aye"]},{"details":{"bioguide_id":"R000570","first_name":"Paul","last_name":"Ryan","facebook_id":"7123827676","twitter_id":"reppaulryan","phone":"202-225-3031"},"vote":["No"]},{"details":{"bioguide_id":"R000577","first_name":"Tim","last_name":"Ryan","facebook_id":"121560497865","twitter_id":"RepTimRyan","phone":"202-225-5261"},"vote":["No"]},{"details":{"bioguide_id":"R000515","first_name":"Bobby","last_name":"Rush","facebook_id":"230753786936538","twitter_id":"RepBobbyRush","phone":"202-225-4372"},"vote":["Aye"]},{"details":{"bioguide_id":"R000576","first_name":"C.","last_name":"Ruppersberger","facebook_id":"184756771570504","twitter_id":"Call_Me_Dutch","phone":"202-225-3061"},"vote":["No"]},{"details":{"bioguide_id":"R000594","first_name":"Jon","last_name":"Runyan","facebook_id":"177208315676754","twitter_id":"RepJonRunyan","phone":"202-225-4765"},"vote":["No"]},{"details":{"bioguide_id":"R000599","first_name":"Raul","last_name":"Ruiz","facebook_id":"245244468941114","twitter_id":"CongressmanRuiz","phone":"202-225-5330"},"vote":["No"]},{"details":{"bioguide_id":"R000487","first_name":"Edward","last_name":"Royce","facebook_id":"6460640558","twitter_id":"RepEdRoyce","phone":"202-225-4111"},"vote":["No"]},{"details":{"bioguide_id":"R000486","first_name":"Lucille","last_name":"Roybal-Allard","facebook_id":"139773069370563","twitter_id":"RepRoybalAllard","phone":"202-225-1766"},"vote":["Aye"]},{"details":{"bioguide_id":"R000598","first_name":"Keith","last_name":"Rothfus","facebook_id":"133803223451004","twitter_id":"KeithRothfus","phone":"202-225-2065"},"vote":["Aye"]},{"details":{"bioguide_id":"R000593","first_name":"Dennis","last_name":"Ross","facebook_id":"469477579757018","twitter_id":"RepDennisRoss","phone":"202-225-1252"},"vote":["Aye"]},{"details":{"bioguide_id":"R000580","first_name":"Peter","last_name":"Roskam","facebook_id":null,"twitter_id":"PeterRoskam","phone":"202-225-4561"},"vote":["No"]},{"details":{"bioguide_id":"R000435","first_name":"Ileana","last_name":"Ros-Lehtinen","facebook_id":"286546974761109","twitter_id":"RosLehtinen","phone":"202-225-3931"},"vote":["No"]},{"details":{"bioguide_id":"R000583","first_name":"Thomas","last_name":"Rooney","facebook_id":"117697790448","twitter_id":"TomRooney","phone":"202-225-5792"},"vote":["No"]},{"details":{"bioguide_id":"R000592","first_name":"Todd","last_name":"Rokita","facebook_id":"183180288372896","twitter_id":"ToddRokita","phone":"202-225-5037"},"vote":["Not Voting"]},{"details":{"bioguide_id":"R000409","first_name":"Dana","last_name":"Rohrabacher","facebook_id":"78476240421","twitter_id":null,"phone":"202-225-2415"},"vote":["Aye"]},{"details":{"bioguide_id":"R000572","first_name":"Mike","last_name":"Rogers","facebook_id":"168209963203416","twitter_id":"RepMikeRogers","phone":"202-225-4872"},"vote":["No"]},{"details":{"bioguide_id":"R000395","first_name":"Harold","last_name":"Rogers","facebook_id":"6722039085","twitter_id":"RepHalRogers","phone":"202-225-4601"},"vote":["No"]},{"details":{"bioguide_id":"R000575","first_name":"Mike","last_name":"Rogers","facebook_id":"171770326187035","twitter_id":"RepMikeRogersAL","phone":"202-225-3261"},"vote":["No"]},{"details":{"bioguide_id":"R000582","first_name":"David","last_name":"Roe","facebook_id":"130725126985966","twitter_id":"DrPhilRoe","phone":"202-225-6356"},"vote":["Aye"]},{"details":{"bioguide_id":"R000591","first_name":"Martha","last_name":"Roby","facebook_id":"174519582574426","twitter_id":"RepMarthaRoby","phone":"202-225-2901"},"vote":["No"]},{"details":{"bioguide_id":"R000589","first_name":"E.","last_name":"Rigell","facebook_id":"167851429918010","twitter_id":"repscottrigell","phone":"202-225-4215"},"vote":["No"]},{"details":{"bioguide_id":"R000588","first_name":"Cedric","last_name":"Richmond","facebook_id":"197737020257085","twitter_id":"reprichmond","phone":"202-225-6636"},"vote":["Aye"]},{"details":{"bioguide_id":"R000597","first_name":"Tom","last_name":"Rice","facebook_id":"403403083083104","twitter_id":"RepTomRice","phone":"202-225-9895"},"vote":["Aye"]},{"details":{"bioguide_id":"R000587","first_name":"Reid","last_name":"Ribble","facebook_id":"157169920997203","twitter_id":"RepRibble","phone":"202-225-5665"},"vote":["Aye"]},{"details":{"bioguide_id":"R000586","first_name":"James","last_name":"Renacci","facebook_id":"187639524595278","twitter_id":"repjimrenacci","phone":"202-225-3876"},"vote":["No"]},{"details":{"bioguide_id":"R000578","first_name":"David","last_name":"Reichert","facebook_id":"91504302598","twitter_id":"davereichert","phone":"202-225-7761"},"vote":["No"]},{"details":{"bioguide_id":"R000585","first_name":"Tom","last_name":"Reed","facebook_id":"102449199835273","twitter_id":"RepTomReed","phone":"202-225-3161"},"vote":["No"]},{"details":{"bioguide_id":"R000053","first_name":"Charles","last_name":"Rangel","facebook_id":"7390589055","twitter_id":"cbrangel","phone":"202-225-4365"},"vote":["Aye"]},{"details":{"bioguide_id":"R000011","first_name":"Nick","last_name":"Rahall","facebook_id":"357958026910","twitter_id":null,"phone":"202-225-3452"},"vote":["Aye"]},{"details":{"bioguide_id":"R000596","first_name":"Trey","last_name":"Radel","facebook_id":"391501350939452","twitter_id":"treyradel","phone":"202-225-2536"},"vote":["Aye"]},{"details":{"bioguide_id":"Q000023","first_name":"Mike","last_name":"Quigley","facebook_id":"158963645688","twitter_id":"RepMikeQuigley","phone":"202-225-4061"},"vote":["No"]},{"details":{"bioguide_id":"P000523","first_name":"David","last_name":"Price","facebook_id":null,"twitter_id":"RepDavidEPrice","phone":"202-225-1784"},"vote":["No"]},{"details":{"bioguide_id":"P000591","first_name":"Tom","last_name":"Price","facebook_id":"172032960420","twitter_id":"RepTomPrice","phone":"202-225-4501"},"vote":["Aye"]},{"details":{"bioguide_id":"P000599","first_name":"Bill","last_name":"Posey","facebook_id":"100000080395369","twitter_id":"congbillposey","phone":"202-225-3671"},"vote":["Aye"]},{"details":{"bioguide_id":"P000602","first_name":"Mike","last_name":"Pompeo","facebook_id":"101965369880683","twitter_id":"repmikepompeo","phone":"202-225-6216"},"vote":["No"]},{"details":{"bioguide_id":"P000598","first_name":"Jared","last_name":"Polis","facebook_id":"53481427529","twitter_id":"RepPolisPress","phone":"202-225-2161"},"vote":["Aye"]},{"details":{"bioguide_id":"P000592","first_name":"Ted","last_name":"Poe","facebook_id":"106631626049851","twitter_id":"JudgeTedPoe","phone":"202-225-6565"},"vote":["Aye"]},{"details":{"bioguide_id":"P000607","first_name":"Mark","last_name":"Pocan","facebook_id":"436881033058309","twitter_id":"repmarkpocan","phone":"202-225-2906"},"vote":["Aye"]},{"details":{"bioguide_id":"P000373","first_name":"Joseph","last_name":"Pitts","facebook_id":"94156528752","twitter_id":"RepJoePitts","phone":"202-225-2411"},"vote":["No"]},{"details":{"bioguide_id":"P000606","first_name":"Robert","last_name":"Pittenger","facebook_id":"376142742468386","twitter_id":"RepPittenger","phone":"202-225-1976"},"vote":["No"]},{"details":{"bioguide_id":"P000597","first_name":"Chellie","last_name":"Pingree","facebook_id":"91529332807","twitter_id":"chelliepingree","phone":"202-225-6116"},"vote":["Aye"]},{"details":{"bioguide_id":"P000265","first_name":"Thomas","last_name":"Petri","facebook_id":"thomaspetri","twitter_id":null,"phone":"202-225-2476"},"vote":["Aye"]},{"details":{"bioguide_id":"P000258","first_name":"Collin","last_name":"Peterson","facebook_id":"6595227967","twitter_id":null,"phone":"202-225-2165"},"vote":["No"]},{"details":{"bioguide_id":"P000595","first_name":"Gary","last_name":"Peters","facebook_id":"88851604323","twitter_id":"RepGaryPeters","phone":"202-225-5802"},"vote":["No"]},{"details":{"bioguide_id":"P000608","first_name":"Scott","last_name":"Peters","facebook_id":"449337038470352","twitter_id":"RepScottPeters","phone":"202-225-0508"},"vote":["No"]},{"details":{"bioguide_id":"P000605","first_name":"Scott","last_name":"Perry","facebook_id":"376801102416184","twitter_id":"RepScottPerry","phone":"202-225-5836"},"vote":["Aye"]},{"details":{"bioguide_id":"P000593","first_name":"Ed","last_name":"Perlmutter","facebook_id":"86174496459","twitter_id":"RepPerlmutter","phone":"202-225-2645"},"vote":["Aye"]},{"details":{"bioguide_id":"P000197","first_name":"Nancy","last_name":"Pelosi","facebook_id":"86574174383","twitter_id":"NancyPelosi","phone":"202-225-4965"},"vote":["No"]},{"details":{"bioguide_id":"P000588","first_name":"Stevan","last_name":"Pearce","facebook_id":"180280568662135","twitter_id":"RepStevePearce","phone":"202-225-2365"},"vote":["Aye"]},{"details":{"bioguide_id":"P000604","first_name":"Donald","last_name":"Payne","facebook_id":"360976767343741","twitter_id":"RepDonaldPayne","phone":"202-225-3436"},"vote":["No"]},{"details":{"bioguide_id":"P000594","first_name":"Erik","last_name":"Paulsen","facebook_id":"128558293848160","twitter_id":"RepErikPaulsen","phone":"202-225-2871"},"vote":["No"]},{"details":{"bioguide_id":"P000099","first_name":"Ed","last_name":"Pastor","phone":"202-225-4065"},"vote":["Aye"]},{"details":{"bioguide_id":"P000096","first_name":"Bill","last_name":"Pascrell","facebook_id":"303312929155","twitter_id":"BillPascrell","phone":"202-225-5751"},"vote":["Aye"]},{"details":{"bioguide_id":"P000034","first_name":"Frank","last_name":"Pallone","facebook_id":"6517277731","twitter_id":"FrankPallone","phone":"202-225-4671"},"vote":["Not Voting"]},{"details":{"bioguide_id":"P000601","first_name":"Steven","last_name":"Palazzo","facebook_id":"186908658003781","twitter_id":"congpalazzo","phone":"202-225-5772"},"vote":["No"]},{"details":{"bioguide_id":"O000169","first_name":"William","last_name":"Owens","facebook_id":"132985523396856","twitter_id":"BillOwensNY","phone":"202-225-4611"},"vote":["Aye"]},{"details":{"bioguide_id":"O000168","first_name":"Pete","last_name":"Olson","facebook_id":"20718168936","twitter_id":"OlsonPressShop","phone":"202-225-5951"},"vote":["No"]},{"details":{"bioguide_id":"O000170","first_name":"Beto","last_name":"O'Rourke","facebook_id":"460776160654909","twitter_id":"betoorourketx16","phone":"202-225-4831"},"vote":["Aye"]},{"details":{"bioguide_id":"N000186","first_name":"Alan","last_name":"Nunnelee","facebook_id":"144919278895639","twitter_id":"repalannunnelee","phone":"202-225-4306"},"vote":["No"]},{"details":{"bioguide_id":"N000181","first_name":"Devin","last_name":"Nunes","facebook_id":null,"twitter_id":"Rep_DevinNunes","phone":"202-225-2523"},"vote":["No"]},{"details":{"bioguide_id":"N000185","first_name":"Richard","last_name":"Nugent","facebook_id":"183541871674897","twitter_id":"RepRichNugent","phone":"202-225-1002"},"vote":["Aye"]},{"details":{"bioguide_id":"N000127","first_name":"Richard","last_name":"Nolan","facebook_id":"388085277945339","twitter_id":"usrepricknolan","phone":"202-225-6211"},"vote":["Aye"]},{"details":{"bioguide_id":"N000184","first_name":"Kristi","last_name":"Noem","facebook_id":null,"twitter_id":"repkristinoem","phone":"202-225-2801"},"vote":["No"]},{"details":{"bioguide_id":"N000182","first_name":"Randy","last_name":"Neugebauer","facebook_id":"64137294987","twitter_id":"RandyNeugebauer","phone":"202-225-4005"},"vote":["No"]},{"details":{"bioguide_id":"N000187","first_name":"Gloria","last_name":"Negrete McLeod","facebook_id":"282772691776629","twitter_id":"RepMcLeod","phone":"202-225-6161"},"vote":["Not Voting"]},{"details":{"bioguide_id":"N000015","first_name":"Richard","last_name":"Neal","facebook_id":"325642654132598","twitter_id":"RepRichardNeal","phone":"202-225-5601"},"vote":["Aye"]},{"details":{"bioguide_id":"N000179","first_name":"Grace","last_name":"Napolitano","facebook_id":"163108420409412","twitter_id":"gracenapolitano","phone":"202-225-5256"},"vote":["Aye"]},{"details":{"bioguide_id":"N000002","first_name":"Jerrold","last_name":"Nadler","facebook_id":"78291598977","twitter_id":"RepJerryNadler","phone":"202-225-5635"},"vote":["Aye"]},{"details":{"bioguide_id":"M001151","first_name":"Tim","last_name":"Murphy","facebook_id":"105769762798552","twitter_id":"RepTimMurphy","phone":"202-225-2301"},"vote":["No"]},{"details":{"bioguide_id":"M001191","first_name":"Patrick","last_name":"Murphy","facebook_id":"317735028342371","twitter_id":"RepMurphyFL","phone":"202-225-3026"},"vote":["No"]},{"details":{"bioguide_id":"M001182","first_name":"Mick","last_name":"Mulvaney","facebook_id":"188649667827713","twitter_id":"repmickmulvaney","phone":"202-225-5501"},"vote":["Aye"]},{"details":{"bioguide_id":"M001190","first_name":"Markwayne","last_name":"Mullin","facebook_id":"453637624684399","twitter_id":"RepMullin","phone":"202-225-2701"},"vote":["Aye"]},{"details":{"bioguide_id":"M000933","first_name":"James","last_name":"Moran","facebook_id":"100123453059","twitter_id":"Jim_Moran","phone":"202-225-4376"},"vote":["Aye"]},{"details":{"bioguide_id":"M001160","first_name":"Gwen","last_name":"Moore","facebook_id":"58864029545","twitter_id":"RepGwenMoore","phone":"202-225-4572"},"vote":["Aye"]},{"details":{"bioguide_id":"M000725","first_name":"George","last_name":"Miller","facebook_id":"75298637905","twitter_id":"askgeorge","phone":"202-225-2095"},"vote":["Aye"]},{"details":{"bioguide_id":"M001139","first_name":"Gary","last_name":"Miller","facebook_id":"105352226181045","twitter_id":"repgarymiller","phone":"202-225-3201"},"vote":["Aye"]},{"details":{"bioguide_id":"M001150","first_name":"Candice","last_name":"Miller","facebook_id":"210401648605","twitter_id":"CandiceMiller","phone":"202-225-2106"},"vote":["No"]},{"details":{"bioguide_id":"M001144","first_name":"Jeff","last_name":"Miller","facebook_id":"66367876671","twitter_id":null,"phone":"202-225-4136"},"vote":["No"]},{"details":{"bioguide_id":"M001149","first_name":"Michael","last_name":"Michaud","facebook_id":"131279995382","twitter_id":"RepMikeMichaud","phone":"202-225-6306"},"vote":["Aye"]},{"details":{"bioguide_id":"M000689","first_name":"John","last_name":"Mica","facebook_id":"JohnMica","twitter_id":null,"phone":"202-225-4035"},"vote":["Aye"]},{"details":{"bioguide_id":"M001189","first_name":"Luke","last_name":"Messer","facebook_id":"367444640018564","twitter_id":"RepLukeMesser","phone":"202-225-3021"},"vote":["No"]},{"details":{"bioguide_id":"M001188","first_name":"Grace","last_name":"Meng","facebook_id":"195734010571362","twitter_id":"RepGraceMeng","phone":"202-225-2601"},"vote":["No"]},{"details":{"bioguide_id":"M001137","first_name":"Gregory","last_name":"Meeks","facebook_id":"1557025818","twitter_id":"GregoryMeeks","phone":"202-225-3461"},"vote":["No"]},{"details":{"bioguide_id":"M001181","first_name":"Patrick","last_name":"Meehan","facebook_id":"136000283132824","twitter_id":"repmeehan","phone":"202-225-2011"},"vote":["No"]},{"details":{"bioguide_id":"M001187","first_name":"Mark","last_name":"Meadows","facebook_id":"409882952423501","twitter_id":"RepMarkMeadows","phone":"202-225-6401"},"vote":["Aye"]},{"details":{"bioguide_id":"M001166","first_name":"Jerry","last_name":"McNerney","facebook_id":"215241308510238","twitter_id":"RepMcNerney","phone":"202-225-1947"},"vote":["No"]},{"details":{"bioguide_id":"M001159","first_name":"Cathy","last_name":"McMorris Rodgers","facebook_id":"321618789771","twitter_id":"cathymcmorris","phone":"202-225-2006"},"vote":["Aye"]},{"details":{"bioguide_id":"M001180","first_name":"David","last_name":"McKinley","facebook_id":"130377260362609","twitter_id":"RepMcKinley","phone":"202-225-4172"},"vote":["No"]},{"details":{"bioguide_id":"M000508","first_name":"Howard","last_name":"McKeon","facebook_id":"8138529578","twitter_id":"BuckMcKeon","phone":"202-225-1956"},"vote":["No"]},{"details":{"bioguide_id":"M000485","first_name":"Mike","last_name":"McIntyre","facebook_id":"340903514856","twitter_id":"repmikemcintyre","phone":"202-225-2731"},"vote":["No"]},{"details":{"bioguide_id":"M001156","first_name":"Patrick","last_name":"McHenry","facebook_id":"8045519803","twitter_id":"PatrickMcHenry","phone":"202-225-2576"},"vote":["Aye"]},{"details":{"bioguide_id":"M000312","first_name":"James","last_name":"McGovern","facebook_id":null,"twitter_id":"RepMcGovern","phone":"202-225-6101"},"vote":["Aye"]},{"details":{"bioguide_id":"M000404","first_name":"Jim","last_name":"McDermott","facebook_id":"246418928093","twitter_id":"RepJimMcDermott","phone":"202-225-3106"},"vote":["Aye"]},{"details":{"bioguide_id":"M001143","first_name":"Betty","last_name":"McCollum","facebook_id":"153386471383393","twitter_id":"BettyMcCollum04","phone":"202-225-6631"},"vote":["Aye"]},{"details":{"bioguide_id":"M001177","first_name":"Tom","last_name":"McClintock","facebook_id":"81125319109","twitter_id":"RepMcClintock","phone":"202-225-2511"},"vote":["Aye"]},{"details":{"bioguide_id":"M001157","first_name":"Michael","last_name":"McCaul","facebook_id":"6355254859","twitter_id":"McCaulPressShop","phone":"202-225-2401"},"vote":["No"]},{"details":{"bioguide_id":"M000309","first_name":"Carolyn","last_name":"McCarthy","facebook_id":null,"twitter_id":"RepMcCarthyNY","phone":"202-225-5516"},"vote":["Not Voting"]},{"details":{"bioguide_id":"M001165","first_name":"Kevin","last_name":"McCarthy","facebook_id":"51052893175","twitter_id":"GOPWhip","phone":"202-225-2915"},"vote":["No"]},{"details":{"bioguide_id":"M001163","first_name":"Doris","last_name":"Matsui","facebook_id":"doris.matsui","twitter_id":"DorisMatsui","phone":"202-225-7163"},"vote":["Aye"]},{"details":{"bioguide_id":"M001142","first_name":"Jim","last_name":"Matheson","facebook_id":"131888123517015","twitter_id":"RepJimMatheson","phone":"202-225-3011"},"vote":["No"]},{"details":{"bioguide_id":"M001184","first_name":"Thomas","last_name":"Massie","facebook_id":"452480994776070","twitter_id":"RepThomasMassie","phone":"202-225-3465"},"vote":["Aye"]},{"details":{"bioguide_id":"M001179","first_name":"Tom","last_name":"Marino","facebook_id":"144408762280226","twitter_id":"RepTomMarino","phone":"202-225-3731"},"vote":["No"]},{"details":{"bioguide_id":"M001158","first_name":"Kenny","last_name":"Marchant","facebook_id":"6349487899","twitter_id":"RepKenMarchant","phone":"202-225-6605"},"vote":["Aye"]},{"details":{"bioguide_id":"M001185","first_name":"Sean","last_name":"Maloney","facebook_id":"551199354892891","twitter_id":"RepSeanMaloney","phone":"202-225-5441"},"vote":["No"]},{"details":{"bioguide_id":"M000087","first_name":"Carolyn","last_name":"Maloney","facebook_id":null,"twitter_id":"RepMaloney","phone":"202-225-7944"},"vote":["Aye"]},{"details":{"bioguide_id":"M001171","first_name":"Daniel","last_name":"Maffei","facebook_id":"470842942980263","twitter_id":"RepDanMaffei","phone":"202-225-3701"},"vote":["Aye"]},{"details":{"bioguide_id":"L000562","first_name":"Stephen","last_name":"Lynch","facebook_id":"133720816696865","twitter_id":"RepStephenLynch","phone":"202-225-8273"},"vote":["Aye"]},{"details":{"bioguide_id":"L000571","first_name":"Cynthia","last_name":"Lummis","facebook_id":"152754318103332","twitter_id":"CynthiaLummis","phone":"202-225-2311"},"vote":["Aye"]},{"details":{"bioguide_id":"L000570","first_name":"Ben","last_name":"Luján","facebook_id":"112962521120","twitter_id":"repbenraylujan","phone":"202-225-6190"},"vote":["Aye"]},{"details":{"bioguide_id":"L000580","first_name":"Michelle","last_name":"Lujan Grisham","facebook_id":"191640657646128","twitter_id":"replujangrisham","phone":"202-225-6316"},"vote":["Aye"]},{"details":{"bioguide_id":"L000569","first_name":"Blaine","last_name":"Luetkemeyer","facebook_id":"1358702716","twitter_id":null,"phone":"202-225-2956"},"vote":["No"]},{"details":{"bioguide_id":"L000491","first_name":"Frank","last_name":"Lucas","facebook_id":"7872057395","twitter_id":"RepFrankLucas","phone":"202-225-5565"},"vote":["No"]},{"details":{"bioguide_id":"L000480","first_name":"Nita","last_name":"Lowey","facebook_id":"158290607551599","twitter_id":"NitaLowey","phone":"202-225-6506"},"vote":["No"]},{"details":{"bioguide_id":"L000579","first_name":"Alan","last_name":"Lowenthal","facebook_id":"392631677490897","twitter_id":"RepLowenthal","phone":"202-225-7924"},"vote":["Aye"]},{"details":{"bioguide_id":"L000576","first_name":"Billy","last_name":"Long","facebook_id":"139631049438354","twitter_id":null,"phone":"202-225-6536"},"vote":["No"]},{"details":{"bioguide_id":"L000397","first_name":"Zoe","last_name":"Lofgren","facebook_id":"221191600719","twitter_id":"RepZoeLofgren","phone":"202-225-3072"},"vote":["Aye"]},{"details":{"bioguide_id":"L000565","first_name":"David","last_name":"Loebsack","facebook_id":"291731316748","twitter_id":"daveloebsack","phone":"202-225-6576"},"vote":["Aye"]},{"details":{"bioguide_id":"L000554","first_name":"Frank","last_name":"LoBiondo","facebook_id":"FrankLoBiondo","twitter_id":"RepLoBiondo","phone":"202-225-6572"},"vote":["No"]},{"details":{"bioguide_id":"L000563","first_name":"Daniel","last_name":"Lipinski","facebook_id":"103286879730089","twitter_id":"replipinski","phone":"202-225-5701"},"vote":["No"]},{"details":{"bioguide_id":"L000287","first_name":"John","last_name":"Lewis","facebook_id":"82737208404","twitter_id":"RepJohnLewis","phone":"202-225-3801"},"vote":["Aye"]},{"details":{"bioguide_id":"L000263","first_name":"Sander","last_name":"Levin","facebook_id":"223726364320243","twitter_id":"repsandylevin","phone":"202-225-4961"},"vote":["No"]},{"details":{"bioguide_id":"L000551","first_name":"Barbara","last_name":"Lee","facebook_id":"92190287786","twitter_id":"repbarbaralee","phone":"202-225-2661"},"vote":["Aye"]},{"details":{"bioguide_id":"L000566","first_name":"Robert","last_name":"Latta","facebook_id":"100000004848334","twitter_id":"boblatta","phone":"202-225-6405"},"vote":["No"]},{"details":{"bioguide_id":"L000111","first_name":"Tom","last_name":"Latham","facebook_id":"345331988887412","twitter_id":"TomLatham","phone":"202-225-5476"},"vote":["No"]},{"details":{"bioguide_id":"L000557","first_name":"John","last_name":"Larson","facebook_id":"6352928631","twitter_id":"repjohnlarson","phone":"202-225-2265"},"vote":["Aye"]},{"details":{"bioguide_id":"L000560","first_name":"Rick","last_name":"Larsen","facebook_id":"135654683137079","twitter_id":"RepRickLarsen","phone":"202-225-2605"},"vote":["No"]},{"details":{"bioguide_id":"L000575","first_name":"James","last_name":"Lankford","facebook_id":"130873066975024","twitter_id":"replankford","phone":"202-225-2132"},"vote":["No"]},{"details":{"bioguide_id":"L000559","first_name":"James","last_name":"Langevin","facebook_id":"6578978441","twitter_id":"jimlangevin","phone":"202-225-2735"},"vote":["No"]},{"details":{"bioguide_id":"L000567","first_name":"Leonard","last_name":"Lance","facebook_id":"100830109970339","twitter_id":null,"phone":"202-225-5361"},"vote":["No"]},{"details":{"bioguide_id":"L000564","first_name":"Doug","last_name":"Lamborn","facebook_id":"45059452286","twitter_id":"RepDLamborn","phone":"202-225-4422"},"vote":["Aye"]},{"details":{"bioguide_id":"L000578","first_name":"Doug","last_name":"LaMalfa","twitter_id":"RepLaMalfa","phone":"202-225-3076"},"vote":["Aye"]},{"details":{"bioguide_id":"L000573","first_name":"Raúl","last_name":"Labrador","facebook_id":"180970951936493","twitter_id":"Raul_Labrador","phone":"202-225-6611"},"vote":["Aye"]},{"details":{"bioguide_id":"K000382","first_name":"Ann","last_name":"Kuster","facebook_id":"115543081952049","twitter_id":"RepAnnieKuster","phone":"202-225-5206"},"vote":["No"]},{"details":{"bioguide_id":"K000363","first_name":"John","last_name":"Kline","facebook_id":null,"twitter_id":"repjohnkline","phone":"202-225-2271"},"vote":["No"]},{"details":{"bioguide_id":"K000368","first_name":"Ann","last_name":"Kirkpatrick","facebook_id":"152493768236405","twitter_id":"RepKirkpatrick","phone":"202-225-3361"},"vote":["No"]},{"details":{"bioguide_id":"K000378","first_name":"Adam","last_name":"Kinzinger","facebook_id":"187811174579106","twitter_id":"RepKinzinger","phone":"202-225-3635"},"vote":["No"]},{"details":{"bioguide_id":"K000220","first_name":"Jack","last_name":"Kingston","facebook_id":"6914617307","twitter_id":"JackKingston","phone":"202-225-5831"},"vote":["Aye"]},{"details":{"bioguide_id":"K000210","first_name":"Peter","last_name":"King","facebook_id":null,"twitter_id":"RepPeteKing","phone":"202-225-7896"},"vote":["No"]},{"details":{"bioguide_id":"K000362","first_name":"Steve","last_name":"King","facebook_id":"134325379926458","twitter_id":"stevekingia","phone":"202-225-4426"},"vote":["No"]},{"details":{"bioguide_id":"K000188","first_name":"Ron","last_name":"Kind","facebook_id":"89152017954","twitter_id":"repronkind","phone":"202-225-5506"},"vote":["No"]},{"details":{"bioguide_id":"K000381","first_name":"Derek","last_name":"Kilmer","facebook_id":"450819048314124","twitter_id":"RepDerekKilmer","phone":"202-225-5916"},"vote":["No"]},{"details":{"bioguide_id":"K000380","first_name":"Daniel","last_name":"Kildee","facebook_id":"484166588292670","twitter_id":"RepDanKildee","phone":"202-225-3611"},"vote":["Aye"]},{"details":{"bioguide_id":"K000379","first_name":"Joseph","last_name":"Kennedy","facebook_id":"301936109927957","twitter_id":"RepJoeKennedy","phone":"202-225-5931"},"vote":["No"]},{"details":{"bioguide_id":"K000376","first_name":"Mike","last_name":"Kelly","facebook_id":"191056827594903","twitter_id":"MikeKellyPA","phone":"202-225-5406"},"vote":["No"]},{"details":{"bioguide_id":"K000385","first_name":"Robin","last_name":"Kelly","facebook_id":"150380975141321","twitter_id":"RepRobinKelly","phone":"202-225-0773"},"vote":["No"]},{"details":{"bioguide_id":"K000375","first_name":"William","last_name":"Keating","facebook_id":"183092598372008","twitter_id":"USRepKeating","phone":"202-225-3111"},"vote":["Aye"]},{"details":{"bioguide_id":"K000009","first_name":"Marcy","last_name":"Kaptur","facebook_id":"173753129419169","twitter_id":"RepMarcyKaptur","phone":"202-225-4146"},"vote":["No"]},{"details":{"bioguide_id":"J000295","first_name":"David","last_name":"Joyce","facebook_id":"404318572981934","twitter_id":"RepDaveJoyce","phone":"202-225-5731"},"vote":["No"]},{"details":{"bioguide_id":"J000289","first_name":"Jim","last_name":"Jordan","facebook_id":"35499336459","twitter_id":"Jim_Jordan","phone":"202-225-2676"},"vote":["Aye"]},{"details":{"bioguide_id":"J000255","first_name":"Walter","last_name":"Jones","facebook_id":"15083070102","twitter_id":"RepWalterJones","phone":"202-225-3415"},"vote":["Aye"]},{"details":{"bioguide_id":"J000174","first_name":"Sam","last_name":"Johnson","facebook_id":"52454091867","twitter_id":"SamsPressShop","phone":"202-225-4201"},"vote":["No"]},{"details":{"bioguide_id":"J000126","first_name":"Eddie","last_name":"Johnson","facebook_id":"84096022067","twitter_id":"RepEBJ","phone":"202-225-8885"},"vote":["No"]},{"details":{"bioguide_id":"J000292","first_name":"Bill","last_name":"Johnson","facebook_id":"170477096312258","twitter_id":"repbilljohnson","phone":"202-225-5705"},"vote":["Aye"]},{"details":{"bioguide_id":"J000288","first_name":"Henry","last_name":"Johnson","facebook_id":"115356957005","twitter_id":"RepHankJohnson","phone":"202-225-1605"},"vote":["No"]},{"details":{"bioguide_id":"J000290","first_name":"Lynn","last_name":"Jenkins","facebook_id":"6974973662","twitter_id":"RepLynnJenkins","phone":"202-225-6601"},"vote":["Aye"]},{"details":{"bioguide_id":"J000294","first_name":"Hakeem","last_name":"Jeffries","facebook_id":"118349138343701","twitter_id":"HakeemJeffries","phone":"202-225-5936"},"vote":["Aye"]},{"details":{"bioguide_id":"J000032","first_name":"Sheila","last_name":"Jackson Lee","facebook_id":"169479190984","twitter_id":"JacksonLeeTX18","phone":"202-225-3816"},"vote":["No"]},{"details":{"bioguide_id":"I000056","first_name":"Darrell","last_name":"Issa","facebook_id":"19463427992","twitter_id":"DarrellIssa","phone":"202-225-3906"},"vote":["No"]},{"details":{"bioguide_id":"I000057","first_name":"Steve","last_name":"Israel","facebook_id":"RepSteveIsrael","twitter_id":"RepSteveIsrael","phone":"202-225-3335"},"vote":["No"]},{"details":{"bioguide_id":"H001060","first_name":"Robert","last_name":"Hurt","facebook_id":"120068161395562","twitter_id":"RepRobertHurt","phone":"202-225-4711"},"vote":["No"]},{"details":{"bioguide_id":"H001048","first_name":"Duncan","last_name":"Hunter","facebook_id":"79072979038","twitter_id":"Rep_Hunter","phone":"202-225-5672"},"vote":["No"]},{"details":{"bioguide_id":"H001059","first_name":"Randy","last_name":"Hultgren","facebook_id":"186221644739359","twitter_id":"rephultgren","phone":"202-225-2976"},"vote":["Aye"]},{"details":{"bioguide_id":"H001058","first_name":"Bill","last_name":"Huizenga","facebook_id":"145764842143763","twitter_id":"rephuizenga","phone":"202-225-4401"},"vote":["Aye"]},{"details":{"bioguide_id":"H001068","first_name":"Jared","last_name":"Huffman","facebook_id":"200227780116038","twitter_id":"RepHuffman","phone":"202-225-5161"},"vote":["Aye"]},{"details":{"bioguide_id":"H001057","first_name":"Tim","last_name":"Huelskamp","facebook_id":"congressmanhuelskamp","twitter_id":"conghuelskamp","phone":"202-225-2715"},"vote":["Aye"]},{"details":{"bioguide_id":"H001067","first_name":"Richard","last_name":"Hudson","facebook_id":"212153802255530","twitter_id":"RepRichHudson","phone":"202-225-3715"},"vote":["No"]},{"details":{"bioguide_id":"H000874","first_name":"Steny","last_name":"Hoyer","facebook_id":"282861997886","twitter_id":"WhipHoyer","phone":"202-225-4131"},"vote":["No"]},{"details":{"bioguide_id":"H001066","first_name":"Steven","last_name":"Horsford","facebook_id":"321461017954658","twitter_id":"RepHorsford","phone":"202-225-9894"},"vote":["Not Voting"]},{"details":{"bioguide_id":"H001034","first_name":"Michael","last_name":"Honda","facebook_id":"15675385380","twitter_id":"RepMikeHonda","phone":"202-225-2631"},"vote":["Aye"]},{"details":{"bioguide_id":"H001032","first_name":"Rush","last_name":"Holt","facebook_id":null,"twitter_id":null,"phone":"202-225-5801"},"vote":["Aye"]},{"details":{"bioguide_id":"H001065","first_name":"George","last_name":"Holding","facebook_id":"384164668340890","twitter_id":"RepHolding","phone":"202-225-3032"},"vote":["No"]},{"details":{"bioguide_id":"H000636","first_name":"Rubén","last_name":"Hinojosa","facebook_id":"402891225161","twitter_id":"USRepRHinojosa","phone":"202-225-2531"},"vote":["No"]},{"details":{"bioguide_id":"H001047","first_name":"James","last_name":"Himes","facebook_id":"85767418619","twitter_id":"jahimes","phone":"202-225-5541"},"vote":["No"]},{"details":{"bioguide_id":"H001038","first_name":"Brian","last_name":"Higgins","facebook_id":"88144056440","twitter_id":"RepBrianHiggins","phone":"202-225-3306"},"vote":["No"]},{"details":{"bioguide_id":"H001056","first_name":"Jaime","last_name":"Herrera Beutler","facebook_id":"177551525610164","twitter_id":"herrerabeutler","phone":"202-225-3536"},"vote":["Not Voting"]},{"details":{"bioguide_id":"H001036","first_name":"Jeb","last_name":"Hensarling","facebook_id":"7875604788","twitter_id":"RepHensarling","phone":"202-225-3484"},"vote":["No"]},{"details":{"bioguide_id":"H001064","first_name":"Denny","last_name":"Heck","facebook_id":"547907568553615","twitter_id":"RepDennyHeck","phone":"202-225-9740"},"vote":["No"]},{"details":{"bioguide_id":"H001055","first_name":"Joseph","last_name":"Heck","facebook_id":"155211751194624","twitter_id":"RepJoeHeck","phone":"202-225-3252"},"vote":["No"]},{"details":{"bioguide_id":"H000329","first_name":"Doc","last_name":"Hastings","facebook_id":"7507129675","twitter_id":"DocHastings","phone":"202-225-5816"},"vote":["No"]},{"details":{"bioguide_id":"H000324","first_name":"Alcee","last_name":"Hastings","facebook_id":"95696782238","twitter_id":null,"phone":"202-225-1313"},"vote":["Aye"]},{"details":{"bioguide_id":"H001053","first_name":"Vicky","last_name":"Hartzler","facebook_id":"183580061667324","twitter_id":"RepHartzler","phone":"202-225-2876"},"vote":["No"]},{"details":{"bioguide_id":"H001052","first_name":"Andy","last_name":"Harris","facebook_id":"508912729153334","twitter_id":"repandyharrismd","phone":"202-225-5311"},"vote":["Aye"]},{"details":{"bioguide_id":"H001045","first_name":"Gregg","last_name":"Harper","facebook_id":"48248435938","twitter_id":"GreggHarper","phone":"202-225-5031"},"vote":["No"]},{"details":{"bioguide_id":"H001051","first_name":"Richard","last_name":"Hanna","facebook_id":"172284859480630","twitter_id":"RepRichardHanna","phone":"202-225-3665"},"vote":["No"]},{"details":{"bioguide_id":"H001050","first_name":"Colleen","last_name":"Hanabusa","facebook_id":"169979129710178","twitter_id":"RepHanabusa","phone":"202-225-2726"},"vote":["No"]},{"details":{"bioguide_id":"H000067","first_name":"Ralph","last_name":"Hall","facebook_id":"6311458773","twitter_id":"RalphHallPress","phone":"202-225-6673"},"vote":["Aye"]},{"details":{"bioguide_id":"H001063","first_name":"Janice","last_name":"Hahn","facebook_id":"256471267712118","twitter_id":"Rep_JaniceHahn","phone":"202-225-8220"},"vote":["Aye"]},{"details":{"bioguide_id":"G000535","first_name":"Luis","last_name":"Gutierrez","facebook_id":"91052833204","twitter_id":"LuisGutierrez","phone":"202-225-8203"},"vote":["No"]},{"details":{"bioguide_id":"G000558","first_name":"Brett","last_name":"Guthrie","facebook_id":null,"twitter_id":null,"phone":"202-225-3501"},"vote":["No"]},{"details":{"bioguide_id":"G000569","first_name":"Michael","last_name":"Grimm","twitter_id":"repmichaelgrimm","phone":"202-225-3371"},"vote":["No"]},{"details":{"bioguide_id":"G000551","first_name":"Raúl","last_name":"Grijalva","facebook_id":"73539896233","twitter_id":"repraulgrijalva","phone":"202-225-2435"},"vote":["Aye"]},{"details":{"bioguide_id":"G000568","first_name":"H.","last_name":"Griffith","facebook_id":"141893975868919","twitter_id":"RepMGriffith","phone":"202-225-3861"},"vote":["Aye"]},{"details":{"bioguide_id":"G000567","first_name":"Tim","last_name":"Griffin","facebook_id":null,"twitter_id":"RepTimGriffin","phone":"202-225-2506"},"vote":["Aye"]},{"details":{"bioguide_id":"G000410","first_name":"Gene","last_name":"Green","facebook_id":"128735131872","twitter_id":"RepGeneGreen","phone":"202-225-1688"},"vote":["Aye"]},{"details":{"bioguide_id":"G000553","first_name":"Al","last_name":"Green","facebook_id":"136237609724391","twitter_id":"RepAlGreen","phone":"202-225-7508"},"vote":["No"]},{"details":{"bioguide_id":"G000556","first_name":"Alan","last_name":"Grayson","facebook_id":null,"twitter_id":null,"phone":"202-225-9889"},"vote":["Aye"]},{"details":{"bioguide_id":"G000546","first_name":"Sam","last_name":"Graves","facebook_id":"118514606128","twitter_id":null,"phone":"202-225-7041"},"vote":["No"]},{"details":{"bioguide_id":"G000560","first_name":"Tom","last_name":"Graves","facebook_id":"104548906262119","twitter_id":"reptomgraves","phone":"202-225-5211"},"vote":["Aye"]},{"details":{"bioguide_id":"G000377","first_name":"Kay","last_name":"Granger","facebook_id":"49640749719","twitter_id":"RepKayGranger","phone":"202-225-5071"},"vote":["No"]},{"details":{"bioguide_id":"G000566","first_name":"Trey","last_name":"Gowdy","facebook_id":"143059759084016","twitter_id":"tgowdysc","phone":"202-225-6030"},"vote":["Aye"]},{"details":{"bioguide_id":"G000565","first_name":"Paul","last_name":"Gosar","facebook_id":"104329699641957","twitter_id":"RepGosar","phone":"202-225-2315"},"vote":["Aye"]},{"details":{"bioguide_id":"G000289","first_name":"Bob","last_name":"Goodlatte","facebook_id":"6459789414","twitter_id":"RepGoodlatte","phone":"202-225-5431"},"vote":["No"]},{"details":{"bioguide_id":"G000552","first_name":"Louie","last_name":"Gohmert","facebook_id":"50375006903","twitter_id":"replouiegohmert","phone":"202-225-3035"},"vote":["Aye"]},{"details":{"bioguide_id":"G000550","first_name":"Phil","last_name":"Gingrey","facebook_id":"6934467868","twitter_id":"RepPhilGingrey","phone":"202-225-2931"},"vote":["No"]},{"details":{"bioguide_id":"G000564","first_name":"Christopher","last_name":"Gibson","facebook_id":"190238567659779","twitter_id":"RepChrisGibson","phone":"202-225-5614"},"vote":["Aye"]},{"details":{"bioguide_id":"G000563","first_name":"Bob","last_name":"Gibbs","facebook_id":"191159267565100","twitter_id":"repbobgibbs","phone":"202-225-6265"},"vote":["No"]},{"details":{"bioguide_id":"G000549","first_name":"Jim","last_name":"Gerlach","facebook_id":"123113211050305","twitter_id":"JimGerlach","phone":"202-225-4315"},"vote":["No"]},{"details":{"bioguide_id":"G000548","first_name":"Scott","last_name":"Garrett","facebook_id":"6756553401","twitter_id":"repgarrett","phone":"202-225-4465"},"vote":["Aye"]},{"details":{"bioguide_id":"G000562","first_name":"Cory","last_name":"Gardner","facebook_id":"160924893954206","twitter_id":"repcorygardner","phone":"202-225-4676"},"vote":["Aye"]},{"details":{"bioguide_id":"G000573","first_name":"Joe","last_name":"Garcia","phone":"202-225-2778"},"vote":["No"]},{"details":{"bioguide_id":"G000559","first_name":"John","last_name":"Garamendi","facebook_id":"182567716746","twitter_id":"RepGaramendi","phone":"202-225-1880"},"vote":["Aye"]},{"details":{"bioguide_id":"G000572","first_name":"Pete","last_name":"Gallego","facebook_id":"123858487781636","twitter_id":"RepPeteGallego","phone":"202-225-4511"},"vote":["No"]},{"details":{"bioguide_id":"G000571","first_name":"Tulsi","last_name":"Gabbard","facebook_id":"392284484191405","twitter_id":"tulsipress","phone":"202-225-4906"},"vote":["Aye"]},{"details":{"bioguide_id":"F000455","first_name":"Marcia","last_name":"Fudge","facebook_id":"279006440801","twitter_id":"RepMarciaFudge","phone":"202-225-7032"},"vote":["Aye"]},{"details":{"bioguide_id":"F000372","first_name":"Rodney","last_name":"Frelinghuysen","facebook_id":"rfrelinghuysen","twitter_id":"USRepRodney","phone":"202-225-5034"},"vote":["No"]},{"details":{"bioguide_id":"F000448","first_name":"Trent","last_name":"Franks","facebook_id":"209486545087","twitter_id":"RepTrentFranks","phone":"202-225-4576"},"vote":["No"]},{"details":{"bioguide_id":"F000462","first_name":"Lois","last_name":"Frankel","facebook_id":"138220153003017","twitter_id":"RepLoisFrankel","phone":"202-225-9890"},"vote":["No"]},{"details":{"bioguide_id":"F000450","first_name":"Virginia","last_name":"Foxx","facebook_id":"RepVirginiaFoxx","twitter_id":"virginiafoxx","phone":"202-225-2071"},"vote":["No"]},{"details":{"bioguide_id":"F000454","first_name":"Bill","last_name":"Foster","facebook_id":"102918290599","twitter_id":"RepBillFoster","phone":"202-225-3515"},"vote":["No"]},{"details":{"bioguide_id":"F000449","first_name":"Jeff","last_name":"Fortenberry","facebook_id":"48426842354","twitter_id":"JeffFortenberry","phone":"202-225-4806"},"vote":["No"]},{"details":{"bioguide_id":"F000445","first_name":"J.","last_name":"Forbes","facebook_id":"356330225020","twitter_id":"Randy_Forbes","phone":"202-225-6365"},"vote":["No"]},{"details":{"bioguide_id":"F000461","first_name":"Bill","last_name":"Flores","facebook_id":"146176682102245","twitter_id":"RepBillFlores","phone":"202-225-6105"},"vote":["No"]},{"details":{"bioguide_id":"F000456","first_name":"John","last_name":"Fleming","facebook_id":"372154186772","twitter_id":"RepFleming","phone":"202-225-2777"},"vote":["Aye"]},{"details":{"bioguide_id":"F000459","first_name":"Charles","last_name":"Fleischmann","facebook_id":"189998554345168","twitter_id":"RepChuck","phone":"202-225-3271"},"vote":["Aye"]},{"details":{"bioguide_id":"F000451","first_name":"Michael","last_name":"Fitzpatrick","facebook_id":"132077153521454","twitter_id":"RepFitzpatrick","phone":"202-225-4276"},"vote":["Aye"]},{"details":{"bioguide_id":"F000458","first_name":"Stephen","last_name":"Fincher","facebook_id":"128861763849209","twitter_id":"RepFincherTN08","phone":"202-225-4714"},"vote":["Aye"]},{"details":{"bioguide_id":"F000043","first_name":"Chaka","last_name":"Fattah","facebook_id":"165961823475034","twitter_id":"chakafattah","phone":"202-225-4001"},"vote":["Aye"]},{"details":{"bioguide_id":"F000030","first_name":"Sam","last_name":"Farr","facebook_id":"7018136294","twitter_id":"RepSamFarr","phone":"202-225-2861"},"vote":["Aye"]},{"details":{"bioguide_id":"F000460","first_name":"Blake","last_name":"Farenthold","facebook_id":"186894244673001","twitter_id":"farenthold","phone":"202-225-7742"},"vote":["Aye"]},{"details":{"bioguide_id":"E000293","first_name":"Elizabeth","last_name":"Esty","facebook_id":"292076514228382","twitter_id":"RepEsty","phone":"202-225-4476"},"vote":["No"]},{"details":{"bioguide_id":"E000215","first_name":"Anna","last_name":"Eshoo","facebook_id":"174979964227","twitter_id":"RepAnnaEshoo","phone":"202-225-8104"},"vote":["Aye"]},{"details":{"bioguide_id":"E000292","first_name":"William","last_name":"Enyart","facebook_id":"527281100637880","twitter_id":"repbillenyart","phone":"202-225-5661"},"vote":["No"]},{"details":{"bioguide_id":"E000179","first_name":"Eliot","last_name":"Engel","facebook_id":"103355984852","twitter_id":"RepEliotEngel","phone":"202-225-2464"},"vote":["No"]},{"details":{"bioguide_id":"E000291","first_name":"Renee","last_name":"Ellmers","facebook_id":"167641493275000","twitter_id":"RepReneeEllmers","phone":"202-225-4531"},"vote":["No"]},{"details":{"bioguide_id":"E000288","first_name":"Keith","last_name":"Ellison","facebook_id":"7997462059","twitter_id":"keithellison","phone":"202-225-4755"},"vote":["Aye"]},{"details":{"bioguide_id":"E000290","first_name":"Donna","last_name":"Edwards","facebook_id":"107297211756","twitter_id":"repdonnaedwards","phone":"202-225-8699"},"vote":["Aye"]},{"details":{"bioguide_id":"D000533","first_name":"John","last_name":"Duncan","facebook_id":"102385999834718","twitter_id":null,"phone":"202-225-5435"},"vote":["Aye"]},{"details":{"bioguide_id":"D000615","first_name":"Jeff","last_name":"Duncan","facebook_id":"187268144624279","twitter_id":"RepJeffDuncan","phone":"202-225-5301"},"vote":["Aye"]},{"details":{"bioguide_id":"D000614","first_name":"Sean","last_name":"Duffy","facebook_id":"119657691436457","twitter_id":"RepSeanDuffy","phone":"202-225-3365"},"vote":["Aye"]},{"details":{"bioguide_id":"D000622","first_name":"Tammy","last_name":"Duckworth","facebook_id":"112300955610529","twitter_id":"repduckworth","phone":"202-225-3711"},"vote":["No"]},{"details":{"bioguide_id":"D000482","first_name":"Michael","last_name":"Doyle","facebook_id":"79663724861","twitter_id":"USRepMikeDoyle","phone":"202-225-2135"},"vote":["Aye"]},{"details":{"bioguide_id":"D000399","first_name":"Lloyd","last_name":"Doggett","facebook_id":"154050553704","twitter_id":"RepLloydDoggett","phone":"202-225-4865"},"vote":["Aye"]},{"details":{"bioguide_id":"D000355","first_name":"John","last_name":"Dingell","facebook_id":"87490183861","twitter_id":"john_dingell","phone":"202-225-4071"},"vote":["Aye"]},{"details":{"bioguide_id":"D000600","first_name":"Mario","last_name":"Diaz-Balart","facebook_id":"119538428117878","twitter_id":"MarioDB","phone":"202-225-4211"},"vote":["No"]},{"details":{"bioguide_id":"D000610","first_name":"Theodore","last_name":"Deutch","facebook_id":"112179098816942","twitter_id":"repteddeutch","phone":"202-225-3001"},"vote":["Aye"]},{"details":{"bioguide_id":"D000616","first_name":"Scott","last_name":"DesJarlais","facebook_id":"ScottDesJarlaisTN04","twitter_id":"DesJarlaisTN04","phone":"202-225-6831"},"vote":["Aye"]},{"details":{"bioguide_id":"D000621","first_name":"Ron","last_name":"DeSantis","facebook_id":"464253846966014","twitter_id":"RepDeSantis","phone":"202-225-2706"},"vote":["Aye"]},{"details":{"bioguide_id":"D000604","first_name":"Charles","last_name":"Dent","facebook_id":"69862092533","twitter_id":null,"phone":"202-225-6411"},"vote":["No"]},{"details":{"bioguide_id":"D000612","first_name":"Jeff","last_name":"Denham","facebook_id":"133714040028137","twitter_id":"RepJeffDenham","phone":"202-225-4540"},"vote":["No"]},{"details":{"bioguide_id":"D000617","first_name":"Suzan","last_name":"DelBene","facebook_id":"483962224987343","twitter_id":"RepDelBene","phone":"202-225-6311"},"vote":["Aye"]},{"details":{"bioguide_id":"D000216","first_name":"Rosa","last_name":"DeLauro","facebook_id":"181302611907634","twitter_id":"rosadelauro","phone":"202-225-3661"},"vote":["Aye"]},{"details":{"bioguide_id":"D000620","first_name":"John","last_name":"Delaney","facebook_id":"324527257655694","twitter_id":"RepJohnDelaney","phone":"202-225-2721"},"vote":["No"]},{"details":{"bioguide_id":"D000197","first_name":"Diana","last_name":"DeGette","facebook_id":"110757973488","twitter_id":"RepDianaDeGette","phone":"202-225-4431"},"vote":["Aye"]},{"details":{"bioguide_id":"D000191","first_name":"Peter","last_name":"DeFazio","facebook_id":"94324364811","twitter_id":"RepPeterDeFazio","phone":"202-225-6416"},"vote":["Aye"]},{"details":{"bioguide_id":"D000619","first_name":"Rodney","last_name":"Davis","facebook_id":"323631667743052","twitter_id":"RodneyDavis","phone":"202-225-2371"},"vote":["Aye"]},{"details":{"bioguide_id":"D000096","first_name":"Danny","last_name":"Davis","facebook_id":"280757931935749","twitter_id":null,"phone":"202-225-5006"},"vote":["Aye"]},{"details":{"bioguide_id":"D000598","first_name":"Susan","last_name":"Davis","facebook_id":"103767526332478","twitter_id":"RepSusanDavis","phone":"202-225-2040"},"vote":["No"]},{"details":{"bioguide_id":"D000618","first_name":"Steve","last_name":"Daines","facebook_id":"185361254941832","twitter_id":"stevedaines","phone":"202-225-3211"},"vote":["Aye"]},{"details":{"bioguide_id":"C000984","first_name":"Elijah","last_name":"Cummings","facebook_id":"291368465380","twitter_id":"ElijahECummings","phone":"202-225-4741"},"vote":["Aye"]},{"details":{"bioguide_id":"C001048","first_name":"John","last_name":"Culberson","facebook_id":"1751723339","twitter_id":"congculberson","phone":"202-225-2571"},"vote":["No"]},{"details":{"bioguide_id":"C001063","first_name":"Henry","last_name":"Cuellar","facebook_id":"152569121550","twitter_id":"RepCuellar","phone":"202-225-1640"},"vote":["No"]},{"details":{"bioguide_id":"C001038","first_name":"Joseph","last_name":"Crowley","facebook_id":"176197712425090","twitter_id":"repjoecrowley","phone":"202-225-3965"},"vote":["Aye"]},{"details":{"bioguide_id":"C001045","first_name":"Ander","last_name":"Crenshaw","facebook_id":"200388204657","twitter_id":"AnderCrenshaw","phone":"202-225-2501"},"vote":["No"]},{"details":{"bioguide_id":"C001087","first_name":"Eric","last_name":"Crawford","facebook_id":"143344975723788","twitter_id":"RepRickCrawford","phone":"202-225-4076"},"vote":["No"]},{"details":{"bioguide_id":"C001096","first_name":"Kevin","last_name":"Cramer","facebook_id":"498751820147706","twitter_id":"RepKevinCramer","phone":"202-225-2611"},"vote":["Aye"]},{"details":{"bioguide_id":"C001069","first_name":"Joe","last_name":"Courtney","facebook_id":"330408799230","twitter_id":"repjoecourtney","phone":"202-225-2076"},"vote":["Aye"]},{"details":{"bioguide_id":"C001095","first_name":"Tom","last_name":"Cotton","facebook_id":"120355701459307","twitter_id":"RepTomCotton","phone":"202-225-3772"},"vote":["No"]},{"details":{"bioguide_id":"C001059","first_name":"Jim","last_name":"Costa","facebook_id":"RepJimCosta","twitter_id":"repjimcosta","phone":"202-225-3341"},"vote":["No"]},{"details":{"bioguide_id":"C000754","first_name":"Jim","last_name":"Cooper","facebook_id":"JimCooper","twitter_id":"repjimcooper","phone":"202-225-4311"},"vote":["No"]},{"details":{"bioguide_id":"C001094","first_name":"Paul","last_name":"Cook","facebook_id":"464458413604093","twitter_id":"RepPaulCook","phone":"202-225-5861"},"vote":["No"]},{"details":{"bioguide_id":"C000714","first_name":"John","last_name":"Conyers","facebook_id":"206947066849","twitter_id":"repjohnconyers","phone":"202-225-5126"},"vote":["Aye"]},{"details":{"bioguide_id":"C001078","first_name":"Gerald","last_name":"Connolly","facebook_id":"177164035838","twitter_id":"GerryConnolly","phone":"202-225-1492"},"vote":["Aye"]},{"details":{"bioguide_id":"C001062","first_name":"K.","last_name":"Conaway","facebook_id":"203482063021985","twitter_id":"ConawayTX11","phone":"202-225-3605"},"vote":["No"]},{"details":{"bioguide_id":"C001092","first_name":"Chris","last_name":"Collins","facebook_id":"467047586692268","twitter_id":"RepChrisCollins","phone":"202-225-5265"},"vote":["No"]},{"details":{"bioguide_id":"C001093","first_name":"Doug","last_name":"Collins","facebook_id":"505646972800006","twitter_id":"RepDougCollins","phone":"202-225-9893"},"vote":["No"]},{"details":{"bioguide_id":"C001053","first_name":"Tom","last_name":"Cole","facebook_id":"146497782066300","twitter_id":"tomcoleok04","phone":"202-225-6165"},"vote":["No"]},{"details":{"bioguide_id":"C001068","first_name":"Steve","last_name":"Cohen","facebook_id":"111456965545421","twitter_id":"repcohen","phone":"202-225-3265"},"vote":["Aye"]},{"details":{"bioguide_id":"C001077","first_name":"Mike","last_name":"Coffman","facebook_id":"366142384492","twitter_id":"RepMikeCoffman","phone":"202-225-7882"},"vote":["Aye"]},{"details":{"bioguide_id":"C000556","first_name":"Howard","last_name":"Coble","facebook_id":"208742429162356","twitter_id":"HowardCoble","phone":"202-225-3065"},"vote":["Not Voting"]},{"details":{"bioguide_id":"C000537","first_name":"James","last_name":"Clyburn","facebook_id":"127744320598870","twitter_id":"clyburn","phone":"202-225-3315"},"vote":["Aye"]},{"details":{"bioguide_id":"C001061","first_name":"Emanuel","last_name":"Cleaver","facebook_id":"7954512692","twitter_id":"repcleaver","phone":"202-225-4535"},"vote":["Aye"]},{"details":{"bioguide_id":"C001049","first_name":"Wm.","last_name":"Clay","facebook_id":"109135405838588","twitter_id":null,"phone":"202-225-2406"},"vote":["Aye"]},{"details":{"bioguide_id":"C001067","first_name":"Yvette","last_name":"Clarke","facebook_id":"135031389892682","twitter_id":"YvetteClarke","phone":"202-225-6231"},"vote":["Aye"]},{"details":{"bioguide_id":"C001084","first_name":"David","last_name":"Cicilline","facebook_id":"186949061341027","twitter_id":"repcicilline","phone":"202-225-4911"},"vote":["Aye"]},{"details":{"bioguide_id":"C001080","first_name":"Judy","last_name":"Chu","facebook_id":"41228315130","twitter_id":"RepJudyChu","phone":"202-225-5464"},"vote":["Aye"]},{"details":{"bioguide_id":"C001076","first_name":"Jason","last_name":"Chaffetz","facebook_id":"390419731073316","twitter_id":"jasoninthehouse","phone":"202-225-7751"},"vote":["Aye"]},{"details":{"bioguide_id":"C000266","first_name":"Steve","last_name":"Chabot","facebook_id":"204705339555378","twitter_id":"RepSteveChabot","phone":"202-225-2216"},"vote":["Aye"]},{"details":{"bioguide_id":"C001091","first_name":"Joaquin","last_name":"Castro","facebook_id":"326420614138023","twitter_id":"JoaquinCastrotx","phone":"202-225-3236"},"vote":["No"]},{"details":{"bioguide_id":"C001066","first_name":"Kathy","last_name":"Castor","facebook_id":null,"twitter_id":null,"phone":"202-225-3376"},"vote":["No"]},{"details":{"bioguide_id":"C001075","first_name":"Bill","last_name":"Cassidy","facebook_id":null,"twitter_id":null,"phone":"202-225-3901"},"vote":["Aye"]},{"details":{"bioguide_id":"C001090","first_name":"Matthew","last_name":"Cartwright","facebook_id":"248507065275406","twitter_id":"RepCartwright","phone":"202-225-5546"},"vote":["Aye"]},{"details":{"bioguide_id":"C001051","first_name":"John","last_name":"Carter","facebook_id":"1287257083","twitter_id":"JudgeCarter","phone":"202-225-3864"},"vote":["No"]},{"details":{"bioguide_id":"C001072","first_name":"André","last_name":"Carson","facebook_id":"123884330964019","twitter_id":"RepAndreCarson","phone":"202-225-4011"},"vote":["Aye"]},{"details":{"bioguide_id":"C001083","first_name":"John","last_name":"Carney","facebook_id":"156024857781159","twitter_id":"johncarneyde","phone":"202-225-4165"},"vote":["No"]},{"details":{"bioguide_id":"C001097","first_name":"Tony","last_name":"Cárdenas","facebook_id":"485493954794945","twitter_id":"repcardenas","phone":"202-225-6131"},"vote":["Aye"]},{"details":{"bioguide_id":"C001037","first_name":"Michael","last_name":"Capuano","facebook_id":"151168844937573","twitter_id":null,"phone":"202-225-5111"},"vote":["Aye"]},{"details":{"bioguide_id":"C001036","first_name":"Lois","last_name":"Capps","facebook_id":"168989481141","twitter_id":"RepLoisCapps","phone":"202-225-3601"},"vote":["Aye"]},{"details":{"bioguide_id":"C001047","first_name":"Shelley","last_name":"Capito","facebook_id":"8057864757","twitter_id":"RepShelley","phone":"202-225-2711"},"vote":["No"]},{"details":{"bioguide_id":"C001046","first_name":"Eric","last_name":"Cantor","facebook_id":null,"twitter_id":"gopleader","phone":"202-225-2815"},"vote":["No"]},{"details":{"bioguide_id":"C001064","first_name":"John","last_name":"Campbell","facebook_id":"60546090739","twitter_id":"RepJohnCampbell","phone":"202-225-5611"},"vote":["Not Voting"]},{"details":{"bioguide_id":"C000071","first_name":"Dave","last_name":"Camp","facebook_id":"6775033524","twitter_id":"RepDaveCamp","phone":"202-225-3561"},"vote":["No"]},{"details":{"bioguide_id":"C000059","first_name":"Ken","last_name":"Calvert","facebook_id":"70063393423","twitter_id":"KenCalvert","phone":"202-225-1986"},"vote":["No"]},{"details":{"bioguide_id":"B001251","first_name":"George","last_name":"Butterfield","facebook_id":"274687979233940","twitter_id":"gkbutterfield","phone":"202-225-3101"},"vote":["No"]},{"details":{"bioguide_id":"B001286","first_name":"Cheri","last_name":"Bustos","facebook_id":"581909665168588","twitter_id":"RepCheri","phone":"202-225-5905"},"vote":["Not Voting"]},{"details":{"bioguide_id":"B001248","first_name":"Michael","last_name":"Burgess","facebook_id":"6916472567","twitter_id":"michaelcburgess","phone":"202-225-7772"},"vote":["Aye"]},{"details":{"bioguide_id":"B001275","first_name":"Larry","last_name":"Bucshon","facebook_id":"135670516492974","twitter_id":"RepLarryBucshon","phone":"202-225-4636"},"vote":["No"]},{"details":{"bioguide_id":"B001260","first_name":"Vern","last_name":"Buchanan","facebook_id":"67106719910","twitter_id":"VernBuchanan","phone":"202-225-5015"},"vote":["Aye"]},{"details":{"bioguide_id":"B001285","first_name":"Julia","last_name":"Brownley","facebook_id":"330504617051234","twitter_id":"JuliaBrownley26","phone":"202-225-5811"},"vote":["No"]},{"details":{"bioguide_id":"B000911","first_name":"Corrine","last_name":"Brown","facebook_id":"179120958813519","twitter_id":"RepCorrineBrown","phone":"202-225-0123"},"vote":["No"]},{"details":{"bioguide_id":"B001262","first_name":"Paul","last_name":"Broun","facebook_id":"123908980957273","twitter_id":"RepPaulBrounMD","phone":"202-225-4101"},"vote":["Aye"]},{"details":{"bioguide_id":"B001284","first_name":"Susan","last_name":"Brooks","facebook_id":"517697358277175","twitter_id":"SusanWBrooks","phone":"202-225-2276"},"vote":["No"]},{"details":{"bioguide_id":"B001274","first_name":"Mo","last_name":"Brooks","facebook_id":"155220881193244","twitter_id":"RepMoBrooks","phone":"202-225-4801"},"vote":["No"]},{"details":{"bioguide_id":"B001283","first_name":"Jim","last_name":"Bridenstine","facebook_id":"460003650715961","twitter_id":"RepJBridenstine","phone":"202-225-2211"},"vote":["Aye"]},{"details":{"bioguide_id":"B001259","first_name":"Bruce","last_name":"Braley","facebook_id":"178887452188843","twitter_id":"brucebraley","phone":"202-225-2911"},"vote":["Aye"]},{"details":{"bioguide_id":"B000755","first_name":"Kevin","last_name":"Brady","facebook_id":"9307301412","twitter_id":"RepKevinBrady","phone":"202-225-4901"},"vote":["No"]},{"details":{"bioguide_id":"B001227","first_name":"Robert","last_name":"Brady","facebook_id":"118845109487","twitter_id":"RepBrady","phone":"202-225-4731"},"vote":["Aye"]},{"details":{"bioguide_id":"B001255","first_name":"Charles","last_name":"Boustany","facebook_id":"197407646951718","twitter_id":"RepBoustany","phone":"202-225-2031"},"vote":["No"]},{"details":{"bioguide_id":"B001244","first_name":"Jo","last_name":"Bonner","facebook_id":"49704847429","twitter_id":"repjobonner","phone":"202-225-4931"},"vote":["No"]},{"details":{"bioguide_id":"B001278","first_name":"Suzanne","last_name":"Bonamici","facebook_id":"252633384817156","twitter_id":"RepBonamici","phone":"202-225-0855"},"vote":["Aye"]},{"details":{"bioguide_id":"B000589","first_name":"John","last_name":"Boehner","facebook_id":null,"twitter_id":"SpeakerBoehner","phone":"202-225-6205"},"vote":["No"]},{"details":{"bioguide_id":"B000574","first_name":"Earl","last_name":"Blumenauer","facebook_id":null,"twitter_id":"blumenauermedia","phone":"202-225-4811"},"vote":["Aye"]},{"details":{"bioguide_id":"B001243","first_name":"Marsha","last_name":"Blackburn","facebook_id":"6470828395","twitter_id":"MarshaBlackburn","phone":"202-225-2811"},"vote":["Aye"]},{"details":{"bioguide_id":"B001273","first_name":"Diane","last_name":"Black","facebook_id":"186436274719648","twitter_id":"RepDianeBlack","phone":"202-225-4231"},"vote":["Aye"]},{"details":{"bioguide_id":"B001250","first_name":"Rob","last_name":"Bishop","facebook_id":"RepRobBishop","twitter_id":"RepRobBishop","phone":"202-225-0453"},"vote":["Aye"]},{"details":{"bioguide_id":"B001242","first_name":"Timothy","last_name":"Bishop","facebook_id":"7940339401","twitter_id":"timbishopny","phone":"202-225-3826"},"vote":["No"]},{"details":{"bioguide_id":"B000490","first_name":"Sanford","last_name":"Bishop","facebook_id":"366739521606","twitter_id":"SanfordBishop","phone":"202-225-3631"},"vote":["No"]},{"details":{"bioguide_id":"B001257","first_name":"Gus","last_name":"Bilirakis","facebook_id":"135445766485586","twitter_id":"RepGusBilirakis","phone":"202-225-5755"},"vote":["No"]},{"details":{"bioguide_id":"B001287","first_name":"Ami","last_name":"Bera","facebook_id":"528662157146886","twitter_id":"repbera","phone":"202-225-5716"},"vote":["No"]},{"details":{"bioguide_id":"B001280","first_name":"Kerry","last_name":"Bentivolio","facebook_id":"316865541750741","twitter_id":"repkerryb","phone":"202-225-8171"},"vote":["Aye"]},{"details":{"bioguide_id":"B001271","first_name":"Dan","last_name":"Benishek","facebook_id":"124163654317596","twitter_id":"CongressmanDan","phone":"202-225-4735"},"vote":["No"]},{"details":{"bioguide_id":"B000287","first_name":"Xavier","last_name":"Becerra","facebook_id":"90311772229","twitter_id":"repbecerra","phone":"202-225-6235"},"vote":["Aye"]},{"details":{"bioguide_id":"B001281","first_name":"Joyce","last_name":"Beatty","twitter_id":"RepBeatty","phone":"202-225-4324"},"vote":["Not Voting"]},{"details":{"bioguide_id":"B001270","first_name":"Karen","last_name":"Bass","facebook_id":"190867440939405","twitter_id":"RepKarenBass","phone":"202-225-7084"},"vote":["Aye"]},{"details":{"bioguide_id":"B000213","first_name":"Joe","last_name":"Barton","facebook_id":"15617630596","twitter_id":"RepJoeBarton","phone":"202-225-2002"},"vote":["Aye"]},{"details":{"bioguide_id":"B001252","first_name":"John","last_name":"Barrow","facebook_id":"285483767395","twitter_id":"repjohnbarrow","phone":"202-225-2823"},"vote":["No"]},{"details":{"bioguide_id":"B001282","first_name":"Garland","last_name":"Barr","facebook_id":"457461137635018","twitter_id":"RepAndyBarr","phone":"202-225-4706"},"vote":["No"]},{"details":{"bioguide_id":"B001269","first_name":"Lou","last_name":"Barletta","facebook_id":"192357174108435","twitter_id":"RepLouBarletta","phone":"202-225-6511"},"vote":["Not Voting"]},{"details":{"bioguide_id":"B001279","first_name":"Ron","last_name":"Barber","facebook_id":"244907165625305","twitter_id":"RepRonBarber","phone":"202-225-2542"},"vote":["No"]},{"details":{"bioguide_id":"B000013","first_name":"Spencer","last_name":"Bachus","facebook_id":"6769692966","twitter_id":"BachusAL06","phone":"202-225-4921"},"vote":["Aye"]},{"details":{"bioguide_id":"B001256","first_name":"Michele","last_name":"Bachmann","facebook_id":"7658849357","twitter_id":"MicheleBachmann","phone":"202-225-2331"},"vote":["No"]},{"details":{"bioguide_id":"A000210","first_name":"Robert","last_name":"Andrews","phone":"202-225-6501"},"vote":["No"]},{"details":{"bioguide_id":"A000369","first_name":"Mark","last_name":"Amodei","facebook_id":"307227745970624","twitter_id":"markamodeinv2","phone":"202-225-6155"},"vote":["Aye"]},{"details":{"bioguide_id":"A000367","first_name":"Justin","last_name":"Amash","facebook_id":"173604349345646","twitter_id":"repjustinamash","phone":"202-225-3831"},"vote":["Aye"]},{"details":{"bioguide_id":"A000361","first_name":"Rodney","last_name":"Alexander","facebook_id":"20002702544","twitter_id":"USRepAlexander","phone":"202-225-8490"},"vote":["No"]},{"details":{"bioguide_id":"A000055","first_name":"Robert","last_name":"Aderholt","facebook_id":"19787529402","twitter_id":"Robert_Aderholt","phone":"202-225-4876"},"vote":["No"]}],"meta":[{"majority":["R"],"congress":["113"],"session":["1st"],"committee":["U.S. House of Representatives"],"rollcall-num":["412"],"legis-num":["H R 2397"],"vote-question":["On Agreeing to the Amendment"],"amendment-num":["70"],"amendment-author":["Amash of Michigan Amendment No. 100"],"vote-type":["RECORDED VOTE"],"vote-result":["Failed"],"action-date":["24-Jul-2013"],"action-time":[{"_":"6:51 PM","$":{"time-etz":"18:51"}}],"vote-desc":[""],"vote-totals":[{"totals-by-party-header":[{"party-header":["Party"],"yea-header":["Ayes"],"nay-header":["Noes"],"present-header":["Answered “Present”"],"not-voting-header":["Not Voting"]}],"totals-by-party":[{"party":["Republican"],"yea-total":["94"],"nay-total":["134"],"present-total":["0"],"not-voting-total":["6"]},{"party":["Democratic"],"yea-total":["111"],"nay-total":["83"],"present-total":["0"],"not-voting-total":["6"]},{"party":["Independent"],"yea-total":["0"],"nay-total":["0"],"present-total":["0"],"not-voting-total":["0"]}],"totals-by-vote":[{"total-stub":["Totals"],"yea-total":["205"],"nay-total":["217"],"present-total":["0"],"not-voting-total":["12"]}]}]}]} --------------------------------------------------------------------------------