jcbowyer commited on
Commit
a95107b
·
verified ·
1 Parent(s): b316ec7

Deploy: Consolidated gold tables, fixed nginx docs routing

Browse files
api/routes/bills_neon.py CHANGED
@@ -94,6 +94,9 @@ async def fetch_bills_from_parquet(
94
  q: Optional[str] = None,
95
  session: Optional[str] = None,
96
  topic: Optional[str] = None,
 
 
 
97
  limit: int = 50,
98
  offset: int = 0
99
  ) -> Dict[str, Any]:
@@ -143,6 +146,50 @@ async def fetch_bills_from_parquet(
143
  where_clauses.append("session = ?")
144
  params.append(session)
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  where_clause = " AND ".join(where_clauses)
147
 
148
  # Count total
@@ -197,6 +244,9 @@ async def fetch_bills_from_parquet(
197
  "state": state,
198
  "query": q,
199
  "topic": topic,
 
 
 
200
  "session": session,
201
  "bills": bills,
202
  "total": total,
@@ -411,6 +461,9 @@ async def get_bills(
411
  q: Optional[str] = Query(None, description="Search query (bill number or title)"),
412
  session: Optional[str] = Query(None, description="Legislative session"),
413
  topic: Optional[str] = Query(None, description="Policy topic (e.g., fluoride, dental, medicaid)"),
 
 
 
414
  limit: int = Query(50, ge=1, le=500),
415
  offset: int = Query(0, ge=0)
416
  ):
@@ -421,6 +474,9 @@ async def get_bills(
421
  - `/api/bills?state=AL&q=dental` - Search Alabama bills for "dental"
422
  - `/api/bills?state=AL&session=2024rs` - Get all 2024 regular session bills
423
  - `/api/bills?state=AL&topic=fluoride` - Get fluoride-related Alabama bills
 
 
 
424
  - `/api/bills?state=AL&limit=50` - Browse recent Alabama bills
425
  """
426
  try:
@@ -429,6 +485,9 @@ async def get_bills(
429
  q=q,
430
  session=session,
431
  topic=topic,
 
 
 
432
  limit=limit,
433
  offset=offset
434
  )
 
94
  q: Optional[str] = None,
95
  session: Optional[str] = None,
96
  topic: Optional[str] = None,
97
+ chamber: Optional[str] = None,
98
+ bill_type: Optional[str] = None,
99
+ status: Optional[str] = None,
100
  limit: int = 50,
101
  offset: int = 0
102
  ) -> Dict[str, Any]:
 
146
  where_clauses.append("session = ?")
147
  params.append(session)
148
 
149
+ # Chamber filter (based on bill number prefix)
150
+ if chamber:
151
+ if chamber.lower() == 'house':
152
+ where_clauses.append("(bill_number LIKE 'HB%' OR bill_number LIKE 'HR%' OR bill_number LIKE 'HJR%' OR bill_number LIKE 'HCR%' OR bill_number LIKE 'HJM%' OR bill_number LIKE 'H %')")
153
+ elif chamber.lower() == 'senate':
154
+ where_clauses.append("(bill_number LIKE 'SB%' OR bill_number LIKE 'SR%' OR bill_number LIKE 'SJR%' OR bill_number LIKE 'SCR%' OR bill_number LIKE 'SJM%' OR bill_number LIKE 'S %')")
155
+ elif chamber.lower() == 'joint':
156
+ where_clauses.append("(bill_number LIKE '%JR%' OR bill_number LIKE '%JM%')")
157
+
158
+ # Bill type filter (based on bill number pattern)
159
+ if bill_type:
160
+ if bill_type == 'bill':
161
+ where_clauses.append("(bill_number LIKE 'HB%' OR bill_number LIKE 'SB%' OR bill_number LIKE 'AB%')")
162
+ elif bill_type == 'resolution':
163
+ where_clauses.append("(bill_number LIKE 'HR%' OR bill_number LIKE 'SR%' OR bill_number LIKE 'AR%')")
164
+ elif bill_type == 'joint_resolution':
165
+ where_clauses.append("(bill_number LIKE 'HJR%' OR bill_number LIKE 'SJR%' OR bill_number LIKE 'AJR%')")
166
+ elif bill_type == 'concurrent_resolution':
167
+ where_clauses.append("(bill_number LIKE 'HCR%' OR bill_number LIKE 'SCR%')")
168
+ elif bill_type == 'memorial':
169
+ where_clauses.append("(bill_number LIKE 'HJM%' OR bill_number LIKE 'SJM%')")
170
+
171
+ # Status filter (based on latest_action_description)
172
+ if status:
173
+ status_keywords = {
174
+ 'enacted': 'Enacted',
175
+ 'passed': 'passed|Passed',
176
+ 'adopted': 'Adopted|adopted',
177
+ 'failed': 'Failed|failed',
178
+ 'introduced': 'Introduced|introduced',
179
+ 'referred': 'referred|Referred',
180
+ 'reported': 'reported|Reported'
181
+ }
182
+ keyword = status_keywords.get(status.lower(), status)
183
+
184
+ if '|' in keyword:
185
+ keyword_parts = keyword.split('|')
186
+ keyword_clauses = ["LOWER(latest_action_description) LIKE LOWER(?)"] * len(keyword_parts)
187
+ where_clauses.append(f"({' OR '.join(keyword_clauses)})")
188
+ params.extend([f'%{kw}%' for kw in keyword_parts])
189
+ else:
190
+ where_clauses.append("LOWER(latest_action_description) LIKE LOWER(?)")
191
+ params.append(f'%{keyword}%')
192
+
193
  where_clause = " AND ".join(where_clauses)
194
 
195
  # Count total
 
244
  "state": state,
245
  "query": q,
246
  "topic": topic,
247
+ "chamber": chamber,
248
+ "bill_type": bill_type,
249
+ "status": status,
250
  "session": session,
251
  "bills": bills,
252
  "total": total,
 
461
  q: Optional[str] = Query(None, description="Search query (bill number or title)"),
462
  session: Optional[str] = Query(None, description="Legislative session"),
463
  topic: Optional[str] = Query(None, description="Policy topic (e.g., fluoride, dental, medicaid)"),
464
+ chamber: Optional[str] = Query(None, description="Legislative chamber (house, senate, joint)"),
465
+ bill_type: Optional[str] = Query(None, description="Bill type (bill, resolution, joint_resolution, concurrent_resolution, memorial)"),
466
+ status: Optional[str] = Query(None, description="Bill status (enacted, passed, adopted, failed, introduced, referred, reported)"),
467
  limit: int = Query(50, ge=1, le=500),
468
  offset: int = Query(0, ge=0)
469
  ):
 
474
  - `/api/bills?state=AL&q=dental` - Search Alabama bills for "dental"
475
  - `/api/bills?state=AL&session=2024rs` - Get all 2024 regular session bills
476
  - `/api/bills?state=AL&topic=fluoride` - Get fluoride-related Alabama bills
477
+ - `/api/bills?state=AL&chamber=house` - Get House bills only
478
+ - `/api/bills?state=AL&bill_type=bill` - Get only bills (not resolutions)
479
+ - `/api/bills?state=AL&status=enacted` - Get enacted bills only
480
  - `/api/bills?state=AL&limit=50` - Browse recent Alabama bills
481
  """
482
  try:
 
485
  q=q,
486
  session=session,
487
  topic=topic,
488
+ chamber=chamber,
489
+ bill_type=bill_type,
490
+ status=status,
491
  limit=limit,
492
  offset=offset
493
  )
frontend/src/pages/PolicyMap.tsx CHANGED
@@ -55,6 +55,11 @@ export default function PolicyMap() {
55
  const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
56
  const [expandedBill, setExpandedBill] = useState<string | null>(null)
57
 
 
 
 
 
 
58
  // Read topic from URL, or default to empty (showing topic selector)
59
  const topicFromUrl = searchParams.get('topic') || ''
60
  const [selectedTopic, setSelectedTopic] = useState(topicFromUrl)
@@ -114,7 +119,7 @@ export default function PolicyMap() {
114
  bills: Bill[]
115
  pagination: { limit: number; offset: number; has_more: boolean }
116
  }>({
117
- queryKey: ['bills', selectedState, selectedSession, searchQuery, selectedTopic, page],
118
  queryFn: async () => {
119
  const params = new URLSearchParams({
120
  state: selectedState,
@@ -124,6 +129,9 @@ export default function PolicyMap() {
124
  if (selectedSession) params.append('session', selectedSession)
125
  if (searchQuery) params.append('q', searchQuery)
126
  if (selectedTopic) params.append('topic', selectedTopic)
 
 
 
127
 
128
  const response = await api.get(`/bills?${params}`)
129
  return response.data
@@ -469,6 +477,78 @@ export default function PolicyMap() {
469
  </select>
470
  </div>
471
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
472
 
473
  {/* Search */}
474
  <div className="flex-1 min-w-[250px]">
@@ -489,17 +569,20 @@ export default function PolicyMap() {
489
  </div>
490
 
491
  {/* Clear button */}
492
- {(searchQuery || selectedSession) && (
493
  <button
494
  type="button"
495
  onClick={() => {
496
  setSearchQuery('')
497
  setSelectedSession('')
 
 
 
498
  setPage(1)
499
  }}
500
  className="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors text-sm font-medium"
501
  >
502
- Clear
503
  </button>
504
  )}
505
 
 
55
  const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
56
  const [expandedBill, setExpandedBill] = useState<string | null>(null)
57
 
58
+ // New filter states
59
+ const [selectedChamber, setSelectedChamber] = useState<string>('')
60
+ const [selectedBillType, setSelectedBillType] = useState<string>('')
61
+ const [selectedStatus, setSelectedStatus] = useState<string>('')
62
+
63
  // Read topic from URL, or default to empty (showing topic selector)
64
  const topicFromUrl = searchParams.get('topic') || ''
65
  const [selectedTopic, setSelectedTopic] = useState(topicFromUrl)
 
119
  bills: Bill[]
120
  pagination: { limit: number; offset: number; has_more: boolean }
121
  }>({
122
+ queryKey: ['bills', selectedState, selectedSession, searchQuery, selectedTopic, selectedChamber, selectedBillType, selectedStatus, page],
123
  queryFn: async () => {
124
  const params = new URLSearchParams({
125
  state: selectedState,
 
129
  if (selectedSession) params.append('session', selectedSession)
130
  if (searchQuery) params.append('q', searchQuery)
131
  if (selectedTopic) params.append('topic', selectedTopic)
132
+ if (selectedChamber) params.append('chamber', selectedChamber)
133
+ if (selectedBillType) params.append('bill_type', selectedBillType)
134
+ if (selectedStatus) params.append('status', selectedStatus)
135
 
136
  const response = await api.get(`/bills?${params}`)
137
  return response.data
 
477
  </select>
478
  </div>
479
  )}
480
+
481
+ {/* Chamber Filter - list view only */}
482
+ {viewMode === 'list' && (
483
+ <div className="flex-1 min-w-[150px]">
484
+ <label className="block text-sm font-medium text-gray-700 mb-1">
485
+ Chamber
486
+ </label>
487
+ <select
488
+ className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm text-gray-900 py-2"
489
+ value={selectedChamber}
490
+ onChange={(e) => {
491
+ setSelectedChamber(e.target.value)
492
+ setPage(1)
493
+ }}
494
+ >
495
+ <option value="">All Chambers</option>
496
+ <option value="house">House</option>
497
+ <option value="senate">Senate</option>
498
+ <option value="joint">Joint</option>
499
+ </select>
500
+ </div>
501
+ )}
502
+
503
+ {/* Bill Type Filter - list view only */}
504
+ {viewMode === 'list' && (
505
+ <div className="flex-1 min-w-[150px]">
506
+ <label className="block text-sm font-medium text-gray-700 mb-1">
507
+ Bill Type
508
+ </label>
509
+ <select
510
+ className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm text-gray-900 py-2"
511
+ value={selectedBillType}
512
+ onChange={(e) => {
513
+ setSelectedBillType(e.target.value)
514
+ setPage(1)
515
+ }}
516
+ >
517
+ <option value="">All Types</option>
518
+ <option value="bill">Bill (HB/SB)</option>
519
+ <option value="resolution">Resolution (HR/SR)</option>
520
+ <option value="joint_resolution">Joint Resolution (HJR/SJR)</option>
521
+ <option value="concurrent_resolution">Concurrent Resolution (HCR/SCR)</option>
522
+ <option value="memorial">Memorial (HJM/SJM)</option>
523
+ </select>
524
+ </div>
525
+ )}
526
+
527
+ {/* Status Filter - list view only */}
528
+ {viewMode === 'list' && (
529
+ <div className="flex-1 min-w-[150px]">
530
+ <label className="block text-sm font-medium text-gray-700 mb-1">
531
+ Status
532
+ </label>
533
+ <select
534
+ className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm text-gray-900 py-2"
535
+ value={selectedStatus}
536
+ onChange={(e) => {
537
+ setSelectedStatus(e.target.value)
538
+ setPage(1)
539
+ }}
540
+ >
541
+ <option value="">All Statuses</option>
542
+ <option value="enacted">Enacted</option>
543
+ <option value="passed">Passed</option>
544
+ <option value="adopted">Adopted</option>
545
+ <option value="failed">Failed</option>
546
+ <option value="introduced">Introduced</option>
547
+ <option value="referred">Referred to Committee</option>
548
+ <option value="reported">Reported from Committee</option>
549
+ </select>
550
+ </div>
551
+ )}
552
 
553
  {/* Search */}
554
  <div className="flex-1 min-w-[250px]">
 
569
  </div>
570
 
571
  {/* Clear button */}
572
+ {(searchQuery || selectedSession || selectedChamber || selectedBillType || selectedStatus) && (
573
  <button
574
  type="button"
575
  onClick={() => {
576
  setSearchQuery('')
577
  setSelectedSession('')
578
+ setSelectedChamber('')
579
+ setSelectedBillType('')
580
+ setSelectedStatus('')
581
  setPage(1)
582
  }}
583
  className="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors text-sm font-medium"
584
  >
585
+ Clear Filters
586
  </button>
587
  )}
588