We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
LA: "Total --" being saved as vote name
The [LA bill scraper](https://github.com/openstates/openstates/blame/master/openstates/la/bills.py#L180) is saving vote names as "Total --" instead of actual names.

Some examples:
Total -- 35, Total -- 0, Total -- 4, Total -- 34, Total -- 5

This is causing the vote to come back with a null leg_id, since the name cannot be matched back to a known legislator.

--- 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/la/bills.py`
Content:
```
1 import datetime as dt
2 import lxml.html
3 import scrapelib
4 import tempfile
5 import os
6 import re
7 from billy.scrape import ScrapeError
8 from billy.scrape.bills import BillScraper, Bill
9 from billy.scrape.votes import Vote
10 from billy.scrape.utils import pdf_to_lxml
11 from openstates.utils import LXMLMixin
12 
13 
14 class LABillScraper(BillScraper, LXMLMixin):
15     jurisdiction = 'la'
16 
17     _chambers = {
18         'S': 'upper',
19         'H': 'lower',
20         'J': 'joint',
21     }
22 
23     _bill_types = {
24         'B': 'bill',
25         'R': 'resolution',
26         'CR': 'concurrent resolution',
27         'SR': 'study request',
28         'CSR': 'concurrent study request',
29     }
30 
31     def _get_bill_abbreviations(self, session_id):
32         page = self.lxmlize('http://www.legis.la.gov/legis/BillSearch.aspx?'
33             'sid={}'.format(session_id))
34         select_options = page.xpath('//select[contains(@id, "InstTypes")]/option')
35 
36         bill_abbreviations = {
37             'upper': [],
38             'lower': [],
39         }
40 
41         for option in select_options:
42             type_text = option.text
43             if type_text.startswith('S'):
44                 bill_abbreviations['upper'].append(type_text)
45             elif type_text.startswith('H'):
46                 bill_abbreviations['lower'].append(type_text)
47 
48         return bill_abbreviations
49 
50     def do_post_back(self, page, event_target, event_argument):
51         form = page.xpath("//form[@id='aspnetForm']")[0]
52         block = {name: value for name, value in [(obj.name, obj.value)
53                     for obj in form.xpath(".//input")]}
54         block['__EVENTTARGET'] = event_target
55         block['__EVENTARGUMENT'] = event_argument
56         if form.method == "GET":
57             ret = lxml.html.fromstring(self.get(form.action, data=block).text)
58         elif form.method == "POST":
59             ret = lxml.html.fromstring(self.post(form.action, data=block).text)
60         else:
61             raise AssertionError("Unrecognized request type found: {}".format(
62                                  form.method))
63 
64         ret.make_links_absolute(form.action)
65         return ret
66 
67     def bill_pages(self, session_id, bill_abbreviation):
68         url = 'http://www.legis.la.gov/Legis/BillSearchListQ.aspx?s={}&r={}1*'\
69             .format(session_id, bill_abbreviation)
70         page = self.lxmlize(url)
71         yield page
72 
73         while True:
74             hrefs = page.xpath("//a[text()=' > ']")
75             if hrefs == [] or "disabled" in hrefs[0].attrib:
76                 return
77 
78             href = hrefs[0].attrib['href']
79             tokens = re.match(".*\(\'(?P<token>.*)\',\'.*", href).groupdict()
80 
81             page = self.do_post_back(
82                 page,
83                 tokens['token'],
84                 ""
85             )
86             if page:
87                 yield page
88 
89     def scrape_bare_page(self, url):
90         page = self.lxmlize(url)
91         return page.xpath("//a")
92 
93     def scrape(self, chamber, session):
94         session_id = self.metadata['session_details'][session]['_id']
95 
96         # Scan bill abbreviation list if necessary.
97         self._bill_abbreviations = self._get_bill_abbreviations(session_id)
98 
99         for bill_abbreviation in self._bill_abbreviations[chamber]:
100             for bill_page in self.bill_pages(session_id, bill_abbreviation):
101                 for bill in bill_page.xpath(
102                         "//a[contains(@href, 'BillInfo.aspx') and text()='more...']"):
103                     self.scrape_bill_page(chamber,
104                         session,
105                         bill.attrib['href'],
106                         bill_abbreviation)
107 
108     def get_one_xpath(self, page, xpath):
109         ret = page.xpath(xpath)
110         if len(ret) != 1:
111             raise Exception
112         return ret[0]
113 
114     def scrape_votes(self, bill, url):
115         text = self.get(url).text
116         page = lxml.html.fromstring(text)
117         page.make_links_absolute(url)
118 
119         for a in page.xpath("//a[contains(@href, 'ViewDocument.aspx')]"):
120             self.scrape_vote(bill, a.text, a.attrib['href'])
121 
122     def scrape_vote(self, bill, name, url):
123         match = re.match('^(Senate|House) Vote on [^,]*,(.*)$', name)
124 
125         if not match:
126             return
127 
128         chamber = {'Senate': 'upper', 'House': 'lower'}[match.group(1)]
129         motion = match.group(2).strip()
130 
131         if motion.startswith('FINAL PASSAGE'):
132             type = 'passage'
133         elif motion.startswith('AMENDMENT'):
134             type = 'amendment'
135         elif 'ON 3RD READING' in motion:
136             type = 'reading:3'
137         else:
138             type = 'other'
139 
140         vote = Vote(chamber, None, motion, None,
141                     None, None, None)
142         vote['type'] = type
143         vote.add_source(url)
144 
145         (fd, temp_path) = tempfile.mkstemp()
146         self.urlretrieve(url, temp_path)
147 
148         html = pdf_to_lxml(temp_path)
149         os.close(fd)
150         os.remove(temp_path)
151 
152         vote_type = None
153         total_re = re.compile('^Total--(\d+)$')
154         body = html.xpath('string(/html/body)')
155 
156         date_match = re.search('Date: (\d{1,2}/\d{1,2}/\d{4})', body)
157         try:
158             date = date_match.group(1)
159         except AttributeError:
160             self.warning("BAD VOTE: date error")
161             return
162 
163         vote['date'] = dt.datetime.strptime(date, '%m/%d/%Y')
164 
165         for line in body.replace(u'\xa0', '\n').split('\n'):
166             line = line.replace('&nbsp;', '').strip()
167             if not line:
168                 continue
169 
170             if line in ('YEAS', 'NAYS', 'ABSENT'):
171                 vote_type = {'YEAS': 'yes', 'NAYS': 'no',
172                              'ABSENT': 'other'}[line]
173             elif line in ('Total', '--'):
174                 vote_type = None
175             elif vote_type:
176                 match = total_re.match(line)
177                 if match:
178                     vote['%s_count' % vote_type] = int(match.group(1))
179                 elif vote_type == 'yes':
180                     vote.yes(line)
181                 elif vote_type == 'no':
182                     vote.no(line)
183                 elif vote_type == 'other':
184                     vote.other(line)
185 
186         # tally counts
187         vote['yes_count'] = len(vote['yes_votes'])
188         vote['no_count'] = len(vote['no_votes'])
189         vote['other_count'] = len(vote['other_votes'])
190 
191         # The PDFs oddly don't say whether a vote passed or failed.
192         # Hopefully passage just requires yes_votes > not_yes_votes
193         if vote['yes_count'] > (vote['no_count'] + vote['other_count']):
194             vote['passed'] = True
195         else:
196             vote['passed'] = False
197 
198         bill.add_vote(vote)
199 
200     def scrape_bill_page(self, chamber, session, bill_url, bill_abbreviation):
201         page = self.lxmlize(bill_url)
202         author = self.get_one_xpath(
203             page,
204             "//a[@id='ctl00_PageBody_LinkAuthor']/text()"
205         )
206 
207         sbp = lambda x: self.scrape_bare_page(page.xpath(
208             "//a[contains(text(), '%s')]" % (x))[0].attrib['href'])
209 
210         authors = [x.text for x in sbp("Authors")]
211 
212         try:
213             digests = sbp("Digests")
214         except IndexError:
215             digests = []
216 
217         try:
218             versions = sbp("Text")
219         except IndexError:
220             versions = []
221 
222         try:
223             amendments = sbp("Amendments")
224         except IndexError:
225             amendments = []
226 
227         title = page.xpath(
228             "//span[@id='ctl00_PageBody_LabelShortTitle']/text()")[0]
229         actions = page.xpath(
230             "//div[@id='ctl00_PageBody_PanelBillInfo']/"
231             "/table[@style='font-size:small']/tr")
232 
233         bill_id = page.xpath(
234             "//span[@id='ctl00_PageBody_LabelBillID']/text()")[0]
235         bill_type = LABillScraper._bill_types[bill_abbreviation[1:]]
236         bill = Bill(session, chamber, bill_id, title, type=bill_type)
237         bill.add_source(bill_url)
238 
239         authors.remove(author)
240         bill.add_sponsor('primary', author)
241         for author in authors:
242             bill.add_sponsor('cosponsor', author)
243 
244         for digest in digests:
245             bill.add_document(digest.text,
246                               digest.attrib['href'],
247                               mimetype="application/pdf")
248 
249         for version in versions:
250             bill.add_version(version.text,
251                              version.attrib['href'],
252                              mimetype="application/pdf")
253 
254         for amendment in amendments:
255             bill.add_version(amendment.text,
256                              amendment.attrib['href'],
257                              mimetype="application/pdf")
258 
259         flags = {
260             "prefiled": ["bill:filed"],
261             "referred to the committee": ["committee:referred"],
262             "sent to the house": ['bill:passed'],
263             "ordered returned to the house": ['bill:passed'],
264             "ordered to the senate": ['bill:passed'],
265             "signed by the governor": ['governor:signed'],
266             "sent to the governor": ['governor:received'],
267         }
268 
269         try:
270             votes_link = page.xpath("//a[text() = 'Votes']")[0]
271             self.scrape_votes(bill, votes_link.attrib['href'])
272         except IndexError:
273             # Some bills don't have any votes
274             pass
275 
276         for action in actions:
277             date, chamber, page, text = [x.text for x in action.xpath(".//td")]
278             session_year = self.metadata['session_details'][session]\
279                 ['start_date'].year
280             # Session is April -> June. Prefiles look like they're in
281             # January at earliest.
282             date += '/{}'.format(session_year)
283             date = dt.datetime.strptime(date, '%m/%d/%Y')
284             chamber = LABillScraper._chambers[chamber]
285 
286             cat = []
287             for flag in flags:
288                 if flag in text.lower():
289                     cat += flags[flag]
290 
291             if cat == []:
292                 cat = ["other"]
293             bill.add_action(chamber, text, date, cat)
294 
295         self.save_bill(bill)
296
```
--- 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/la/bills.py b/openstates/la/bills.py
--- a/openstates/la/bills.py
+++ b/openstates/la/bills.py
@@ -150,7 +150,6 @@
         os.remove(temp_path)
 
         vote_type = None
-        total_re = re.compile('^Total--(\d+)$')
         body = html.xpath('string(/html/body)')
 
         date_match = re.search('Date: (\d{1,2}/\d{1,2}/\d{4})', body)
@@ -164,7 +163,8 @@
 
         for line in body.replace(u'\xa0', '\n').split('\n'):
             line = line.replace('&nbsp;', '').strip()
-            if not line:
+            # Skip blank lines and "Total --"
+            if not line or 'Total --' in line:
                 continue
 
             if line in ('YEAS', 'NAYS', 'ABSENT'):
@@ -173,10 +173,7 @@
             elif line in ('Total', '--'):
                 vote_type = None
             elif vote_type:
-                match = total_re.match(line)
-                if match:
-                    vote['%s_count' % vote_type] = int(match.group(1))
-                elif vote_type == 'yes':
+                if vote_type == 'yes':
                     vote.yes(line)
                 elif vote_type == 'no':
                     vote.no(line)

{"golden_diff": "diff --git a/openstates/la/bills.py b/openstates/la/bills.py\n--- a/openstates/la/bills.py\n+++ b/openstates/la/bills.py\n@@ -150,7 +150,6 @@\n         os.remove(temp_path)\n \n         vote_type = None\n-        total_re = re.compile('^Total--(\\d+)$')\n         body = html.xpath('string(/html/body)')\n \n         date_match = re.search('Date: (\\d{1,2}/\\d{1,2}/\\d{4})', body)\n@@ -164,7 +163,8 @@\n \n         for line in body.replace(u'\\xa0', '\\n').split('\\n'):\n             line = line.replace('&nbsp;', '').strip()\n-            if not line:\n+            # Skip blank lines and \"Total --\"\n+            if not line or 'Total --' in line:\n                 continue\n \n             if line in ('YEAS', 'NAYS', 'ABSENT'):\n@@ -173,10 +173,7 @@\n             elif line in ('Total', '--'):\n                 vote_type = None\n             elif vote_type:\n-                match = total_re.match(line)\n-                if match:\n-                    vote['%s_count' % vote_type] = int(match.group(1))\n-                elif vote_type == 'yes':\n+                if vote_type == 'yes':\n                     vote.yes(line)\n                 elif vote_type == 'no':\n                     vote.no(line)\n", "issue": "LA: \"Total --\" being saved as vote name\nThe [LA bill scraper](https://github.com/openstates/openstates/blame/master/openstates/la/bills.py#L180) is saving vote names as \"Total --\" instead of actual names.\r\n\r\nSome examples:\r\nTotal -- 35, Total -- 0, Total -- 4, Total -- 34, Total -- 5\r\n\r\nThis is causing the vote to come back with a null leg_id, since the name cannot be matched back to a known legislator.\n", "before_files": [{"content": "import datetime as dt\nimport lxml.html\nimport scrapelib\nimport tempfile\nimport os\nimport re\nfrom billy.scrape import ScrapeError\nfrom billy.scrape.bills import BillScraper, Bill\nfrom billy.scrape.votes import Vote\nfrom billy.scrape.utils import pdf_to_lxml\nfrom openstates.utils import LXMLMixin\n\n\nclass LABillScraper(BillScraper, LXMLMixin):\n    jurisdiction = 'la'\n\n    _chambers = {\n        'S': 'upper',\n        'H': 'lower',\n        'J': 'joint',\n    }\n\n    _bill_types = {\n        'B': 'bill',\n        'R': 'resolution',\n        'CR': 'concurrent resolution',\n        'SR': 'study request',\n        'CSR': 'concurrent study request',\n    }\n\n    def _get_bill_abbreviations(self, session_id):\n        page = self.lxmlize('http://www.legis.la.gov/legis/BillSearch.aspx?'\n            'sid={}'.format(session_id))\n        select_options = page.xpath('//select[contains(@id, \"InstTypes\")]/option')\n\n        bill_abbreviations = {\n            'upper': [],\n            'lower': [],\n        }\n\n        for option in select_options:\n            type_text = option.text\n            if type_text.startswith('S'):\n                bill_abbreviations['upper'].append(type_text)\n            elif type_text.startswith('H'):\n                bill_abbreviations['lower'].append(type_text)\n\n        return bill_abbreviations\n\n    def do_post_back(self, page, event_target, event_argument):\n        form = page.xpath(\"//form[@id='aspnetForm']\")[0]\n        block = {name: value for name, value in [(obj.name, obj.value)\n                    for obj in form.xpath(\".//input\")]}\n        block['__EVENTTARGET'] = event_target\n        block['__EVENTARGUMENT'] = event_argument\n        if form.method == \"GET\":\n            ret = lxml.html.fromstring(self.get(form.action, data=block).text)\n        elif form.method == \"POST\":\n            ret = lxml.html.fromstring(self.post(form.action, data=block).text)\n        else:\n            raise AssertionError(\"Unrecognized request type found: {}\".format(\n                                 form.method))\n\n        ret.make_links_absolute(form.action)\n        return ret\n\n    def bill_pages(self, session_id, bill_abbreviation):\n        url = 'http://www.legis.la.gov/Legis/BillSearchListQ.aspx?s={}&r={}1*'\\\n            .format(session_id, bill_abbreviation)\n        page = self.lxmlize(url)\n        yield page\n\n        while True:\n            hrefs = page.xpath(\"//a[text()=' > ']\")\n            if hrefs == [] or \"disabled\" in hrefs[0].attrib:\n                return\n\n            href = hrefs[0].attrib['href']\n            tokens = re.match(\".*\\(\\'(?P<token>.*)\\',\\'.*\", href).groupdict()\n\n            page = self.do_post_back(\n                page,\n                tokens['token'],\n                \"\"\n            )\n            if page:\n                yield page\n\n    def scrape_bare_page(self, url):\n        page = self.lxmlize(url)\n        return page.xpath(\"//a\")\n\n    def scrape(self, chamber, session):\n        session_id = self.metadata['session_details'][session]['_id']\n\n        # Scan bill abbreviation list if necessary.\n        self._bill_abbreviations = self._get_bill_abbreviations(session_id)\n\n        for bill_abbreviation in self._bill_abbreviations[chamber]:\n            for bill_page in self.bill_pages(session_id, bill_abbreviation):\n                for bill in bill_page.xpath(\n                        \"//a[contains(@href, 'BillInfo.aspx') and text()='more...']\"):\n                    self.scrape_bill_page(chamber,\n                        session,\n                        bill.attrib['href'],\n                        bill_abbreviation)\n\n    def get_one_xpath(self, page, xpath):\n        ret = page.xpath(xpath)\n        if len(ret) != 1:\n            raise Exception\n        return ret[0]\n\n    def scrape_votes(self, bill, url):\n        text = self.get(url).text\n        page = lxml.html.fromstring(text)\n        page.make_links_absolute(url)\n\n        for a in page.xpath(\"//a[contains(@href, 'ViewDocument.aspx')]\"):\n            self.scrape_vote(bill, a.text, a.attrib['href'])\n\n    def scrape_vote(self, bill, name, url):\n        match = re.match('^(Senate|House) Vote on [^,]*,(.*)$', name)\n\n        if not match:\n            return\n\n        chamber = {'Senate': 'upper', 'House': 'lower'}[match.group(1)]\n        motion = match.group(2).strip()\n\n        if motion.startswith('FINAL PASSAGE'):\n            type = 'passage'\n        elif motion.startswith('AMENDMENT'):\n            type = 'amendment'\n        elif 'ON 3RD READING' in motion:\n            type = 'reading:3'\n        else:\n            type = 'other'\n\n        vote = Vote(chamber, None, motion, None,\n                    None, None, None)\n        vote['type'] = type\n        vote.add_source(url)\n\n        (fd, temp_path) = tempfile.mkstemp()\n        self.urlretrieve(url, temp_path)\n\n        html = pdf_to_lxml(temp_path)\n        os.close(fd)\n        os.remove(temp_path)\n\n        vote_type = None\n        total_re = re.compile('^Total--(\\d+)$')\n        body = html.xpath('string(/html/body)')\n\n        date_match = re.search('Date: (\\d{1,2}/\\d{1,2}/\\d{4})', body)\n        try:\n            date = date_match.group(1)\n        except AttributeError:\n            self.warning(\"BAD VOTE: date error\")\n            return\n\n        vote['date'] = dt.datetime.strptime(date, '%m/%d/%Y')\n\n        for line in body.replace(u'\\xa0', '\\n').split('\\n'):\n            line = line.replace('&nbsp;', '').strip()\n            if not line:\n                continue\n\n            if line in ('YEAS', 'NAYS', 'ABSENT'):\n                vote_type = {'YEAS': 'yes', 'NAYS': 'no',\n                             'ABSENT': 'other'}[line]\n            elif line in ('Total', '--'):\n                vote_type = None\n            elif vote_type:\n                match = total_re.match(line)\n                if match:\n                    vote['%s_count' % vote_type] = int(match.group(1))\n                elif vote_type == 'yes':\n                    vote.yes(line)\n                elif vote_type == 'no':\n                    vote.no(line)\n                elif vote_type == 'other':\n                    vote.other(line)\n\n        # tally counts\n        vote['yes_count'] = len(vote['yes_votes'])\n        vote['no_count'] = len(vote['no_votes'])\n        vote['other_count'] = len(vote['other_votes'])\n\n        # The PDFs oddly don't say whether a vote passed or failed.\n        # Hopefully passage just requires yes_votes > not_yes_votes\n        if vote['yes_count'] > (vote['no_count'] + vote['other_count']):\n            vote['passed'] = True\n        else:\n            vote['passed'] = False\n\n        bill.add_vote(vote)\n\n    def scrape_bill_page(self, chamber, session, bill_url, bill_abbreviation):\n        page = self.lxmlize(bill_url)\n        author = self.get_one_xpath(\n            page,\n            \"//a[@id='ctl00_PageBody_LinkAuthor']/text()\"\n        )\n\n        sbp = lambda x: self.scrape_bare_page(page.xpath(\n            \"//a[contains(text(), '%s')]\" % (x))[0].attrib['href'])\n\n        authors = [x.text for x in sbp(\"Authors\")]\n\n        try:\n            digests = sbp(\"Digests\")\n        except IndexError:\n            digests = []\n\n        try:\n            versions = sbp(\"Text\")\n        except IndexError:\n            versions = []\n\n        try:\n            amendments = sbp(\"Amendments\")\n        except IndexError:\n            amendments = []\n\n        title = page.xpath(\n            \"//span[@id='ctl00_PageBody_LabelShortTitle']/text()\")[0]\n        actions = page.xpath(\n            \"//div[@id='ctl00_PageBody_PanelBillInfo']/\"\n            \"/table[@style='font-size:small']/tr\")\n\n        bill_id = page.xpath(\n            \"//span[@id='ctl00_PageBody_LabelBillID']/text()\")[0]\n        bill_type = LABillScraper._bill_types[bill_abbreviation[1:]]\n        bill = Bill(session, chamber, bill_id, title, type=bill_type)\n        bill.add_source(bill_url)\n\n        authors.remove(author)\n        bill.add_sponsor('primary', author)\n        for author in authors:\n            bill.add_sponsor('cosponsor', author)\n\n        for digest in digests:\n            bill.add_document(digest.text,\n                              digest.attrib['href'],\n                              mimetype=\"application/pdf\")\n\n        for version in versions:\n            bill.add_version(version.text,\n                             version.attrib['href'],\n                             mimetype=\"application/pdf\")\n\n        for amendment in amendments:\n            bill.add_version(amendment.text,\n                             amendment.attrib['href'],\n                             mimetype=\"application/pdf\")\n\n        flags = {\n            \"prefiled\": [\"bill:filed\"],\n            \"referred to the committee\": [\"committee:referred\"],\n            \"sent to the house\": ['bill:passed'],\n            \"ordered returned to the house\": ['bill:passed'],\n            \"ordered to the senate\": ['bill:passed'],\n            \"signed by the governor\": ['governor:signed'],\n            \"sent to the governor\": ['governor:received'],\n        }\n\n        try:\n            votes_link = page.xpath(\"//a[text() = 'Votes']\")[0]\n            self.scrape_votes(bill, votes_link.attrib['href'])\n        except IndexError:\n            # Some bills don't have any votes\n            pass\n\n        for action in actions:\n            date, chamber, page, text = [x.text for x in action.xpath(\".//td\")]\n            session_year = self.metadata['session_details'][session]\\\n                ['start_date'].year\n            # Session is April -> June. Prefiles look like they're in\n            # January at earliest.\n            date += '/{}'.format(session_year)\n            date = dt.datetime.strptime(date, '%m/%d/%Y')\n            chamber = LABillScraper._chambers[chamber]\n\n            cat = []\n            for flag in flags:\n                if flag in text.lower():\n                    cat += flags[flag]\n\n            if cat == []:\n                cat = [\"other\"]\n            bill.add_action(chamber, text, date, cat)\n\n        self.save_bill(bill)\n", "path": "openstates/la/bills.py"}], "after_files": [{"content": "import datetime as dt\nimport lxml.html\nimport scrapelib\nimport tempfile\nimport os\nimport re\nfrom billy.scrape import ScrapeError\nfrom billy.scrape.bills import BillScraper, Bill\nfrom billy.scrape.votes import Vote\nfrom billy.scrape.utils import pdf_to_lxml\nfrom openstates.utils import LXMLMixin\n\n\nclass LABillScraper(BillScraper, LXMLMixin):\n    jurisdiction = 'la'\n\n    _chambers = {\n        'S': 'upper',\n        'H': 'lower',\n        'J': 'joint',\n    }\n\n    _bill_types = {\n        'B': 'bill',\n        'R': 'resolution',\n        'CR': 'concurrent resolution',\n        'SR': 'study request',\n        'CSR': 'concurrent study request',\n    }\n\n    def _get_bill_abbreviations(self, session_id):\n        page = self.lxmlize('http://www.legis.la.gov/legis/BillSearch.aspx?'\n            'sid={}'.format(session_id))\n        select_options = page.xpath('//select[contains(@id, \"InstTypes\")]/option')\n\n        bill_abbreviations = {\n            'upper': [],\n            'lower': [],\n        }\n\n        for option in select_options:\n            type_text = option.text\n            if type_text.startswith('S'):\n                bill_abbreviations['upper'].append(type_text)\n            elif type_text.startswith('H'):\n                bill_abbreviations['lower'].append(type_text)\n\n        return bill_abbreviations\n\n    def do_post_back(self, page, event_target, event_argument):\n        form = page.xpath(\"//form[@id='aspnetForm']\")[0]\n        block = {name: value for name, value in [(obj.name, obj.value)\n                    for obj in form.xpath(\".//input\")]}\n        block['__EVENTTARGET'] = event_target\n        block['__EVENTARGUMENT'] = event_argument\n        if form.method == \"GET\":\n            ret = lxml.html.fromstring(self.get(form.action, data=block).text)\n        elif form.method == \"POST\":\n            ret = lxml.html.fromstring(self.post(form.action, data=block).text)\n        else:\n            raise AssertionError(\"Unrecognized request type found: {}\".format(\n                                 form.method))\n\n        ret.make_links_absolute(form.action)\n        return ret\n\n    def bill_pages(self, session_id, bill_abbreviation):\n        url = 'http://www.legis.la.gov/Legis/BillSearchListQ.aspx?s={}&r={}1*'\\\n            .format(session_id, bill_abbreviation)\n        page = self.lxmlize(url)\n        yield page\n\n        while True:\n            hrefs = page.xpath(\"//a[text()=' > ']\")\n            if hrefs == [] or \"disabled\" in hrefs[0].attrib:\n                return\n\n            href = hrefs[0].attrib['href']\n            tokens = re.match(\".*\\(\\'(?P<token>.*)\\',\\'.*\", href).groupdict()\n\n            page = self.do_post_back(\n                page,\n                tokens['token'],\n                \"\"\n            )\n            if page:\n                yield page\n\n    def scrape_bare_page(self, url):\n        page = self.lxmlize(url)\n        return page.xpath(\"//a\")\n\n    def scrape(self, chamber, session):\n        session_id = self.metadata['session_details'][session]['_id']\n\n        # Scan bill abbreviation list if necessary.\n        self._bill_abbreviations = self._get_bill_abbreviations(session_id)\n\n        for bill_abbreviation in self._bill_abbreviations[chamber]:\n            for bill_page in self.bill_pages(session_id, bill_abbreviation):\n                for bill in bill_page.xpath(\n                        \"//a[contains(@href, 'BillInfo.aspx') and text()='more...']\"):\n                    self.scrape_bill_page(chamber,\n                        session,\n                        bill.attrib['href'],\n                        bill_abbreviation)\n\n    def get_one_xpath(self, page, xpath):\n        ret = page.xpath(xpath)\n        if len(ret) != 1:\n            raise Exception\n        return ret[0]\n\n    def scrape_votes(self, bill, url):\n        text = self.get(url).text\n        page = lxml.html.fromstring(text)\n        page.make_links_absolute(url)\n\n        for a in page.xpath(\"//a[contains(@href, 'ViewDocument.aspx')]\"):\n            self.scrape_vote(bill, a.text, a.attrib['href'])\n\n    def scrape_vote(self, bill, name, url):\n        match = re.match('^(Senate|House) Vote on [^,]*,(.*)$', name)\n\n        if not match:\n            return\n\n        chamber = {'Senate': 'upper', 'House': 'lower'}[match.group(1)]\n        motion = match.group(2).strip()\n\n        if motion.startswith('FINAL PASSAGE'):\n            type = 'passage'\n        elif motion.startswith('AMENDMENT'):\n            type = 'amendment'\n        elif 'ON 3RD READING' in motion:\n            type = 'reading:3'\n        else:\n            type = 'other'\n\n        vote = Vote(chamber, None, motion, None,\n                    None, None, None)\n        vote['type'] = type\n        vote.add_source(url)\n\n        (fd, temp_path) = tempfile.mkstemp()\n        self.urlretrieve(url, temp_path)\n\n        html = pdf_to_lxml(temp_path)\n        os.close(fd)\n        os.remove(temp_path)\n\n        vote_type = None\n        body = html.xpath('string(/html/body)')\n\n        date_match = re.search('Date: (\\d{1,2}/\\d{1,2}/\\d{4})', body)\n        try:\n            date = date_match.group(1)\n        except AttributeError:\n            self.warning(\"BAD VOTE: date error\")\n            return\n\n        vote['date'] = dt.datetime.strptime(date, '%m/%d/%Y')\n\n        for line in body.replace(u'\\xa0', '\\n').split('\\n'):\n            line = line.replace('&nbsp;', '').strip()\n            # Skip blank lines and \"Total --\"\n            if not line or 'Total --' in line:\n                continue\n\n            if line in ('YEAS', 'NAYS', 'ABSENT'):\n                vote_type = {'YEAS': 'yes', 'NAYS': 'no',\n                             'ABSENT': 'other'}[line]\n            elif line in ('Total', '--'):\n                vote_type = None\n            elif vote_type:\n                if vote_type == 'yes':\n                    vote.yes(line)\n                elif vote_type == 'no':\n                    vote.no(line)\n                elif vote_type == 'other':\n                    vote.other(line)\n\n        # tally counts\n        vote['yes_count'] = len(vote['yes_votes'])\n        vote['no_count'] = len(vote['no_votes'])\n        vote['other_count'] = len(vote['other_votes'])\n\n        # The PDFs oddly don't say whether a vote passed or failed.\n        # Hopefully passage just requires yes_votes > not_yes_votes\n        if vote['yes_count'] > (vote['no_count'] + vote['other_count']):\n            vote['passed'] = True\n        else:\n            vote['passed'] = False\n\n        bill.add_vote(vote)\n\n    def scrape_bill_page(self, chamber, session, bill_url, bill_abbreviation):\n        page = self.lxmlize(bill_url)\n        author = self.get_one_xpath(\n            page,\n            \"//a[@id='ctl00_PageBody_LinkAuthor']/text()\"\n        )\n\n        sbp = lambda x: self.scrape_bare_page(page.xpath(\n            \"//a[contains(text(), '%s')]\" % (x))[0].attrib['href'])\n\n        authors = [x.text for x in sbp(\"Authors\")]\n\n        try:\n            digests = sbp(\"Digests\")\n        except IndexError:\n            digests = []\n\n        try:\n            versions = sbp(\"Text\")\n        except IndexError:\n            versions = []\n\n        try:\n            amendments = sbp(\"Amendments\")\n        except IndexError:\n            amendments = []\n\n        title = page.xpath(\n            \"//span[@id='ctl00_PageBody_LabelShortTitle']/text()\")[0]\n        actions = page.xpath(\n            \"//div[@id='ctl00_PageBody_PanelBillInfo']/\"\n            \"/table[@style='font-size:small']/tr\")\n\n        bill_id = page.xpath(\n            \"//span[@id='ctl00_PageBody_LabelBillID']/text()\")[0]\n        bill_type = LABillScraper._bill_types[bill_abbreviation[1:]]\n        bill = Bill(session, chamber, bill_id, title, type=bill_type)\n        bill.add_source(bill_url)\n\n        authors.remove(author)\n        bill.add_sponsor('primary', author)\n        for author in authors:\n            bill.add_sponsor('cosponsor', author)\n\n        for digest in digests:\n            bill.add_document(digest.text,\n                              digest.attrib['href'],\n                              mimetype=\"application/pdf\")\n\n        for version in versions:\n            bill.add_version(version.text,\n                             version.attrib['href'],\n                             mimetype=\"application/pdf\")\n\n        for amendment in amendments:\n            bill.add_version(amendment.text,\n                             amendment.attrib['href'],\n                             mimetype=\"application/pdf\")\n\n        flags = {\n            \"prefiled\": [\"bill:filed\"],\n            \"referred to the committee\": [\"committee:referred\"],\n            \"sent to the house\": ['bill:passed'],\n            \"ordered returned to the house\": ['bill:passed'],\n            \"ordered to the senate\": ['bill:passed'],\n            \"signed by the governor\": ['governor:signed'],\n            \"sent to the governor\": ['governor:received'],\n        }\n\n        try:\n            votes_link = page.xpath(\"//a[text() = 'Votes']\")[0]\n            self.scrape_votes(bill, votes_link.attrib['href'])\n        except IndexError:\n            # Some bills don't have any votes\n            pass\n\n        for action in actions:\n            date, chamber, page, text = [x.text for x in action.xpath(\".//td\")]\n            session_year = self.metadata['session_details'][session]\\\n                ['start_date'].year\n            # Session is April -> June. Prefiles look like they're in\n            # January at earliest.\n            date += '/{}'.format(session_year)\n            date = dt.datetime.strptime(date, '%m/%d/%Y')\n            chamber = LABillScraper._chambers[chamber]\n\n            cat = []\n            for flag in flags:\n                if flag in text.lower():\n                    cat += flags[flag]\n\n            if cat == []:\n                cat = [\"other\"]\n            bill.add_action(chamber, text, date, cat)\n\n        self.save_bill(bill)\n", "path": "openstates/la/bills.py"}]}