We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
VA failing since at least 2018-03-21
VA has been failing since 2018-03-21

Based on automated runs it appears that VA has not run successfully in 3 days (2018-03-21).


```
  00:06:18 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+062
00:06:20 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+068
00:06:21 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+063
00:06:23 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+064
00:06:24 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+026
00:06:26 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+065
00:06:27 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+lst+ALL
00:06:29 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+mbr+HB5001
00:06:30 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sum+HB5001
00:06:32 INFO pupa: save bill HB 5001 in 2018specialI as bill_f1ff8050-2e57-11e8-8346-0a58a9feac2a.json
00:06:32 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+mbr+HB5002
00:06:33 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sum+HB5002
00:06:35 INFO pupa: save bill HB 5002 in 2018specialI as bill_f3c89dd6-2e57-11e8-8346-0a58a9feac2a.json
00:06:35 INFO pupa: no session specified, using 2018specialI
00:06:35 INFO scrapelib: GET - http://lis.virginia.gov/182/mbr/MBR.HTM
00:06:35 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+mbr+S71
loaded Open States pupa settings...
va (scrape, import)
  bills: {}
  people: {}
Traceback (most recent call last):
  File "/opt/openstates/venv-pupa//bin/pupa", line 11, in <module>
    load_entry_point('pupa', 'console_scripts', 'pupa')()
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/__main__.py", line 68, in main
    subcommands[args.subcommand].handle(args, other)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py", line 260, in handle
    return self.do_handle(args, other, juris)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py", line 305, in do_handle
    report['scrape'] = self.do_scrape(juris, args, scrapers)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py", line 173, in do_scrape
    report[scraper_name] = scraper.do_scrape(**scrape_args)
  File "/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py", line 111, in do_scrape
    for obj in self.scrape(**kwargs) or []:
  File "/opt/openstates/openstates/openstates/va/people.py", line 194, in scrape
    SenateList, session=session, url=url, committees=committees)
  File "/opt/openstates/venv-pupa/lib/python3.5/site-packages/spatula/base.py", line 11, in scrape_page_items
    yield from page_type(self, url=url, **kwargs).handle_page()
  File "/opt/openstates/venv-pupa/lib/python3.5/site-packages/spatula/base.py", line 69, in handle_page
    yield from processed
  File "/opt/openstates/openstates/openstates/va/people.py", line 135, in handle_list_item
    obj=leg,
  File "/opt/openstates/venv-pupa/lib/python3.5/site-packages/spatula/base.py", line 66, in handle_page
    processed = self.handle_list_item(item)
  File "/opt/openstates/openstates/openstates/va/people.py", line 36, in handle_list_item
    self.get_committees(item)
  File "/opt/openstates/openstates/openstates/va/people.py", line 79, in get_committees
    end_date=maybe_date(self.kwargs['session'].get('end_date')),
  File "/opt/openstates/openstates/openstates/va/people.py", line 203, in maybe_date
    date = datetime.datetime.strptime(text, '%Y-%d-%m')
TypeError: strptime() argument 1 must be str, not None
```

Visit http://bobsled.openstates.org for more info.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `openstates/va/people.py`
Content:
```
1 import re
2 import pytz
3 import datetime
4 
5 import lxml.html
6 from pupa.scrape import Person
7 from pupa.scrape import Scraper
8 from pupa.scrape import Organization
9 from spatula import Page, Spatula
10 
11 from .common import SESSION_SITE_IDS
12 
13 
14 CHAMBER_MOVES = {
15     "A. Benton \"Ben\" Chafin-Elect": "upper",
16     "A. Benton Chafin-Senate Elect": "upper",
17 }
18 PARTY_MAP = {
19     'R': 'Republican',
20     'D': 'Democratic',
21     'I': 'Independent',
22 }
23 TIMEZONE = pytz.timezone('US/Eastern')
24 
25 
26 class MemberDetail(Page):
27     list_xpath = '//body'
28 
29     def handle_list_item(self, item):
30         party_district_text = item.xpath('//h3/font/text()')[0]
31         party, district = get_party_district(party_district_text)
32         self.obj.add_term(self.role, self.chamber, district=district)
33         self.obj.add_party(PARTY_MAP[party])
34 
35         self.get_offices(item)
36         self.get_committees(item)
37 
38         photo_url = self.get_photo_url()
39         if photo_url is not None:
40             self.obj.image = photo_url
41 
42     def get_offices(self, item):
43         for ul in item.xpath('//ul[@class="linkNon" and normalize-space()]'):
44             address = []
45             phone = None
46             email = None
47             for li in ul.getchildren():
48                 text = li.text_content()
49                 if re.match('\(\d{3}\)', text):
50                     phone = text.strip()
51                 elif text.startswith('email:'):
52                     email = text.strip('email: ').strip()
53                 else:
54                     address.append(text.strip())
55                 office_type = ('Capitol Office' if 'Capitol Square' in address
56                                else 'District Office')
57 
58             self.obj.add_contact_detail(type='address', value='\n'.join(address), note=office_type)
59             if phone:
60                 self.obj.add_contact_detail(type='voice', value=phone, note=office_type)
61             if email:
62                 self.obj.add_contact_detail(type='email', value=email, note=office_type)
63 
64     def get_committees(self, item):
65         for com in item.xpath('//ul[@class="linkSect"][1]/li/a/text()'):
66             key = (com, self.chamber)
67             if key not in self.kwargs['committees']:
68                 org = Organization(
69                     name=com,
70                     chamber=self.chamber,
71                     classification='committee',
72                 )
73                 org.add_source(self.url)
74                 self.kwargs['committees'][key] = org
75 
76             self.obj.add_membership(
77                 self.kwargs['committees'][key],
78                 start_date=maybe_date(self.kwargs['session'].get('start_date')),
79                 end_date=maybe_date(self.kwargs['session'].get('end_date')),
80             )
81 
82     def get_photo_url(self):
83         pass
84 
85 
86 class SenateDetail(MemberDetail):
87     role = 'Senator'
88     chamber = 'upper'
89 
90     def get_photo_url(self):
91         lis_id = get_lis_id(self.chamber, self.url)
92         profile_url = 'http://apps.senate.virginia.gov/Senator/memberpage.php?id={}'.format(lis_id)
93         page = lxml.html.fromstring(self.scraper.get(profile_url).text)
94         src = page.xpath('.//img[@class="profile_pic"]/@src')
95         img = src[0] if src else None
96         if img.startswith('//'):
97             img = 'https:' + img
98         return img
99 
100 
101 class DelegateDetail(MemberDetail):
102     role = 'Delegate'
103     chamber = 'lower'
104 
105     def get_photo_url(self):
106         lis_id = get_lis_id(self.chamber, self.url)
107         if lis_id:
108             lis_id = '{}{:04d}'.format(lis_id[0], int(lis_id[1:]))
109             return (
110                 'http://memdata.virginiageneralassembly.gov'
111                 '/images/display_image/{}'
112             ).format(lis_id)
113 
114 
115 class MemberList(Page):
116     def handle_list_item(self, item):
117         name = item.text
118 
119         if 'resigned' in name.lower() or 'vacated' in name.lower():
120             return
121         if (name in CHAMBER_MOVES and(self.chamber != CHAMBER_MOVES[name])):
122             return
123 
124         name, action, date = clean_name(name)
125 
126         leg = Person(name=name)
127         leg.add_source(self.url)
128         leg.add_source(item.get('href'))
129         leg.add_link(item.get('href'))
130         yield from self.scrape_page(
131             self.detail_page,
132             item.get('href'),
133             session=self.kwargs['session'],
134             committees=self.kwargs['committees'],
135             obj=leg,
136         )
137         yield leg
138 
139 
140 party_district_pattern = re.compile(r'\((R|D|I)\) - (?:House|Senate) District\s+(\d+)')
141 
142 
143 def get_party_district(text):
144     return party_district_pattern.match(text).groups()
145 
146 
147 lis_id_patterns = {
148     'upper': re.compile(r'(S[0-9]+$)'),
149     'lower': re.compile(r'(H[0-9]+$)'),
150 }
151 
152 
153 def get_lis_id(chamber, url):
154     """Retrieve LIS ID of legislator from URL."""
155     match = re.search(lis_id_patterns[chamber], url)
156     if match.groups:
157         return match.group(1)
158 
159 
160 name_elect_pattern = re.compile(r'(- Elect)$')
161 
162 
163 def clean_name(name):
164     name = name_elect_pattern.sub('', name).strip()
165     action, date = (None, None)
166     match = re.search(r'-(Resigned|Member) (\d{1,2}/\d{1,2})?', name)
167     if match:
168         action, date = match.groups()
169         name = name.rsplit('-')[0]
170     return name, action, date
171 
172 
173 class SenateList(MemberList):
174     chamber = 'upper'
175     detail_page = SenateDetail
176     list_xpath = '//div[@class="lColRt"]/ul/li/a'
177 
178 
179 class DelegateList(MemberList):
180     chamber = 'lower'
181     detail_page = DelegateDetail
182     list_xpath = '//div[@class="lColLt"]/ul/li/a'
183 
184 
185 class VaPersonScraper(Scraper, Spatula):
186     def scrape(self, session=None):
187         if not session:
188             session = self.jurisdiction.legislative_sessions[-1]
189             self.info('no session specified, using %s', session['identifier'])
190         url = 'http://lis.virginia.gov/{}/mbr/MBR.HTM'.format(
191             SESSION_SITE_IDS[session['identifier']])
192         committees = {}
193         yield from self.scrape_page_items(
194             SenateList, session=session, url=url, committees=committees)
195         yield from self.scrape_page_items(
196             DelegateList, session=session, url=url, committees=committees)
197         for committee in committees.values():
198             yield committee
199 
200 
201 def maybe_date(text):
202     try:
203         date = datetime.datetime.strptime(text, '%Y-%d-%m')
204         return date.strftime('%Y-%m-%d')
205     except ValueError:
206         return ''
207
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/openstates/va/people.py b/openstates/va/people.py
--- a/openstates/va/people.py
+++ b/openstates/va/people.py
@@ -76,7 +76,7 @@
             self.obj.add_membership(
                 self.kwargs['committees'][key],
                 start_date=maybe_date(self.kwargs['session'].get('start_date')),
-                end_date=maybe_date(self.kwargs['session'].get('end_date')),
+                end_date=maybe_date(self.kwargs['session'].get('end_date', '')),
             )
 
     def get_photo_url(self):

{"golden_diff": "diff --git a/openstates/va/people.py b/openstates/va/people.py\n--- a/openstates/va/people.py\n+++ b/openstates/va/people.py\n@@ -76,7 +76,7 @@\n             self.obj.add_membership(\n                 self.kwargs['committees'][key],\n                 start_date=maybe_date(self.kwargs['session'].get('start_date')),\n-                end_date=maybe_date(self.kwargs['session'].get('end_date')),\n+                end_date=maybe_date(self.kwargs['session'].get('end_date', '')),\n             )\n \n     def get_photo_url(self):\n", "issue": "VA failing since at least 2018-03-21\nVA has been failing since 2018-03-21\n\nBased on automated runs it appears that VA has not run successfully in 3 days (2018-03-21).\n\n\n```\n  00:06:18 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+062\n00:06:20 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+068\n00:06:21 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+063\n00:06:23 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+064\n00:06:24 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+026\n00:06:26 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sbj+065\n00:06:27 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+lst+ALL\n00:06:29 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+mbr+HB5001\n00:06:30 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sum+HB5001\n00:06:32 INFO pupa: save bill HB 5001 in 2018specialI as bill_f1ff8050-2e57-11e8-8346-0a58a9feac2a.json\n00:06:32 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+mbr+HB5002\n00:06:33 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+sum+HB5002\n00:06:35 INFO pupa: save bill HB 5002 in 2018specialI as bill_f3c89dd6-2e57-11e8-8346-0a58a9feac2a.json\n00:06:35 INFO pupa: no session specified, using 2018specialI\n00:06:35 INFO scrapelib: GET - http://lis.virginia.gov/182/mbr/MBR.HTM\n00:06:35 INFO scrapelib: GET - http://lis.virginia.gov/cgi-bin/legp604.exe?182+mbr+S71\nloaded Open States pupa settings...\nva (scrape, import)\n  bills: {}\n  people: {}\nTraceback (most recent call last):\n  File \"/opt/openstates/venv-pupa//bin/pupa\", line 11, in <module>\n    load_entry_point('pupa', 'console_scripts', 'pupa')()\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/__main__.py\", line 68, in main\n    subcommands[args.subcommand].handle(args, other)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py\", line 260, in handle\n    return self.do_handle(args, other, juris)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py\", line 305, in do_handle\n    report['scrape'] = self.do_scrape(juris, args, scrapers)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/cli/commands/update.py\", line 173, in do_scrape\n    report[scraper_name] = scraper.do_scrape(**scrape_args)\n  File \"/opt/openstates/venv-pupa/src/pupa/pupa/scrape/base.py\", line 111, in do_scrape\n    for obj in self.scrape(**kwargs) or []:\n  File \"/opt/openstates/openstates/openstates/va/people.py\", line 194, in scrape\n    SenateList, session=session, url=url, committees=committees)\n  File \"/opt/openstates/venv-pupa/lib/python3.5/site-packages/spatula/base.py\", line 11, in scrape_page_items\n    yield from page_type(self, url=url, **kwargs).handle_page()\n  File \"/opt/openstates/venv-pupa/lib/python3.5/site-packages/spatula/base.py\", line 69, in handle_page\n    yield from processed\n  File \"/opt/openstates/openstates/openstates/va/people.py\", line 135, in handle_list_item\n    obj=leg,\n  File \"/opt/openstates/venv-pupa/lib/python3.5/site-packages/spatula/base.py\", line 66, in handle_page\n    processed = self.handle_list_item(item)\n  File \"/opt/openstates/openstates/openstates/va/people.py\", line 36, in handle_list_item\n    self.get_committees(item)\n  File \"/opt/openstates/openstates/openstates/va/people.py\", line 79, in get_committees\n    end_date=maybe_date(self.kwargs['session'].get('end_date')),\n  File \"/opt/openstates/openstates/openstates/va/people.py\", line 203, in maybe_date\n    date = datetime.datetime.strptime(text, '%Y-%d-%m')\nTypeError: strptime() argument 1 must be str, not None\n```\n\nVisit http://bobsled.openstates.org for more info.\n\n", "before_files": [{"content": "import re\nimport pytz\nimport datetime\n\nimport lxml.html\nfrom pupa.scrape import Person\nfrom pupa.scrape import Scraper\nfrom pupa.scrape import Organization\nfrom spatula import Page, Spatula\n\nfrom .common import SESSION_SITE_IDS\n\n\nCHAMBER_MOVES = {\n    \"A. Benton \\\"Ben\\\" Chafin-Elect\": \"upper\",\n    \"A. Benton Chafin-Senate Elect\": \"upper\",\n}\nPARTY_MAP = {\n    'R': 'Republican',\n    'D': 'Democratic',\n    'I': 'Independent',\n}\nTIMEZONE = pytz.timezone('US/Eastern')\n\n\nclass MemberDetail(Page):\n    list_xpath = '//body'\n\n    def handle_list_item(self, item):\n        party_district_text = item.xpath('//h3/font/text()')[0]\n        party, district = get_party_district(party_district_text)\n        self.obj.add_term(self.role, self.chamber, district=district)\n        self.obj.add_party(PARTY_MAP[party])\n\n        self.get_offices(item)\n        self.get_committees(item)\n\n        photo_url = self.get_photo_url()\n        if photo_url is not None:\n            self.obj.image = photo_url\n\n    def get_offices(self, item):\n        for ul in item.xpath('//ul[@class=\"linkNon\" and normalize-space()]'):\n            address = []\n            phone = None\n            email = None\n            for li in ul.getchildren():\n                text = li.text_content()\n                if re.match('\\(\\d{3}\\)', text):\n                    phone = text.strip()\n                elif text.startswith('email:'):\n                    email = text.strip('email: ').strip()\n                else:\n                    address.append(text.strip())\n                office_type = ('Capitol Office' if 'Capitol Square' in address\n                               else 'District Office')\n\n            self.obj.add_contact_detail(type='address', value='\\n'.join(address), note=office_type)\n            if phone:\n                self.obj.add_contact_detail(type='voice', value=phone, note=office_type)\n            if email:\n                self.obj.add_contact_detail(type='email', value=email, note=office_type)\n\n    def get_committees(self, item):\n        for com in item.xpath('//ul[@class=\"linkSect\"][1]/li/a/text()'):\n            key = (com, self.chamber)\n            if key not in self.kwargs['committees']:\n                org = Organization(\n                    name=com,\n                    chamber=self.chamber,\n                    classification='committee',\n                )\n                org.add_source(self.url)\n                self.kwargs['committees'][key] = org\n\n            self.obj.add_membership(\n                self.kwargs['committees'][key],\n                start_date=maybe_date(self.kwargs['session'].get('start_date')),\n                end_date=maybe_date(self.kwargs['session'].get('end_date')),\n            )\n\n    def get_photo_url(self):\n        pass\n\n\nclass SenateDetail(MemberDetail):\n    role = 'Senator'\n    chamber = 'upper'\n\n    def get_photo_url(self):\n        lis_id = get_lis_id(self.chamber, self.url)\n        profile_url = 'http://apps.senate.virginia.gov/Senator/memberpage.php?id={}'.format(lis_id)\n        page = lxml.html.fromstring(self.scraper.get(profile_url).text)\n        src = page.xpath('.//img[@class=\"profile_pic\"]/@src')\n        img = src[0] if src else None\n        if img.startswith('//'):\n            img = 'https:' + img\n        return img\n\n\nclass DelegateDetail(MemberDetail):\n    role = 'Delegate'\n    chamber = 'lower'\n\n    def get_photo_url(self):\n        lis_id = get_lis_id(self.chamber, self.url)\n        if lis_id:\n            lis_id = '{}{:04d}'.format(lis_id[0], int(lis_id[1:]))\n            return (\n                'http://memdata.virginiageneralassembly.gov'\n                '/images/display_image/{}'\n            ).format(lis_id)\n\n\nclass MemberList(Page):\n    def handle_list_item(self, item):\n        name = item.text\n\n        if 'resigned' in name.lower() or 'vacated' in name.lower():\n            return\n        if (name in CHAMBER_MOVES and(self.chamber != CHAMBER_MOVES[name])):\n            return\n\n        name, action, date = clean_name(name)\n\n        leg = Person(name=name)\n        leg.add_source(self.url)\n        leg.add_source(item.get('href'))\n        leg.add_link(item.get('href'))\n        yield from self.scrape_page(\n            self.detail_page,\n            item.get('href'),\n            session=self.kwargs['session'],\n            committees=self.kwargs['committees'],\n            obj=leg,\n        )\n        yield leg\n\n\nparty_district_pattern = re.compile(r'\\((R|D|I)\\) - (?:House|Senate) District\\s+(\\d+)')\n\n\ndef get_party_district(text):\n    return party_district_pattern.match(text).groups()\n\n\nlis_id_patterns = {\n    'upper': re.compile(r'(S[0-9]+$)'),\n    'lower': re.compile(r'(H[0-9]+$)'),\n}\n\n\ndef get_lis_id(chamber, url):\n    \"\"\"Retrieve LIS ID of legislator from URL.\"\"\"\n    match = re.search(lis_id_patterns[chamber], url)\n    if match.groups:\n        return match.group(1)\n\n\nname_elect_pattern = re.compile(r'(- Elect)$')\n\n\ndef clean_name(name):\n    name = name_elect_pattern.sub('', name).strip()\n    action, date = (None, None)\n    match = re.search(r'-(Resigned|Member) (\\d{1,2}/\\d{1,2})?', name)\n    if match:\n        action, date = match.groups()\n        name = name.rsplit('-')[0]\n    return name, action, date\n\n\nclass SenateList(MemberList):\n    chamber = 'upper'\n    detail_page = SenateDetail\n    list_xpath = '//div[@class=\"lColRt\"]/ul/li/a'\n\n\nclass DelegateList(MemberList):\n    chamber = 'lower'\n    detail_page = DelegateDetail\n    list_xpath = '//div[@class=\"lColLt\"]/ul/li/a'\n\n\nclass VaPersonScraper(Scraper, Spatula):\n    def scrape(self, session=None):\n        if not session:\n            session = self.jurisdiction.legislative_sessions[-1]\n            self.info('no session specified, using %s', session['identifier'])\n        url = 'http://lis.virginia.gov/{}/mbr/MBR.HTM'.format(\n            SESSION_SITE_IDS[session['identifier']])\n        committees = {}\n        yield from self.scrape_page_items(\n            SenateList, session=session, url=url, committees=committees)\n        yield from self.scrape_page_items(\n            DelegateList, session=session, url=url, committees=committees)\n        for committee in committees.values():\n            yield committee\n\n\ndef maybe_date(text):\n    try:\n        date = datetime.datetime.strptime(text, '%Y-%d-%m')\n        return date.strftime('%Y-%m-%d')\n    except ValueError:\n        return ''\n", "path": "openstates/va/people.py"}], "after_files": [{"content": "import re\nimport pytz\nimport datetime\n\nimport lxml.html\nfrom pupa.scrape import Person\nfrom pupa.scrape import Scraper\nfrom pupa.scrape import Organization\nfrom spatula import Page, Spatula\n\nfrom .common import SESSION_SITE_IDS\n\n\nCHAMBER_MOVES = {\n    \"A. Benton \\\"Ben\\\" Chafin-Elect\": \"upper\",\n    \"A. Benton Chafin-Senate Elect\": \"upper\",\n}\nPARTY_MAP = {\n    'R': 'Republican',\n    'D': 'Democratic',\n    'I': 'Independent',\n}\nTIMEZONE = pytz.timezone('US/Eastern')\n\n\nclass MemberDetail(Page):\n    list_xpath = '//body'\n\n    def handle_list_item(self, item):\n        party_district_text = item.xpath('//h3/font/text()')[0]\n        party, district = get_party_district(party_district_text)\n        self.obj.add_term(self.role, self.chamber, district=district)\n        self.obj.add_party(PARTY_MAP[party])\n\n        self.get_offices(item)\n        self.get_committees(item)\n\n        photo_url = self.get_photo_url()\n        if photo_url is not None:\n            self.obj.image = photo_url\n\n    def get_offices(self, item):\n        for ul in item.xpath('//ul[@class=\"linkNon\" and normalize-space()]'):\n            address = []\n            phone = None\n            email = None\n            for li in ul.getchildren():\n                text = li.text_content()\n                if re.match('\\(\\d{3}\\)', text):\n                    phone = text.strip()\n                elif text.startswith('email:'):\n                    email = text.strip('email: ').strip()\n                else:\n                    address.append(text.strip())\n                office_type = ('Capitol Office' if 'Capitol Square' in address\n                               else 'District Office')\n\n            self.obj.add_contact_detail(type='address', value='\\n'.join(address), note=office_type)\n            if phone:\n                self.obj.add_contact_detail(type='voice', value=phone, note=office_type)\n            if email:\n                self.obj.add_contact_detail(type='email', value=email, note=office_type)\n\n    def get_committees(self, item):\n        for com in item.xpath('//ul[@class=\"linkSect\"][1]/li/a/text()'):\n            key = (com, self.chamber)\n            if key not in self.kwargs['committees']:\n                org = Organization(\n                    name=com,\n                    chamber=self.chamber,\n                    classification='committee',\n                )\n                org.add_source(self.url)\n                self.kwargs['committees'][key] = org\n\n            self.obj.add_membership(\n                self.kwargs['committees'][key],\n                start_date=maybe_date(self.kwargs['session'].get('start_date')),\n                end_date=maybe_date(self.kwargs['session'].get('end_date', '')),\n            )\n\n    def get_photo_url(self):\n        pass\n\n\nclass SenateDetail(MemberDetail):\n    role = 'Senator'\n    chamber = 'upper'\n\n    def get_photo_url(self):\n        lis_id = get_lis_id(self.chamber, self.url)\n        profile_url = 'http://apps.senate.virginia.gov/Senator/memberpage.php?id={}'.format(lis_id)\n        page = lxml.html.fromstring(self.scraper.get(profile_url).text)\n        src = page.xpath('.//img[@class=\"profile_pic\"]/@src')\n        img = src[0] if src else None\n        if img.startswith('//'):\n            img = 'https:' + img\n        return img\n\n\nclass DelegateDetail(MemberDetail):\n    role = 'Delegate'\n    chamber = 'lower'\n\n    def get_photo_url(self):\n        lis_id = get_lis_id(self.chamber, self.url)\n        if lis_id:\n            lis_id = '{}{:04d}'.format(lis_id[0], int(lis_id[1:]))\n            return (\n                'http://memdata.virginiageneralassembly.gov'\n                '/images/display_image/{}'\n            ).format(lis_id)\n\n\nclass MemberList(Page):\n    def handle_list_item(self, item):\n        name = item.text\n\n        if 'resigned' in name.lower() or 'vacated' in name.lower():\n            return\n        if (name in CHAMBER_MOVES and(self.chamber != CHAMBER_MOVES[name])):\n            return\n\n        name, action, date = clean_name(name)\n\n        leg = Person(name=name)\n        leg.add_source(self.url)\n        leg.add_source(item.get('href'))\n        leg.add_link(item.get('href'))\n        yield from self.scrape_page(\n            self.detail_page,\n            item.get('href'),\n            session=self.kwargs['session'],\n            committees=self.kwargs['committees'],\n            obj=leg,\n        )\n        yield leg\n\n\nparty_district_pattern = re.compile(r'\\((R|D|I)\\) - (?:House|Senate) District\\s+(\\d+)')\n\n\ndef get_party_district(text):\n    return party_district_pattern.match(text).groups()\n\n\nlis_id_patterns = {\n    'upper': re.compile(r'(S[0-9]+$)'),\n    'lower': re.compile(r'(H[0-9]+$)'),\n}\n\n\ndef get_lis_id(chamber, url):\n    \"\"\"Retrieve LIS ID of legislator from URL.\"\"\"\n    match = re.search(lis_id_patterns[chamber], url)\n    if match.groups:\n        return match.group(1)\n\n\nname_elect_pattern = re.compile(r'(- Elect)$')\n\n\ndef clean_name(name):\n    name = name_elect_pattern.sub('', name).strip()\n    action, date = (None, None)\n    match = re.search(r'-(Resigned|Member) (\\d{1,2}/\\d{1,2})?', name)\n    if match:\n        action, date = match.groups()\n        name = name.rsplit('-')[0]\n    return name, action, date\n\n\nclass SenateList(MemberList):\n    chamber = 'upper'\n    detail_page = SenateDetail\n    list_xpath = '//div[@class=\"lColRt\"]/ul/li/a'\n\n\nclass DelegateList(MemberList):\n    chamber = 'lower'\n    detail_page = DelegateDetail\n    list_xpath = '//div[@class=\"lColLt\"]/ul/li/a'\n\n\nclass VaPersonScraper(Scraper, Spatula):\n    def scrape(self, session=None):\n        if not session:\n            session = self.jurisdiction.legislative_sessions[-1]\n            self.info('no session specified, using %s', session['identifier'])\n        url = 'http://lis.virginia.gov/{}/mbr/MBR.HTM'.format(\n            SESSION_SITE_IDS[session['identifier']])\n        committees = {}\n        yield from self.scrape_page_items(\n            SenateList, session=session, url=url, committees=committees)\n        yield from self.scrape_page_items(\n            DelegateList, session=session, url=url, committees=committees)\n        for committee in committees.values():\n            yield committee\n\n\ndef maybe_date(text):\n    try:\n        date = datetime.datetime.strptime(text, '%Y-%d-%m')\n        return date.strftime('%Y-%m-%d')\n    except ValueError:\n        return ''\n", "path": "openstates/va/people.py"}]}