We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
TX: version-less bills missing bill text(s)
No link to bill text for the version-less bills
examples:
http://openstates.org/browse/tx/82/HJR%20146/
http://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=82R&Bill=HJR146

http://openstates.org/browse/tx/81/HJR%20120/
http://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=81R&Bill=HJR120

http://openstates.org/browse/tx/81/HJR%2093/
http://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=81R&Bill=HJR93

http://openstates.org/browse/tx/82/HCR%2094/
http://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=82R&Bill=HCR94


--- 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/tx/bills.py`
Content:
```
1 import re
2 import urllib2
3 import urlparse
4 import datetime
5 
6 from billy.utils import urlescape
7 from billy.scrape import ScrapeError
8 from billy.scrape.bills import BillScraper, Bill
9 
10 from .utils import chamber_name, parse_ftp_listing
11 
12 import lxml.etree
13 
14 
15 class TXBillScraper(BillScraper):
16     state = 'tx'
17     _ftp_root = 'ftp://ftp.legis.state.tx.us/'
18 
19     def scrape(self, chamber, session):
20         self.validate_session(session)
21 
22         if len(session) == 2:
23             session = "%sR" % session
24 
25         for btype in ['bills', 'concurrent_resolutions',
26                       'joint_resolutions', 'resolutions']:
27             billdirs_path = '/bills/%s/billhistory/%s_%s/' % (
28                 session, chamber_name(chamber), btype)
29             billdirs_url = urlparse.urljoin(self._ftp_root, billdirs_path)
30 
31             with self.urlopen(billdirs_url) as bill_dirs:
32                 for dir in parse_ftp_listing(bill_dirs):
33                     bill_url = urlparse.urljoin(billdirs_url, dir) + '/'
34                     with self.urlopen(bill_url) as bills:
35                         for history in parse_ftp_listing(bills):
36                             self.scrape_bill(chamber, session,
37                                              urlparse.urljoin(bill_url,
38                                                               history))
39 
40     def scrape_bill(self, chamber, session, url):
41         with self.urlopen(url) as data:
42             if "Bill does not exist." in data:
43                 return
44 
45             bill = self.parse_bill_xml(chamber, session, data)
46             bill.add_source(urlescape(url))
47 
48             versions_url = url.replace('billhistory', 'billtext/html')
49             # URLs for versions inexplicably (H|S)(J|C) instead of (H|J)(CR|JR)
50             versions_url = versions_url.replace('JR', 'J').replace('CR', 'C')
51             versions_url = '/'.join(versions_url.split('/')[0:-1])
52 
53             bill_prefix = bill['bill_id'].split()[0]
54             bill_num = int(bill['bill_id'].split()[1])
55             long_bill_id = "%s%05d" % (bill_prefix, bill_num)
56 
57             try:
58                 with self.urlopen(versions_url) as versions_list:
59                     bill.add_source(urlescape(versions_url))
60                     for version in parse_ftp_listing(versions_list):
61                         if version.startswith(long_bill_id):
62                             version_name = version.split('.')[0]
63                             version_url = urlparse.urljoin(
64                                 versions_url + '/',
65                                 version)
66                             bill.add_version(version_name,
67                                              urlescape(version_url))
68             except urllib2.URLError:
69                 # Sometimes the text is missing
70                 pass
71 
72             self.save_bill(bill)
73 
74     def parse_bill_xml(self, chamber, session, txt):
75         root = lxml.etree.fromstring(txt)
76         bill_id = ' '.join(root.attrib['bill'].split(' ')[1:])
77         bill_title = root.findtext("caption")
78 
79         if session[2] == 'R':
80             session = session[0:2]
81 
82         if bill_id[1] == 'B':
83             bill_type = ['bill']
84         elif bill_id[1] == 'R':
85             bill_type = ['resolution']
86         elif bill_id[1:3] == 'CR':
87             bill_type = ['concurrent resolution']
88         elif bill_id[1:3] == 'JR':
89             bill_type = ['joint resolution']
90         else:
91             raise ScrapeError("Invalid bill_id: %s" % bill_id)
92 
93         bill = Bill(session, chamber, bill_id, bill_title, type=bill_type)
94 
95         for action in root.findall('actions/action'):
96             act_date = datetime.datetime.strptime(action.findtext('date'),
97                                             "%m/%d/%Y").date()
98 
99             extra = {}
100             extra['action_number'] = action.find('actionNumber').text
101             comment = action.find('comment')
102             if comment is not None and comment.text:
103                 extra['comment'] = comment.text.strip()
104 
105             actor = {'H': 'lower',
106                      'S': 'upper',
107                      'E': 'executive'}[extra['action_number'][0]]
108 
109             desc = action.findtext('description').strip()
110 
111             if desc == 'Scheduled for public hearing on . . .':
112                 continue
113 
114             introduced = False
115 
116             if desc == 'Amended':
117                 atype = 'amendment:passed'
118             elif desc == 'Amendment(s) offered':
119                 atype = 'amendment:introduced'
120             elif desc == 'Amendment amended':
121                 atype = 'amendment:amended'
122             elif desc == 'Amendment withdrawn':
123                 atype = 'amendment:withdrawn'
124             elif desc == 'Passed' or desc == 'Adopted':
125                 atype = 'bill:passed'
126             elif re.match(r'^Received (by|from) the', desc):
127                 if 'Secretary of the Senate' not in desc:
128                     atype = 'bill:introduced'
129                 else:
130                     atype = 'bill:filed'
131             elif desc.startswith('Sent to the Governor'):
132                 # But what if it gets lost in the mail?
133                 atype = 'governor:received'
134             elif desc.startswith('Signed by the Governor'):
135                 atype = 'governor:signed'
136             elif desc == 'Vetoed by the Governor':
137                 atype = 'governor:vetoed'
138             elif desc == 'Read first time':
139                 atype = ['bill:introduced', 'bill:reading:1']
140                 introduced = True
141             elif desc == 'Read & adopted':
142                 atype = ['bill:passed']
143                 if not introduced:
144                     introduced = True
145                     atype.append('bill:introduced')
146             elif desc == "Passed as amended":
147                 atype = 'bill:passed'
148             elif desc.startswith('Referred to') or desc.startswith("Recommended to be sent to "):
149                 atype = 'committee:referred'
150             elif desc == "Reported favorably w/o amendment(s)":
151                 atype = 'committee:passed'
152             elif desc == "Filed":
153                 atype = 'bill:filed'
154             elif desc == 'Read 3rd time':
155                 atype = 'bill:reading:3'
156             elif desc == 'Read 2nd time':
157                 atype = 'bill:reading:2'
158             elif desc.startswith('Reported favorably'):
159                 atype = 'committee:passed:favorable'
160             else:
161                 atype = 'other'
162 
163             bill.add_action(actor, action.findtext('description'),
164                             act_date, type=atype, **extra)
165 
166         for author in root.findtext('authors').split(' | '):
167             if author != "":
168                 bill.add_sponsor('author', author)
169         for coauthor in root.findtext('coauthors').split(' | '):
170             if coauthor != "":
171                 bill.add_sponsor('coauthor', coauthor)
172         for sponsor in root.findtext('sponsors').split(' | '):
173             if sponsor != "":
174                 bill.add_sponsor('sponsor', sponsor)
175         for cosponsor in root.findtext('cosponsors').split(' | '):
176             if cosponsor != "":
177                 bill.add_sponsor('cosponsor', cosponsor)
178 
179         bill['subjects'] = []
180         for subject in root.iterfind('subjects/subject'):
181             bill['subjects'].append(subject.text.strip())
182 
183         return bill
184
```
--- 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/tx/bills.py b/openstates/tx/bills.py
--- a/openstates/tx/bills.py
+++ b/openstates/tx/bills.py
@@ -51,6 +51,7 @@
             versions_url = '/'.join(versions_url.split('/')[0:-1])
 
             bill_prefix = bill['bill_id'].split()[0]
+            bill_prefix = bill_prefix.replace('JR', 'J').replace('CR', 'C')
             bill_num = int(bill['bill_id'].split()[1])
             long_bill_id = "%s%05d" % (bill_prefix, bill_num)

{"golden_diff": "diff --git a/openstates/tx/bills.py b/openstates/tx/bills.py\n--- a/openstates/tx/bills.py\n+++ b/openstates/tx/bills.py\n@@ -51,6 +51,7 @@\n             versions_url = '/'.join(versions_url.split('/')[0:-1])\n \n             bill_prefix = bill['bill_id'].split()[0]\n+            bill_prefix = bill_prefix.replace('JR', 'J').replace('CR', 'C')\n             bill_num = int(bill['bill_id'].split()[1])\n             long_bill_id = \"%s%05d\" % (bill_prefix, bill_num)\n", "issue": "TX: version-less bills missing bill text(s)\nNo link to bill text for the version-less bills\nexamples:\nhttp://openstates.org/browse/tx/82/HJR%20146/\nhttp://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=82R&Bill=HJR146\n\nhttp://openstates.org/browse/tx/81/HJR%20120/\nhttp://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=81R&Bill=HJR120\n\nhttp://openstates.org/browse/tx/81/HJR%2093/\nhttp://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=81R&Bill=HJR93\n\nhttp://openstates.org/browse/tx/82/HCR%2094/\nhttp://www.legis.state.tx.us/BillLookup/Text.aspx?LegSess=82R&Bill=HCR94\n\n", "before_files": [{"content": "import re\nimport urllib2\nimport urlparse\nimport datetime\n\nfrom billy.utils import urlescape\nfrom billy.scrape import ScrapeError\nfrom billy.scrape.bills import BillScraper, Bill\n\nfrom .utils import chamber_name, parse_ftp_listing\n\nimport lxml.etree\n\n\nclass TXBillScraper(BillScraper):\n    state = 'tx'\n    _ftp_root = 'ftp://ftp.legis.state.tx.us/'\n\n    def scrape(self, chamber, session):\n        self.validate_session(session)\n\n        if len(session) == 2:\n            session = \"%sR\" % session\n\n        for btype in ['bills', 'concurrent_resolutions',\n                      'joint_resolutions', 'resolutions']:\n            billdirs_path = '/bills/%s/billhistory/%s_%s/' % (\n                session, chamber_name(chamber), btype)\n            billdirs_url = urlparse.urljoin(self._ftp_root, billdirs_path)\n\n            with self.urlopen(billdirs_url) as bill_dirs:\n                for dir in parse_ftp_listing(bill_dirs):\n                    bill_url = urlparse.urljoin(billdirs_url, dir) + '/'\n                    with self.urlopen(bill_url) as bills:\n                        for history in parse_ftp_listing(bills):\n                            self.scrape_bill(chamber, session,\n                                             urlparse.urljoin(bill_url,\n                                                              history))\n\n    def scrape_bill(self, chamber, session, url):\n        with self.urlopen(url) as data:\n            if \"Bill does not exist.\" in data:\n                return\n\n            bill = self.parse_bill_xml(chamber, session, data)\n            bill.add_source(urlescape(url))\n\n            versions_url = url.replace('billhistory', 'billtext/html')\n            # URLs for versions inexplicably (H|S)(J|C) instead of (H|J)(CR|JR)\n            versions_url = versions_url.replace('JR', 'J').replace('CR', 'C')\n            versions_url = '/'.join(versions_url.split('/')[0:-1])\n\n            bill_prefix = bill['bill_id'].split()[0]\n            bill_num = int(bill['bill_id'].split()[1])\n            long_bill_id = \"%s%05d\" % (bill_prefix, bill_num)\n\n            try:\n                with self.urlopen(versions_url) as versions_list:\n                    bill.add_source(urlescape(versions_url))\n                    for version in parse_ftp_listing(versions_list):\n                        if version.startswith(long_bill_id):\n                            version_name = version.split('.')[0]\n                            version_url = urlparse.urljoin(\n                                versions_url + '/',\n                                version)\n                            bill.add_version(version_name,\n                                             urlescape(version_url))\n            except urllib2.URLError:\n                # Sometimes the text is missing\n                pass\n\n            self.save_bill(bill)\n\n    def parse_bill_xml(self, chamber, session, txt):\n        root = lxml.etree.fromstring(txt)\n        bill_id = ' '.join(root.attrib['bill'].split(' ')[1:])\n        bill_title = root.findtext(\"caption\")\n\n        if session[2] == 'R':\n            session = session[0:2]\n\n        if bill_id[1] == 'B':\n            bill_type = ['bill']\n        elif bill_id[1] == 'R':\n            bill_type = ['resolution']\n        elif bill_id[1:3] == 'CR':\n            bill_type = ['concurrent resolution']\n        elif bill_id[1:3] == 'JR':\n            bill_type = ['joint resolution']\n        else:\n            raise ScrapeError(\"Invalid bill_id: %s\" % bill_id)\n\n        bill = Bill(session, chamber, bill_id, bill_title, type=bill_type)\n\n        for action in root.findall('actions/action'):\n            act_date = datetime.datetime.strptime(action.findtext('date'),\n                                            \"%m/%d/%Y\").date()\n\n            extra = {}\n            extra['action_number'] = action.find('actionNumber').text\n            comment = action.find('comment')\n            if comment is not None and comment.text:\n                extra['comment'] = comment.text.strip()\n\n            actor = {'H': 'lower',\n                     'S': 'upper',\n                     'E': 'executive'}[extra['action_number'][0]]\n\n            desc = action.findtext('description').strip()\n\n            if desc == 'Scheduled for public hearing on . . .':\n                continue\n\n            introduced = False\n\n            if desc == 'Amended':\n                atype = 'amendment:passed'\n            elif desc == 'Amendment(s) offered':\n                atype = 'amendment:introduced'\n            elif desc == 'Amendment amended':\n                atype = 'amendment:amended'\n            elif desc == 'Amendment withdrawn':\n                atype = 'amendment:withdrawn'\n            elif desc == 'Passed' or desc == 'Adopted':\n                atype = 'bill:passed'\n            elif re.match(r'^Received (by|from) the', desc):\n                if 'Secretary of the Senate' not in desc:\n                    atype = 'bill:introduced'\n                else:\n                    atype = 'bill:filed'\n            elif desc.startswith('Sent to the Governor'):\n                # But what if it gets lost in the mail?\n                atype = 'governor:received'\n            elif desc.startswith('Signed by the Governor'):\n                atype = 'governor:signed'\n            elif desc == 'Vetoed by the Governor':\n                atype = 'governor:vetoed'\n            elif desc == 'Read first time':\n                atype = ['bill:introduced', 'bill:reading:1']\n                introduced = True\n            elif desc == 'Read & adopted':\n                atype = ['bill:passed']\n                if not introduced:\n                    introduced = True\n                    atype.append('bill:introduced')\n            elif desc == \"Passed as amended\":\n                atype = 'bill:passed'\n            elif desc.startswith('Referred to') or desc.startswith(\"Recommended to be sent to \"):\n                atype = 'committee:referred'\n            elif desc == \"Reported favorably w/o amendment(s)\":\n                atype = 'committee:passed'\n            elif desc == \"Filed\":\n                atype = 'bill:filed'\n            elif desc == 'Read 3rd time':\n                atype = 'bill:reading:3'\n            elif desc == 'Read 2nd time':\n                atype = 'bill:reading:2'\n            elif desc.startswith('Reported favorably'):\n                atype = 'committee:passed:favorable'\n            else:\n                atype = 'other'\n\n            bill.add_action(actor, action.findtext('description'),\n                            act_date, type=atype, **extra)\n\n        for author in root.findtext('authors').split(' | '):\n            if author != \"\":\n                bill.add_sponsor('author', author)\n        for coauthor in root.findtext('coauthors').split(' | '):\n            if coauthor != \"\":\n                bill.add_sponsor('coauthor', coauthor)\n        for sponsor in root.findtext('sponsors').split(' | '):\n            if sponsor != \"\":\n                bill.add_sponsor('sponsor', sponsor)\n        for cosponsor in root.findtext('cosponsors').split(' | '):\n            if cosponsor != \"\":\n                bill.add_sponsor('cosponsor', cosponsor)\n\n        bill['subjects'] = []\n        for subject in root.iterfind('subjects/subject'):\n            bill['subjects'].append(subject.text.strip())\n\n        return bill\n", "path": "openstates/tx/bills.py"}], "after_files": [{"content": "import re\nimport urllib2\nimport urlparse\nimport datetime\n\nfrom billy.utils import urlescape\nfrom billy.scrape import ScrapeError\nfrom billy.scrape.bills import BillScraper, Bill\n\nfrom .utils import chamber_name, parse_ftp_listing\n\nimport lxml.etree\n\n\nclass TXBillScraper(BillScraper):\n    state = 'tx'\n    _ftp_root = 'ftp://ftp.legis.state.tx.us/'\n\n    def scrape(self, chamber, session):\n        self.validate_session(session)\n\n        if len(session) == 2:\n            session = \"%sR\" % session\n\n        for btype in ['bills', 'concurrent_resolutions',\n                      'joint_resolutions', 'resolutions']:\n            billdirs_path = '/bills/%s/billhistory/%s_%s/' % (\n                session, chamber_name(chamber), btype)\n            billdirs_url = urlparse.urljoin(self._ftp_root, billdirs_path)\n\n            with self.urlopen(billdirs_url) as bill_dirs:\n                for dir in parse_ftp_listing(bill_dirs):\n                    bill_url = urlparse.urljoin(billdirs_url, dir) + '/'\n                    with self.urlopen(bill_url) as bills:\n                        for history in parse_ftp_listing(bills):\n                            self.scrape_bill(chamber, session,\n                                             urlparse.urljoin(bill_url,\n                                                              history))\n\n    def scrape_bill(self, chamber, session, url):\n        with self.urlopen(url) as data:\n            if \"Bill does not exist.\" in data:\n                return\n\n            bill = self.parse_bill_xml(chamber, session, data)\n            bill.add_source(urlescape(url))\n\n            versions_url = url.replace('billhistory', 'billtext/html')\n            # URLs for versions inexplicably (H|S)(J|C) instead of (H|J)(CR|JR)\n            versions_url = versions_url.replace('JR', 'J').replace('CR', 'C')\n            versions_url = '/'.join(versions_url.split('/')[0:-1])\n\n            bill_prefix = bill['bill_id'].split()[0]\n            bill_prefix = bill_prefix.replace('JR', 'J').replace('CR', 'C')\n            bill_num = int(bill['bill_id'].split()[1])\n            long_bill_id = \"%s%05d\" % (bill_prefix, bill_num)\n\n            try:\n                with self.urlopen(versions_url) as versions_list:\n                    bill.add_source(urlescape(versions_url))\n                    for version in parse_ftp_listing(versions_list):\n                        if version.startswith(long_bill_id):\n                            version_name = version.split('.')[0]\n                            version_url = urlparse.urljoin(\n                                versions_url + '/',\n                                version)\n                            bill.add_version(version_name,\n                                             urlescape(version_url))\n            except urllib2.URLError:\n                # Sometimes the text is missing\n                pass\n\n            self.save_bill(bill)\n\n    def parse_bill_xml(self, chamber, session, txt):\n        root = lxml.etree.fromstring(txt)\n        bill_id = ' '.join(root.attrib['bill'].split(' ')[1:])\n        bill_title = root.findtext(\"caption\")\n\n        if session[2] == 'R':\n            session = session[0:2]\n\n        if bill_id[1] == 'B':\n            bill_type = ['bill']\n        elif bill_id[1] == 'R':\n            bill_type = ['resolution']\n        elif bill_id[1:3] == 'CR':\n            bill_type = ['concurrent resolution']\n        elif bill_id[1:3] == 'JR':\n            bill_type = ['joint resolution']\n        else:\n            raise ScrapeError(\"Invalid bill_id: %s\" % bill_id)\n\n        bill = Bill(session, chamber, bill_id, bill_title, type=bill_type)\n\n        for action in root.findall('actions/action'):\n            act_date = datetime.datetime.strptime(action.findtext('date'),\n                                            \"%m/%d/%Y\").date()\n\n            extra = {}\n            extra['action_number'] = action.find('actionNumber').text\n            comment = action.find('comment')\n            if comment is not None and comment.text:\n                extra['comment'] = comment.text.strip()\n\n            actor = {'H': 'lower',\n                     'S': 'upper',\n                     'E': 'executive'}[extra['action_number'][0]]\n\n            desc = action.findtext('description').strip()\n\n            if desc == 'Scheduled for public hearing on . . .':\n                continue\n\n            introduced = False\n\n            if desc == 'Amended':\n                atype = 'amendment:passed'\n            elif desc == 'Amendment(s) offered':\n                atype = 'amendment:introduced'\n            elif desc == 'Amendment amended':\n                atype = 'amendment:amended'\n            elif desc == 'Amendment withdrawn':\n                atype = 'amendment:withdrawn'\n            elif desc == 'Passed' or desc == 'Adopted':\n                atype = 'bill:passed'\n            elif re.match(r'^Received (by|from) the', desc):\n                if 'Secretary of the Senate' not in desc:\n                    atype = 'bill:introduced'\n                else:\n                    atype = 'bill:filed'\n            elif desc.startswith('Sent to the Governor'):\n                # But what if it gets lost in the mail?\n                atype = 'governor:received'\n            elif desc.startswith('Signed by the Governor'):\n                atype = 'governor:signed'\n            elif desc == 'Vetoed by the Governor':\n                atype = 'governor:vetoed'\n            elif desc == 'Read first time':\n                atype = ['bill:introduced', 'bill:reading:1']\n                introduced = True\n            elif desc == 'Read & adopted':\n                atype = ['bill:passed']\n                if not introduced:\n                    introduced = True\n                    atype.append('bill:introduced')\n            elif desc == \"Passed as amended\":\n                atype = 'bill:passed'\n            elif desc.startswith('Referred to') or desc.startswith(\"Recommended to be sent to \"):\n                atype = 'committee:referred'\n            elif desc == \"Reported favorably w/o amendment(s)\":\n                atype = 'committee:passed'\n            elif desc == \"Filed\":\n                atype = 'bill:filed'\n            elif desc == 'Read 3rd time':\n                atype = 'bill:reading:3'\n            elif desc == 'Read 2nd time':\n                atype = 'bill:reading:2'\n            elif desc.startswith('Reported favorably'):\n                atype = 'committee:passed:favorable'\n            else:\n                atype = 'other'\n\n            bill.add_action(actor, action.findtext('description'),\n                            act_date, type=atype, **extra)\n\n        for author in root.findtext('authors').split(' | '):\n            if author != \"\":\n                bill.add_sponsor('author', author)\n        for coauthor in root.findtext('coauthors').split(' | '):\n            if coauthor != \"\":\n                bill.add_sponsor('coauthor', coauthor)\n        for sponsor in root.findtext('sponsors').split(' | '):\n            if sponsor != \"\":\n                bill.add_sponsor('sponsor', sponsor)\n        for cosponsor in root.findtext('cosponsors').split(' | '):\n            if cosponsor != \"\":\n                bill.add_sponsor('cosponsor', cosponsor)\n\n        bill['subjects'] = []\n        for subject in root.iterfind('subjects/subject'):\n            bill['subjects'].append(subject.text.strip())\n\n        return bill\n", "path": "openstates/tx/bills.py"}]}