va-lis-client 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- va_lis_client-0.1.0/LICENSE +21 -0
- va_lis_client-0.1.0/PKG-INFO +538 -0
- va_lis_client-0.1.0/README.md +507 -0
- va_lis_client-0.1.0/pyproject.toml +54 -0
- va_lis_client-0.1.0/setup.cfg +4 -0
- va_lis_client-0.1.0/src/va_lis_client/__init__.py +4 -0
- va_lis_client-0.1.0/src/va_lis_client/client.py +598 -0
- va_lis_client-0.1.0/src/va_lis_client/exceptions.py +2 -0
- va_lis_client-0.1.0/src/va_lis_client/http.py +34 -0
- va_lis_client-0.1.0/src/va_lis_client/models/__init__.py +121 -0
- va_lis_client-0.1.0/src/va_lis_client/models/calendar.py +256 -0
- va_lis_client-0.1.0/src/va_lis_client/models/committee.py +111 -0
- va_lis_client-0.1.0/src/va_lis_client/models/common.py +53 -0
- va_lis_client-0.1.0/src/va_lis_client/models/docket.py +167 -0
- va_lis_client-0.1.0/src/va_lis_client/models/event.py +171 -0
- va_lis_client-0.1.0/src/va_lis_client/models/legislation.py +201 -0
- va_lis_client-0.1.0/src/va_lis_client/models/schedule.py +88 -0
- va_lis_client-0.1.0/src/va_lis_client/models/session.py +51 -0
- va_lis_client-0.1.0/src/va_lis_client/models/text.py +168 -0
- va_lis_client-0.1.0/src/va_lis_client/rate_limiter.py +67 -0
- va_lis_client-0.1.0/src/va_lis_client.egg-info/PKG-INFO +538 -0
- va_lis_client-0.1.0/src/va_lis_client.egg-info/SOURCES.txt +24 -0
- va_lis_client-0.1.0/src/va_lis_client.egg-info/dependency_links.txt +1 -0
- va_lis_client-0.1.0/src/va_lis_client.egg-info/requires.txt +10 -0
- va_lis_client-0.1.0/src/va_lis_client.egg-info/top_level.txt +1 -0
- va_lis_client-0.1.0/tests/test_client.py +166 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonny Fuller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: va-lis-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Virginia Legislative Information System (LIS) API
|
|
5
|
+
Author-email: Jonny Fuller <mr.waffles@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JonnyWaffles/va-lis-client
|
|
8
|
+
Project-URL: Repository, https://github.com/JonnyWaffles/va-lis-client
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/JonnyWaffles/va-lis-client/issues
|
|
10
|
+
Keywords: virginia,legislature,LIS,legislation,API,client
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: requests
|
|
24
|
+
Requires-Dist: urllib3
|
|
25
|
+
Provides-Extra: rate-limiting
|
|
26
|
+
Requires-Dist: redis>=4.0; extra == "rate-limiting"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# va-lis-client
|
|
33
|
+
|
|
34
|
+
Python client for the **Virginia Legislative Information System (LIS) REST API**.
|
|
35
|
+
|
|
36
|
+
Built from live API responses — the official OpenAPI specs are unreliable
|
|
37
|
+
(response envelope keys, field casing, and parameter behavior all differ from
|
|
38
|
+
the docs).
|
|
39
|
+
|
|
40
|
+
- Developer portal: https://lis.virginia.gov/developers
|
|
41
|
+
- API key registration: https://lis.virginia.gov/apiregistration
|
|
42
|
+
- Help desk: lis@dlas.virginia.gov
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install va-lis-client
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With Redis rate limiting support:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install "va-lis-client[rate-limiting]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import os
|
|
60
|
+
from va_lis_client import LISClient
|
|
61
|
+
|
|
62
|
+
# Pass the key directly, or set the LIS_API_KEY environment variable
|
|
63
|
+
client = LISClient(api_key="your-api-key-here")
|
|
64
|
+
# or: os.environ["LIS_API_KEY"] = "your-key"; client = LISClient()
|
|
65
|
+
|
|
66
|
+
# The GA's current working session. During the interim this is the
|
|
67
|
+
# *upcoming* session, not the one that just ended — see
|
|
68
|
+
# "Bill identity & carry-over" below.
|
|
69
|
+
session = client.get_default_session()
|
|
70
|
+
|
|
71
|
+
# All bills in that session
|
|
72
|
+
bills = client.get_session_bills(session_code=int(session.SessionCode))
|
|
73
|
+
|
|
74
|
+
# Full detail for a bill
|
|
75
|
+
bill = client.get_bill(legislation_id=98525)
|
|
76
|
+
|
|
77
|
+
# Bill text (HTML) and summaries
|
|
78
|
+
texts = client.get_bill_text_detail(legislation_id=98525, session_code=20261)
|
|
79
|
+
summaries = client.get_bill_summaries(legislation_number="HB1", session_code=20261)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Authentication
|
|
83
|
+
|
|
84
|
+
Every request sends a `WebAPIKey` header with a partner GUID obtained at
|
|
85
|
+
https://lis.virginia.gov/apiregistration.
|
|
86
|
+
|
|
87
|
+
Set the key via environment variable:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
export LIS_API_KEY=your-guid-here
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or pass it directly to the constructor:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
client = LISClient(api_key="your-guid-here")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Important:** Heartbeat endpoints accept *any* key — even garbage. Use
|
|
100
|
+
`client.check_api_key()` to actually validate a key via the
|
|
101
|
+
PartnerAuthentication service.
|
|
102
|
+
|
|
103
|
+
## Data availability
|
|
104
|
+
|
|
105
|
+
- The session reference list reaches back to **1994**.
|
|
106
|
+
- Bill data has been observed working for the **2024–2027 sessions** (as of
|
|
107
|
+
Aug 2026). Earlier guidance said only 2025–2026 was authorized via the API.
|
|
108
|
+
- Older session data is available as `legacylis.virginia.gov` CSV downloads.
|
|
109
|
+
- No rate limits are documented anywhere.
|
|
110
|
+
|
|
111
|
+
## Identifier cheat-sheet
|
|
112
|
+
|
|
113
|
+
Bills are uniquely identified by **session + bill number**, but the API uses
|
|
114
|
+
several overlapping identifiers:
|
|
115
|
+
|
|
116
|
+
| Identifier | Example | Notes |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `SessionCode` | `20261` | Year + sequence. `20261` = 2026 Regular, `20262` = Special I |
|
|
119
|
+
| `SessionID` | `59` | Surrogate PK. Either code or ID works as query param |
|
|
120
|
+
| `LegislationNumber` | `"HB1"` | **Must be unpadded** — `HB0001` returns 204 |
|
|
121
|
+
| `LegislationID` | `98525` | Surrogate PK for the *logical* bill — reused across carry-over (see below) |
|
|
122
|
+
| `DocumentCode` | `"HB1ER"` | Bill number + version suffix |
|
|
123
|
+
| `LegislationTextID` | `257719` | PK for a specific text version |
|
|
124
|
+
| `CommitteeID` | `14` | Surrogate PK for a committee |
|
|
125
|
+
| `CommitteeNumber` | `"H14"` | Chamber prefix + number, e.g. `H14`, `S02` |
|
|
126
|
+
| `CalendarID` | `21146` | PK for a House floor calendar |
|
|
127
|
+
| `DocketID` | `21114` | PK for a Senate committee docket |
|
|
128
|
+
| `ScheduleID` | `3626` | PK for a scheduled meeting |
|
|
129
|
+
| `LegislationEventID` | `1561089` | PK for a bill history event |
|
|
130
|
+
|
|
131
|
+
## Bill identity & carry-over
|
|
132
|
+
|
|
133
|
+
Virginia's even-year sessions may carry unfinished bills over into the
|
|
134
|
+
following odd-year session — never the reverse, never twice, and never across
|
|
135
|
+
a two-year General Assembly term. The API models this by **reusing the
|
|
136
|
+
`LegislationID`** (confirmed against the live API, 2026-08-12):
|
|
137
|
+
|
|
138
|
+
- A carried-over bill appears in **both** sessions' `get_session_bills` lists
|
|
139
|
+
with the same `LegislationID` (e.g. HB9, ID `98631`, in `20261` and `20271`).
|
|
140
|
+
- `get_bill(legislation_id)` returns top-level `SessionCode`/`SessionID` as
|
|
141
|
+
`None`. The `Sessions` list carries one cross-ref per session the bill
|
|
142
|
+
appears in — both the even and odd session for a carried-over bill — and is
|
|
143
|
+
the explicit lineage record.
|
|
144
|
+
- So `LegislationID` identifies the *logical bill within a GA term*: at most
|
|
145
|
+
two session appearances (even, then odd if continued). Session-scoped
|
|
146
|
+
identity is `(SessionCode, LegislationNumber)` or
|
|
147
|
+
`(SessionCode, LegislationID)`.
|
|
148
|
+
- An odd-year bill list starts as pure carry-over — as of Aug 2026, `20271`
|
|
149
|
+
returned 443 bills, every one sharing its ID with `20261`. Continued bills
|
|
150
|
+
must be acted on by mid-November of the even year or they die, so expect
|
|
151
|
+
that set to shrink before the odd session convenes.
|
|
152
|
+
|
|
153
|
+
**Sync warning:** if you mirror bills into a database, don't put a global
|
|
154
|
+
unique constraint on `LegislationID` — the first odd-year sync will violate it
|
|
155
|
+
on the first carried-over bill. Scope uniqueness to
|
|
156
|
+
`(session, LegislationID)`.
|
|
157
|
+
|
|
158
|
+
**Default session:** `get_default_session()` tracks the GA's *working*
|
|
159
|
+
session, not the last one convened. Once a session wraps up (sine die, veto
|
|
160
|
+
session, enactments effective July 1), the default advances to the upcoming
|
|
161
|
+
session during the interim — by Aug 2026 it was already `20271`, which is
|
|
162
|
+
where continued bills and (from mid-November) new prefiles accumulate. For
|
|
163
|
+
retrospective work on a just-ended session, pass its explicit session code.
|
|
164
|
+
|
|
165
|
+
## Response envelope gotchas
|
|
166
|
+
|
|
167
|
+
The docs say `ListItems` everywhere. Reality:
|
|
168
|
+
|
|
169
|
+
| Endpoint | Docs say | Actual key |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| Sessions | `ListItems` | `Sessions` |
|
|
172
|
+
| Legislation list | `ListItems` | `Legislations` |
|
|
173
|
+
| Legislation detail | `ListItems` | `Legislations` |
|
|
174
|
+
| Legislation statuses | `ListItems` | `References` |
|
|
175
|
+
| Text list | `ListItems` | `LegislationTextList` |
|
|
176
|
+
| Text detail | `ListItems` | `TextsList` |
|
|
177
|
+
| Summaries | `ListItems` | `LegislationSummaries` |
|
|
178
|
+
| Version refs | `ListItems` | `LegislationVersionList` |
|
|
179
|
+
| Partner check | flat | `PartnerList` |
|
|
180
|
+
| Calendars | `ListItems` | `Calendars` |
|
|
181
|
+
| Dockets | `ListItems` | `Dockets` |
|
|
182
|
+
| Schedules | `ListItems` | `Schedules` |
|
|
183
|
+
| Committees | `ListItems` | `Committees` |
|
|
184
|
+
| Legislation events | `ListItems` | `LegislationEvents` |
|
|
185
|
+
| Schedule types | `ListItems` | `ScheduleTypes` |
|
|
186
|
+
| Meeting rooms | `ListItems` | `MeetingRooms` |
|
|
187
|
+
|
|
188
|
+
## API services
|
|
189
|
+
|
|
190
|
+
### Currently implemented
|
|
191
|
+
|
|
192
|
+
#### Session (`/Session/api/`)
|
|
193
|
+
- `get_sessions(year)` → list of sessions for a year
|
|
194
|
+
- `get_default_session()` → the current/active session
|
|
195
|
+
|
|
196
|
+
#### Legislation (`/Legislation/api/`)
|
|
197
|
+
- `get_session_bills(session_code)` → lightweight bill list for a session
|
|
198
|
+
- `get_bill(legislation_id)` → full bill detail with all patrons
|
|
199
|
+
- `get_legislation_statuses()` → all 52 status reference entries
|
|
200
|
+
|
|
201
|
+
#### LegislationText (`/LegislationText/api/`)
|
|
202
|
+
- `get_legislation_versions()` → 13 version type references (Introduced, Enrolled, etc.)
|
|
203
|
+
- `get_bill_texts(legislation_number, session_code)` → text version list
|
|
204
|
+
- `get_bill_text_detail(legislation_id, session_code)` → full HTML bill text
|
|
205
|
+
|
|
206
|
+
#### LegislationSummary (`/LegislationSummary/api/`)
|
|
207
|
+
- `get_bill_summaries(legislation_number, session_code)` → bill summaries (HTML)
|
|
208
|
+
|
|
209
|
+
#### LegislationEvent (`/LegislationEvent/api/`)
|
|
210
|
+
- `get_bill_events(legislation_id)` → chronological action history for a bill
|
|
211
|
+
- `get_event_types()` → ~3,900 event type references
|
|
212
|
+
- `get_actor_types()` → 5 actor types (House, Senate, Committee, etc.)
|
|
213
|
+
|
|
214
|
+
### Modeled but not yet wired to client methods
|
|
215
|
+
|
|
216
|
+
#### Schedule (`/Schedule/api/`)
|
|
217
|
+
The master meeting calendar. All committee hearings, caucuses, floor sessions.
|
|
218
|
+
|
|
219
|
+
| Endpoint | Description |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `getschedulelistasync` | List meetings by date range, committee, type, room |
|
|
222
|
+
| `previewvcalfileasync` | Generate vCal file for calendar integration |
|
|
223
|
+
| `getmeetingroomsreferenceasync` | Room reference (by chamber) |
|
|
224
|
+
| `getscheduletypesreferenceasync` | Schedule type reference |
|
|
225
|
+
|
|
226
|
+
Key fields: `ScheduleDate`, `ScheduleTime` (often free-text like "15 minutes
|
|
227
|
+
after the Senate adjourns"), `RoomDescription`, `OwnerName` (committee),
|
|
228
|
+
`IsCancelled`, `ScheduleType` (Committee/Chamber/Conference/Caucus/Docket/Other).
|
|
229
|
+
|
|
230
|
+
**Data quality warning:** `ScheduleTime` is frequently free-text, not a
|
|
231
|
+
parseable time. The API faithfully reflects whatever the LIS clerks entered.
|
|
232
|
+
|
|
233
|
+
#### Calendar (`/Calendar/api/`)
|
|
234
|
+
Floor calendars (both chambers) and committee dockets (Senate only).
|
|
235
|
+
|
|
236
|
+
| Endpoint | Description |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `getcalendarlistasync` | List calendars by session + chamber |
|
|
239
|
+
| `getcalendarsbyidasync` | Full calendar with categories, agendas, votes |
|
|
240
|
+
| `getdocketlistasync` | List dockets by committee (**Senate only**) |
|
|
241
|
+
| `getdocketlistbycommitteenumberasync` | Dockets by committee number (**Senate only**) |
|
|
242
|
+
| `getdocketsbyidasync` | Full docket with agenda items, bills, patrons |
|
|
243
|
+
| `getcalendaractionsreferenceasync` | Calendar action reference |
|
|
244
|
+
| `getcalendarcategorytypesreferenceasync` | Category type reference |
|
|
245
|
+
| `getcalendartypesreferenceasync` | Calendar type reference |
|
|
246
|
+
|
|
247
|
+
**Important:** Dockets are **Senate-only**. The House uses Calendars. Requesting
|
|
248
|
+
a docket with `chamberCode=H` returns HTTP 400 with the message
|
|
249
|
+
"Dockets can only be for ChamberCode = S".
|
|
250
|
+
|
|
251
|
+
Docket detail includes:
|
|
252
|
+
- `DocketCategories` → `DocketItems` → bills with summaries and patrons
|
|
253
|
+
- `CommitteeMember` roster with roles (Chair, etc.)
|
|
254
|
+
- `Staff` assignments
|
|
255
|
+
- `Schedules` (linked back to the Schedule service for when/where)
|
|
256
|
+
- PDF/JSON file downloads
|
|
257
|
+
|
|
258
|
+
Calendar detail includes:
|
|
259
|
+
- `CalendarCategories` → `Agendas` → bills with vote tallies
|
|
260
|
+
- `AgendaItems` → `VoteMember` with per-member vote responses (Y/N)
|
|
261
|
+
- Order of business (Call to Order, Invocation, etc.)
|
|
262
|
+
|
|
263
|
+
#### Committee (`/Committee/api/`)
|
|
264
|
+
|
|
265
|
+
| Endpoint | Description |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `getcommitteesasync` | Full committee detail (by number or date) |
|
|
268
|
+
| `getcommitteelistasync` | Shallow list by session + chamber |
|
|
269
|
+
| `getcommitteebyidasync` | Single committee by ID + session |
|
|
270
|
+
|
|
271
|
+
Key fields: `CommitteeID`, `CommitteeNumber` (e.g. `H14`, `S02`), `Name`,
|
|
272
|
+
`ChamberCode`, `Abbreviation`, `MeetingNote`, `ParentCommitteeID` (for
|
|
273
|
+
subcommittees).
|
|
274
|
+
|
|
275
|
+
### Not yet explored
|
|
276
|
+
|
|
277
|
+
These services exist in the portal but haven't been investigated:
|
|
278
|
+
|
|
279
|
+
- AdvancedLegislationSearch
|
|
280
|
+
- CommunicationFileGeneration
|
|
281
|
+
- Contact
|
|
282
|
+
- LegislationByMember
|
|
283
|
+
- LegislationCollections
|
|
284
|
+
- LegislationCommunications
|
|
285
|
+
- LegislationFileGeneration
|
|
286
|
+
- LegislationPatron
|
|
287
|
+
- LegislationSubject
|
|
288
|
+
- Member
|
|
289
|
+
- MemberVoteSearch
|
|
290
|
+
- MembersByCommittee
|
|
291
|
+
- MinutesBook
|
|
292
|
+
- Organization
|
|
293
|
+
- Person
|
|
294
|
+
- Personnel
|
|
295
|
+
|
|
296
|
+
## How meetings/dockets work
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
Committee ──→ Docket (Senate) or Calendar (House) ──→ Schedule ──→ Room
|
|
300
|
+
│ │
|
|
301
|
+
│ has DocketItems/Agendas │ has ScheduleDate,
|
|
302
|
+
│ (which bills are up) │ ScheduleTime (free-text!),
|
|
303
|
+
│ │ RoomDescription,
|
|
304
|
+
└────────────────────────────────────────│ IsCancelled
|
|
305
|
+
└──→ VoteRoom reference
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- **Schedule** is the "when and where" — but `ScheduleTime` is often free-text
|
|
309
|
+
like "15 minutes after adjournment"
|
|
310
|
+
- **Docket/Calendar** is the "what" — which bills are on the agenda
|
|
311
|
+
- **Committee** is the "who" — membership, chair, staff
|
|
312
|
+
|
|
313
|
+
## How a bill becomes law (Virginia)
|
|
314
|
+
|
|
315
|
+
A bill's lifecycle through the General Assembly, mapped to LIS status IDs.
|
|
316
|
+
Virginia has a bicameral legislature (House of Delegates + Senate). A bill
|
|
317
|
+
must pass both chambers in identical form before going to the Governor.
|
|
318
|
+
|
|
319
|
+
### The happy path
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
ORIGINATING CHAMBER
|
|
323
|
+
───────────────────
|
|
324
|
+
┌─ Prefiled (Nov–Jan) ──→ Introduced (1)
|
|
325
|
+
│ │
|
|
326
|
+
│ ▼
|
|
327
|
+
│ In Committee (2)
|
|
328
|
+
│ ┌────┴────┐
|
|
329
|
+
│ │ │
|
|
330
|
+
│ In Subcommittee Left In Committee (20)
|
|
331
|
+
│ (19) = dead, session ends
|
|
332
|
+
│ │
|
|
333
|
+
│ ▼
|
|
334
|
+
│ Reported Out (3 or 52)
|
|
335
|
+
│ Committee votes to send
|
|
336
|
+
│ bill to the full chamber
|
|
337
|
+
│ │
|
|
338
|
+
│ ▼
|
|
339
|
+
│ Floor vote in originating chamber
|
|
340
|
+
│ Passed House (4) or Passed Senate (5)
|
|
341
|
+
│
|
|
342
|
+
│ CROSSING OVER
|
|
343
|
+
│ ─────────────
|
|
344
|
+
│ Bill goes to the other chamber
|
|
345
|
+
│ and repeats: Committee → Floor
|
|
346
|
+
│ │
|
|
347
|
+
│ ▼
|
|
348
|
+
│ Passed Both Chambers (6, 39, 40)
|
|
349
|
+
│ = "Enrolled" version created (ER)
|
|
350
|
+
│ │
|
|
351
|
+
│ ▼
|
|
352
|
+
│
|
|
353
|
+
│ GOVERNOR
|
|
354
|
+
│ ────────
|
|
355
|
+
│ Communicated (13) → With Governor (7)
|
|
356
|
+
│ │
|
|
357
|
+
│ ▼
|
|
358
|
+
│ Awaiting Governor's Action (38)
|
|
359
|
+
│ ┌────┼────────────┐
|
|
360
|
+
│ │ │ │
|
|
361
|
+
│ ▼ ▼ ▼
|
|
362
|
+
│ Approved Gov's Gov's Veto
|
|
363
|
+
│ (8) Recommend. (24, 26)
|
|
364
|
+
│ │ (23, 27) │
|
|
365
|
+
│ ▼ │ ▼
|
|
366
|
+
│ Enacted ▼ Override vote
|
|
367
|
+
│ (36) Chambers or bill dies
|
|
368
|
+
│ │ adopt? (33)
|
|
369
|
+
│ ▼ │
|
|
370
|
+
│ Acts of ▼
|
|
371
|
+
│ Assembly Back to
|
|
372
|
+
│ Chapter Enrolled
|
|
373
|
+
│ (25)
|
|
374
|
+
└────────────────────────────────────────────────
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Key status groups
|
|
378
|
+
|
|
379
|
+
**Filing & introduction:**
|
|
380
|
+
|
|
381
|
+
| ID | DisplayName | Internal Name | What happened |
|
|
382
|
+
|---|---|---|---|
|
|
383
|
+
| 11 | Preview | Preview | Prefiled but session hasn't started yet |
|
|
384
|
+
| 1 | Introduced | Introduced | Formally introduced on the chamber floor |
|
|
385
|
+
|
|
386
|
+
**Committee phase** — where most bills die:
|
|
387
|
+
|
|
388
|
+
| ID | DisplayName | Internal Name | What happened |
|
|
389
|
+
|---|---|---|---|
|
|
390
|
+
| 2 | In Committee | In Committee | Referred to a standing committee |
|
|
391
|
+
| 37 | Committee Referral Pending | Committee Referral Pending | Awaiting committee assignment |
|
|
392
|
+
| 19 | In Subcommittee | In Subcommittee | Referred to a subcommittee |
|
|
393
|
+
| 20 | Left In Committee | Left In Committee | Bill was never voted on — effectively killed |
|
|
394
|
+
| 41 | In Committee | Continued From | Carried over from a previous session into committee |
|
|
395
|
+
| 3 | In House | Reported Out-House | Committee voted to advance (House side) |
|
|
396
|
+
| 52 | In Senate | Reported Out-Senate | Committee voted to advance (Senate side) |
|
|
397
|
+
| 30 | Incorporated | Incorporated | Merged into another bill |
|
|
398
|
+
|
|
399
|
+
**Floor votes:**
|
|
400
|
+
|
|
401
|
+
| ID | DisplayName | Internal Name | What happened |
|
|
402
|
+
|---|---|---|---|
|
|
403
|
+
| 4 | Passed House | Passed House | Full House approved |
|
|
404
|
+
| 5 | Passed Senate | Passed Senate | Full Senate approved |
|
|
405
|
+
| 12 | Engrossed | Engrossed | Passed one chamber, snapshot before crossover |
|
|
406
|
+
| 28 | Engrossed | Engrossed with Amendment | Same but with amendments applied |
|
|
407
|
+
| 29 | Engrossed | Reengrossed with Amendment | Re-amended after engrossment |
|
|
408
|
+
|
|
409
|
+
**Conference** — when the two chambers passed different versions:
|
|
410
|
+
|
|
411
|
+
| ID | DisplayName | Internal Name | What happened |
|
|
412
|
+
|---|---|---|---|
|
|
413
|
+
| 34 | Conference Requested | Conference Requested | One chamber asks for a conference committee |
|
|
414
|
+
| 16 | In Conference | In Conference | Conference committee is negotiating |
|
|
415
|
+
| 31 | In Conference | Conference Report Agreed | Conferees reached agreement |
|
|
416
|
+
| 32 | In Conference | Conference Report Rejected | Conferees' report was rejected |
|
|
417
|
+
| 39 | Passed | Conference Report Adopted | Both chambers accepted the conference version |
|
|
418
|
+
| 45 | Failed | Failed in Conference | Conferees couldn't agree — bill dies |
|
|
419
|
+
|
|
420
|
+
**Passed both chambers:**
|
|
421
|
+
|
|
422
|
+
| ID | DisplayName | Internal Name | What happened |
|
|
423
|
+
|---|---|---|---|
|
|
424
|
+
| 6 | Passed | Passed Both | Both chambers approved identical text (→ version 3, Enrolled) |
|
|
425
|
+
| 40 | Passed | Passed | Generic "passed" status |
|
|
426
|
+
| 35 | Enrolled | Enrolled-House | Final enrolled text prepared (House origin) |
|
|
427
|
+
| 44 | Enrolled | Enrolled-Senate | Final enrolled text prepared (Senate origin) |
|
|
428
|
+
|
|
429
|
+
**Governor pipeline:**
|
|
430
|
+
|
|
431
|
+
| ID | DisplayName | Internal Name | What happened |
|
|
432
|
+
|---|---|---|---|
|
|
433
|
+
| 13 | Communicated | Communicated | Bill formally transmitted to the Governor |
|
|
434
|
+
| 7 | With Governor | With Governor | Governor has received the bill |
|
|
435
|
+
| 14 | Awaiting Signature | Awaiting Signature | Waiting for physical signature |
|
|
436
|
+
| 38 | Awaiting Governor's Action | Awaiting Governor's Action | Governor is reviewing |
|
|
437
|
+
| 8 | Approved | Approved | **Governor signed it** (→ version 4, Chaptered) |
|
|
438
|
+
| 36 | Enacted | Enacted | Becomes law (→ version 4, Chaptered) |
|
|
439
|
+
| 25 | Acts of Assembly Chapter | Acts of Assembly Chapter | Assigned a chapter number |
|
|
440
|
+
| 23 | Governor's Recommendation | Governor's Recommendation | Sent back with proposed amendments (→ version 10) |
|
|
441
|
+
| 27 | Governor's Recommendation | Governor's Recommendation | Same (no version link) |
|
|
442
|
+
| 33 | Governor's Recommendation Adopted | Gov Recommendation Adopted | Chambers accepted the amendments |
|
|
443
|
+
| 24 | Governor's Veto | Governor's Veto | **Governor vetoed** (→ version 9, Veto Explanation) |
|
|
444
|
+
| 26 | Governor's Veto | Governor's Veto | Same (no version link) |
|
|
445
|
+
|
|
446
|
+
**Terminal / carry-over:**
|
|
447
|
+
|
|
448
|
+
| ID | DisplayName | Internal Name | What happened |
|
|
449
|
+
|---|---|---|---|
|
|
450
|
+
| 9 | Failed | Failed | Bill defeated on a floor vote |
|
|
451
|
+
| 10 | Continued | Continued To | Carried over to next session |
|
|
452
|
+
| 46 | Continued | Continued to House | Carried over, sitting in House |
|
|
453
|
+
| 47 | Continued | Continued to Senate | Carried over, sitting in Senate |
|
|
454
|
+
| 48 | Continued | Continued to Conference | Carried over, in conference |
|
|
455
|
+
|
|
456
|
+
### Why there are 52 statuses for ~15 states
|
|
457
|
+
|
|
458
|
+
LIS uses separate status IDs to track *how* a bill entered a logical state.
|
|
459
|
+
For example, "In Conference" has four IDs distinguishing whether the conference
|
|
460
|
+
just started, the report was agreed, rejected, or continued. The `Name`
|
|
461
|
+
(internal) captures the sub-reason; the `DisplayName` (user-facing) collapses
|
|
462
|
+
them.
|
|
463
|
+
|
|
464
|
+
### Version-linked statuses
|
|
465
|
+
|
|
466
|
+
Some statuses have a `LegislationVersionID` indicating which bill text version
|
|
467
|
+
corresponds to that state:
|
|
468
|
+
|
|
469
|
+
| Status | Version ID | Version Name |
|
|
470
|
+
|---|---|---|
|
|
471
|
+
| Passed Both (6) | 3 | Enrolled |
|
|
472
|
+
| Approved (8) | 4 | Chaptered |
|
|
473
|
+
| Enacted (36) | 4 | Chaptered |
|
|
474
|
+
| Engrossed with Amendment (28) | 2 | Engrossed |
|
|
475
|
+
| Reengrossed with Amendment (29) | 6 | Reengrossed |
|
|
476
|
+
| Governor's Recommendation (23) | 10 | Gov Recommendation |
|
|
477
|
+
| Governor's Veto (24) | 9 | Veto Explanation |
|
|
478
|
+
| Reenrolled-House (42) | 7 | Reenrolled |
|
|
479
|
+
| Reenrolled-Senate (43) | 7 | Reenrolled |
|
|
480
|
+
|
|
481
|
+
## Rate limiting (optional)
|
|
482
|
+
|
|
483
|
+
If you're running many workers and want to share a global rate limit across
|
|
484
|
+
them, install the `rate-limiting` extra and use `try_acquire_request_permit`:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
pip install "va-lis-client[rate-limiting]"
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
import redis
|
|
492
|
+
from va_lis_client.rate_limiter import try_acquire_request_permit
|
|
493
|
+
|
|
494
|
+
r = redis.from_url("redis://localhost:6379/0")
|
|
495
|
+
|
|
496
|
+
if try_acquire_request_permit(redis_client=r, rate_limit=100):
|
|
497
|
+
client.get_session_bills(session_code=20261)
|
|
498
|
+
else:
|
|
499
|
+
# back off
|
|
500
|
+
...
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Or set `REDIS_URL` and `LIS_RATE_LIMIT` environment variables and call with
|
|
504
|
+
no arguments — the module picks them up automatically.
|
|
505
|
+
|
|
506
|
+
## Windows / government CA roots
|
|
507
|
+
|
|
508
|
+
On Windows, Python's OpenSSL doesn't automatically load system certificates,
|
|
509
|
+
which can cause SSL errors when connecting to hosts with enterprise or
|
|
510
|
+
government CA roots. `va-lis-client` includes a `SystemCertSSLAdapter` that
|
|
511
|
+
loads the system certificate store automatically, so no manual cert bundling
|
|
512
|
+
is needed.
|
|
513
|
+
|
|
514
|
+
## Package structure
|
|
515
|
+
|
|
516
|
+
```
|
|
517
|
+
src/va_lis_client/
|
|
518
|
+
├── __init__.py # re-exports LISClient, LISClientError
|
|
519
|
+
├── client.py # HTTP client — returns Pydantic models
|
|
520
|
+
├── exceptions.py # LISClientError
|
|
521
|
+
├── http.py # SystemCertSSLAdapter + shared requests session
|
|
522
|
+
├── rate_limiter.py # optional Redis-based rate limiter
|
|
523
|
+
└── models/
|
|
524
|
+
├── __init__.py # re-exports all models
|
|
525
|
+
├── common.py # Heartbeat, Partner
|
|
526
|
+
├── session.py # Session, SessionEvent
|
|
527
|
+
├── legislation.py # Patron, Legislation, LegislationStatus, ...
|
|
528
|
+
├── text.py # LegislationTextItem, LegislationTextDetail, ...
|
|
529
|
+
├── event.py # LegislationEvent, LegislationEventType, ActorType
|
|
530
|
+
├── committee.py # Committee, CommitteeMember, CommitteeAction
|
|
531
|
+
├── schedule.py # Schedule, ScheduleType, MeetingRoom
|
|
532
|
+
├── calendar.py # CalendarDetail, Agenda, VoteMember, ...
|
|
533
|
+
└── docket.py # DocketDetail, DocketItem, DocketCategory, ...
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## License
|
|
537
|
+
|
|
538
|
+
MIT
|