jcbowyer commited on
Commit
b06bb63
·
verified ·
1 Parent(s): 922e350

Deploy: Consolidated gold tables, fixed nginx docs routing

Browse files
api/routes/bills_neon.py CHANGED
@@ -260,8 +260,15 @@ async def fetch_bills_from_parquet(
260
  raise
261
 
262
 
263
- async def fetch_sessions_from_parquet(state: str) -> Dict[str, Any]:
264
- """Fetch sessions from parquet files using DuckDB."""
 
 
 
 
 
 
 
265
  try:
266
  # Use flat file structure (all states in one file)
267
  bills_file = GOLD_DIR / "bills_bills.parquet"
@@ -272,8 +279,71 @@ async def fetch_sessions_from_parquet(state: str) -> Dict[str, Any]:
272
  # Connect to DuckDB
273
  conn = duckdb.connect()
274
 
275
- # Aggregate sessions - filter by state
276
- sql = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  SELECT
278
  session,
279
  MAX(session_name) as session_name,
@@ -281,12 +351,12 @@ async def fetch_sessions_from_parquet(state: str) -> Dict[str, Any]:
281
  MAX(latest_action_date) as end_date,
282
  COUNT(*) as bill_count
283
  FROM read_parquet(?)
284
- WHERE state = ?
285
  GROUP BY session, session_name
286
  ORDER BY session DESC
287
  """
288
 
289
- rows = conn.execute(sql, [data_source, state]).fetchall()
290
 
291
  sessions = []
292
  for row in rows:
@@ -513,16 +583,29 @@ async def get_bills(
513
 
514
  @router.get("/sessions")
515
  async def get_sessions(
516
- state: str = Query(..., description="State abbreviation (e.g., MA, AL)")
 
 
 
 
 
517
  ):
518
  """
519
- Get legislative sessions for a state using parquet files.
520
 
521
  **Examples:**
522
  - `/api/bills/sessions?state=MA` - Get all Massachusetts sessions
 
523
  """
524
  try:
525
- result = await fetch_sessions_from_parquet(state=state.upper())
 
 
 
 
 
 
 
526
 
527
  return result
528
 
 
260
  raise
261
 
262
 
263
+ async def fetch_sessions_from_parquet(
264
+ state: str,
265
+ topic: Optional[str] = None,
266
+ chamber: Optional[str] = None,
267
+ bill_type: Optional[str] = None,
268
+ status: Optional[str] = None,
269
+ q: Optional[str] = None
270
+ ) -> Dict[str, Any]:
271
+ """Fetch sessions from parquet files using DuckDB, filtered by active filters."""
272
  try:
273
  # Use flat file structure (all states in one file)
274
  bills_file = GOLD_DIR / "bills_bills.parquet"
 
279
  # Connect to DuckDB
280
  conn = duckdb.connect()
281
 
282
+ # Build WHERE clause with all filters
283
+ where_conditions = ["state = ?"]
284
+ params = [data_source, state]
285
+
286
+ # Topic filter
287
+ if topic:
288
+ topic_keywords = {
289
+ 'fluoride': 'fluorid',
290
+ 'dental': 'dental',
291
+ 'medicaid': 'medicaid',
292
+ 'oral health': 'oral|dental|teeth',
293
+ 'health': 'health',
294
+ 'education': 'education|school'
295
+ }
296
+ keyword = topic_keywords.get(topic.lower(), topic)
297
+ where_conditions.append(f"REGEXP_MATCHES(LOWER(title), LOWER(?))")
298
+ params.append(keyword)
299
+
300
+ # Chamber filter
301
+ if chamber:
302
+ if chamber.lower() == 'house':
303
+ where_conditions.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%')")
304
+ elif chamber.lower() == 'senate':
305
+ where_conditions.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%')")
306
+ elif chamber.lower() == 'joint':
307
+ where_conditions.append("(bill_number LIKE '%JR%' OR bill_number LIKE '%JM%')")
308
+
309
+ # Bill type filter
310
+ if bill_type:
311
+ type_patterns = {
312
+ 'bill': "(bill_number LIKE 'HB%' OR bill_number LIKE 'SB%' OR bill_number LIKE 'AB%')",
313
+ 'resolution': "(bill_number LIKE 'HR%' OR bill_number LIKE 'SR%' OR bill_number LIKE 'AR%')",
314
+ 'joint_resolution': "(bill_number LIKE 'HJR%' OR bill_number LIKE 'SJR%' OR bill_number LIKE 'AJR%')",
315
+ 'concurrent_resolution': "(bill_number LIKE 'HCR%' OR bill_number LIKE 'SCR%')",
316
+ 'memorial': "(bill_number LIKE 'HJM%' OR bill_number LIKE 'SJM%')"
317
+ }
318
+ if bill_type.lower() in type_patterns:
319
+ where_conditions.append(type_patterns[bill_type.lower()])
320
+
321
+ # Status filter
322
+ if status:
323
+ status_keywords = {
324
+ 'enacted': 'Enacted',
325
+ 'passed': 'passed|Passed',
326
+ 'adopted': 'Adopted|adopted',
327
+ 'failed': 'Failed|failed',
328
+ 'introduced': 'Introduced|introduced',
329
+ 'referred': 'referred|Referred',
330
+ 'reported': 'reported|Reported'
331
+ }
332
+ keyword = status_keywords.get(status.lower(), status)
333
+ where_conditions.append(f"REGEXP_MATCHES(COALESCE(latest_action_description, ''), ?)")
334
+ params.append(keyword)
335
+
336
+ # Search query
337
+ if q:
338
+ where_conditions.append("(LOWER(title) LIKE ? OR LOWER(bill_number) LIKE ?)")
339
+ search_pattern = f"%{q.lower()}%"
340
+ params.append(search_pattern)
341
+ params.append(search_pattern)
342
+
343
+ where_clause = " AND ".join(where_conditions)
344
+
345
+ # Aggregate sessions - filter by state and other filters
346
+ sql = f"""
347
  SELECT
348
  session,
349
  MAX(session_name) as session_name,
 
351
  MAX(latest_action_date) as end_date,
352
  COUNT(*) as bill_count
353
  FROM read_parquet(?)
354
+ WHERE {where_clause}
355
  GROUP BY session, session_name
356
  ORDER BY session DESC
357
  """
358
 
359
+ rows = conn.execute(sql, params).fetchall()
360
 
361
  sessions = []
362
  for row in rows:
 
583
 
584
  @router.get("/sessions")
585
  async def get_sessions(
586
+ state: str = Query(..., description="State abbreviation (e.g., MA, AL)"),
587
+ topic: Optional[str] = Query(None, description="Topic filter (e.g., fluoride, dental)"),
588
+ chamber: Optional[str] = Query(None, description="Chamber filter (house, senate, joint)"),
589
+ bill_type: Optional[str] = Query(None, description="Bill type filter"),
590
+ status: Optional[str] = Query(None, description="Status filter"),
591
+ q: Optional[str] = Query(None, description="Search query")
592
  ):
593
  """
594
+ Get legislative sessions for a state using parquet files, filtered by active search criteria.
595
 
596
  **Examples:**
597
  - `/api/bills/sessions?state=MA` - Get all Massachusetts sessions
598
+ - `/api/bills/sessions?state=MA&topic=dental` - Get sessions with dental bills
599
  """
600
  try:
601
+ result = await fetch_sessions_from_parquet(
602
+ state=state.upper(),
603
+ topic=topic,
604
+ chamber=chamber,
605
+ bill_type=bill_type,
606
+ status=status,
607
+ q=q
608
+ )
609
 
610
  return result
611
 
frontend/src/pages/PolicyMap.tsx CHANGED
@@ -103,9 +103,16 @@ export default function PolicyMap() {
103
 
104
  // Fetch sessions
105
  const { data: sessionsData } = useQuery({
106
- queryKey: ['sessions', selectedState],
107
  queryFn: async () => {
108
- const response = await api.get(`/bills/sessions?state=${selectedState}`)
 
 
 
 
 
 
 
109
  return response.data
110
  },
111
  enabled: viewMode === 'list', // Only fetch sessions in list view
 
103
 
104
  // Fetch sessions
105
  const { data: sessionsData } = useQuery({
106
+ queryKey: ['sessions', selectedState, selectedTopic, selectedChamber, selectedBillType, selectedStatus, searchQuery],
107
  queryFn: async () => {
108
+ const params = new URLSearchParams({ state: selectedState })
109
+ if (selectedTopic) params.append('topic', selectedTopic)
110
+ if (selectedChamber) params.append('chamber', selectedChamber)
111
+ if (selectedBillType) params.append('bill_type', selectedBillType)
112
+ if (selectedStatus) params.append('status', selectedStatus)
113
+ if (searchQuery) params.append('q', searchQuery)
114
+
115
+ const response = await api.get(`/bills/sessions?${params}`)
116
  return response.data
117
  },
118
  enabled: viewMode === 'list', // Only fetch sessions in list view