This guide explains how to track bill status through the legislative process and determine when bills become law.
INTRODUCED → COMMITTEE → FLOOR VOTE → PASSED HOUSE → PASSED SENATE → PRESIDENT → ENACTED
↓ ↓ ↓ ↓ ↓ ↓ ↓
Action Action Action Action Action Action Action
Code Code Code Code Code Code Code
#### House Actions
INTRODUCED
- Bill introduced in HouseREFERRED
- Bill referred to committeeREPORTED
- Committee reported bill favorablyPASSED_HOUSE
- Bill passed House floor voteFAILED_HOUSE
- Bill failed House floor vote#### Senate Actions
RECEIVED_SENATE
- Bill received from HousePASSED_SENATE
- Bill passed Senate floor voteFAILED_SENATE
- Bill failed Senate floor vote#### Final Actions
ENACTED
- Bill signed into law by PresidentVETOED
- Bill vetoed by PresidentBECAME_LAW
- Bill became law without signature (after 10 days)POCKET_VETO
- Bill pocket vetoed (Congress adjourned)-- Bills that passed both House and Senate
SELECT DISTINCT b.bill_id, b.title, b.sponsor_bioguide,
MAX(CASE WHEN a.action_code = 'PASSED_HOUSE' THEN a.action_date END) as house_pass_date,
MAX(CASE WHEN a.action_code = 'PASSED_SENATE' THEN a.action_date END) as senate_pass_date
FROM bills b
JOIN actions a ON b.bill_id = a.bill_id
WHERE b.congress = 119
AND a.action_code IN ('PASSED_HOUSE', 'PASSED_SENATE')
GROUP BY b.bill_id
HAVING house_pass_date IS NOT NULL
AND senate_pass_date IS NOT NULL
ORDER BY house_pass_date DESC;
-- Bills that became law
SELECT b.bill_id, b.title, b.sponsor_bioguide,
MAX(CASE WHEN a.action_code = 'ENACTED' THEN a.action_date END) as enacted_date,
MAX(CASE WHEN a.action_code = 'BECAME_LAW' THEN a.action_date END) as became_law_date
FROM bills b
JOIN actions a ON b.bill_id = a.bill_id
WHERE b.congress = 119
AND a.action_code IN ('ENACTED', 'BECAME_LAW')
GROUP BY b.bill_id
HAVING enacted_date IS NOT NULL OR became_law_date IS NOT NULL
ORDER BY COALESCE(enacted_date, became_law_date) DESC;
-- Comprehensive bill status for all bills
SELECT b.bill_id, b.title, b.policy_area,
MAX(CASE WHEN a.action_code = 'INTRODUCED' THEN a.action_date END) as introduced_date,
MAX(CASE WHEN a.action_code = 'PASSED_HOUSE' THEN a.action_date END) as house_pass_date,
MAX(CASE WHEN a.action_code = 'PASSED_SENATE' THEN a.action_date END) as senate_pass_date,
MAX(CASE WHEN a.action_code = 'ENACTED' THEN a.action_date END) as enacted_date,
MAX(CASE WHEN a.action_code = 'VETOED' THEN a.action_date END) as vetoed_date,
CASE
WHEN MAX(CASE WHEN a.action_code = 'ENACTED' THEN 1 END) = 1 THEN 'ENACTED'
WHEN MAX(CASE WHEN a.action_code = 'VETOED' THEN 1 END) = 1 THEN 'VETOED'
WHEN MAX(CASE WHEN a.action_code = 'PASSED_SENATE' THEN 1 END) = 1 THEN 'PASSED_BOTH_CHAMBERS'
WHEN MAX(CASE WHEN a.action_code = 'PASSED_HOUSE' THEN 1 END) = 1 THEN 'PASSED_HOUSE_ONLY'
ELSE 'IN_PROGRESS'
END as current_status
FROM bills b
LEFT JOIN actions a ON b.bill_id = a.bill_id
WHERE b.congress = 119
GROUP BY b.bill_id
ORDER BY introduced_date DESC;
-- Count bills by current status
SELECT
CASE
WHEN MAX(CASE WHEN a.action_code = 'ENACTED' THEN 1 END) = 1 THEN 'ENACTED'
WHEN MAX(CASE WHEN a.action_code = 'VETOED' THEN 1 END) = 1 THEN 'VETOED'
WHEN MAX(CASE WHEN a.action_code = 'PASSED_SENATE' THEN 1 END) = 1 THEN 'PASSED_BOTH_CHAMBERS'
WHEN MAX(CASE WHEN a.action_code = 'PASSED_HOUSE' THEN 1 END) = 1 THEN 'PASSED_HOUSE_ONLY'
ELSE 'IN_PROGRESS'
END as bill_status,
COUNT(*) as bill_count
FROM bills b
LEFT JOIN actions a ON b.bill_id = a.bill_id
WHERE b.congress = 119
GROUP BY bill_status
ORDER BY bill_count DESC;
from enum import Enum
from datetime import datetime
from typing import Optional, List, Dictclass BillStatus(Enum):
INTRODUCED = "INTRODUCED"
IN_COMMITTEE = "IN_COMMITTEE"
PASSED_HOUSE = "PASSED_HOUSE"
PASSED_SENATE = "PASSED_SENATE"
PASSED_BOTH = "PASSED_BOTH"
ENACTED = "ENACTED"
VETOED = "VETOED"
FAILED = "FAILED"
class BillStatusTracker:
def __init__(self, db_session):
self.session = db_session
def get_bill_status(self, bill_id: str) -> Dict:
"""Get comprehensive status for a specific bill."""
# Get all actions for the bill
actions_query = """
SELECT action_code, action_date, text
FROM actions
WHERE bill_id = :bill_id
ORDER BY action_date ASC
"""
actions = self.session.execute(text(actions_query), {'bill_id': bill_id}).fetchall()
if not actions:
return {'status': 'UNKNOWN', 'actions': []}
# Determine current status
status = self._determine_status(actions)
return {
'bill_id': bill_id,
'status': status.value,
'actions': [dict(action) for action in actions],
'introduced_date': self._get_action_date(actions, 'INTRODUCED'),
'house_pass_date': self._get_action_date(actions, 'PASSED_HOUSE'),
'senate_pass_date': self._get_action_date(actions, 'PASSED_SENATE'),
'enacted_date': self._get_action_date(actions, 'ENACTED'),
'vetoed_date': self._get_action_date(actions, 'VETOED'),
'is_law': status in [BillStatus.ENACTED],
'passed_both_chambers': status in [BillStatus.PASSED_BOTH, BillStatus.ENACTED]
}
def _determine_status(self, actions: List) -> BillStatus:
"""Determine current bill status based on actions."""
action_codes = [action.action_code for action in actions]
if 'ENACTED' in action_codes:
return BillStatus.ENACTED
elif 'VETOED' in action_codes:
return BillStatus.VETOED
elif 'PASSED_HOUSE' in action_codes and 'PASSED_SENATE' in action_codes:
return BillStatus.PASSED_BOTH
elif 'PASSED_HOUSE' in action_codes:
return BillStatus.PASSED_HOUSE
elif 'PASSED_SENATE' in action_codes:
return BillStatus.PASSED_SENATE
elif 'INTRODUCED' in action_codes:
return BillStatus.INTRODUCED
else:
return BillStatus.UNKNOWN
def _get_action_date(self, actions: List, action_code: str) -> Optional[datetime]:
"""Get date for specific action code."""
for action in actions:
if action.action_code == action_code:
return action.action_date
return None
def get_enacted_bills(self, congress: int) -> List[Dict]:
"""Get all bills enacted into law for a Congress."""
query = """
SELECT DISTINCT b.bill_id, b.title, b.sponsor_bioguide,
MAX(CASE WHEN a.action_code = 'ENACTED' THEN a.action_date END) as enacted_date
FROM bills b
JOIN actions a ON b.bill_id = a.bill_id
WHERE b.congress = :congress
AND a.action_code = 'ENACTED'
GROUP BY b.bill_id
ORDER BY enacted_date DESC
"""
results = self.session.execute(text(query), {'congress': congress}).fetchall()
return [dict(result) for result in results]
def get_bills_passed_both_chambers(self, congress: int) -> List[Dict]:
"""Get bills that passed both House and Senate."""
query = """
SELECT DISTINCT b.bill_id, b.title, b.sponsor_bioguide,
MAX(CASE WHEN a.action_code = 'PASSED_HOUSE' THEN a.action_date END) as house_pass_date,
MAX(CASE WHEN a.action_code = 'PASSED_SENATE' THEN a.action_date END) as senate_pass_date
FROM bills b
JOIN actions a ON b.bill_id = a.bill_id
WHERE b.congress = :congress
AND a.action_code IN ('PASSED_HOUSE', 'PASSED_SENATE')
GROUP BY b.bill_id
HAVING house_pass_date IS NOT NULL
AND senate_pass_date IS NOT NULL
ORDER BY house_pass_date DESC
"""
results = self.session.execute(text(query), {'congress': congress}).fetchall()
return [dict(result) for result in results]
@app.route('/api/bill//status')
def get_bill_status(bill_id):
"""Get comprehensive status for a specific bill."""
try:
tracker = BillStatusTracker(get_db_session())
status = tracker.get_bill_status(bill_id)
if status['status'] == 'UNKNOWN':
return jsonify({'error': 'Bill not found'}), 404
return jsonify(status)
except Exception as e:
return jsonify({'error': str(e)}), 500@app.route('/api/bills/enacted/')
def get_enacted_bills(congress):
"""Get all bills enacted into law for a Congress."""
try:
tracker = BillStatusTracker(get_db_session())
bills = tracker.get_enacted_bills(congress)
return jsonify(bills)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/bills/passed-both/')
def get_bills_passed_both(congress):
"""Get bills that passed both chambers for a Congress."""
try:
tracker = BillStatusTracker(get_db_session())
bills = tracker.get_bills_passed_both_chambers(congress)
return jsonify(bills)
except Exception as e:
return jsonify({'error': str(e)}), 500
async function displayBillStatus(billId) {
try {
const response = await fetch(/api/bill/${billId}/status
);
const status = await response.json();
const statusElement = document.getElementById('bill-status');
statusElement.innerHTML = `
Bill Status: ${status.status}
Introduced: ${status.introduced_date || 'N/A'}
House Passed: ${status.house_pass_date || 'N/A'}
Senate Passed: ${status.senate_pass_date || 'N/A'}
Enacted: ${status.enacted_date || 'N/A'}
Vetoed: ${status.vetoed_date || 'N/A'}
Is Law: ${status.is_law ? 'Yes' : 'No'}
Current Status: ${status.status}
`;
} catch (error) {
console.error('Error fetching bill status:', error);
}
}
To properly track bill status, ensure these action codes are populated:
Common action codes from Congress.gov
ACTION_CODE_MAPPING = {
'INTRODUCED': 'INTRODUCED',
'REFERRED': 'REFERRED',
'REPORTED': 'REPORTED',
'PASSED': 'PASSED_HOUSE', # Context determines chamber
'ENACTED': 'ENACTED',
'VETOED': 'VETOED',
'BECAME_LAW': 'BECAME_LAW'
}
Modify the enhanced_daily_update.py
script to also fetch and update action data:
def fetch_bill_actions(self, congressgov_id: str) -> List[Dict]:
"""Fetch bill actions from Congress.gov API."""
actions = []
actions_url = f"https://api.congress.gov/v3/bill/{congressgov_id}/actions"
headers = {'X-API-Key': self.congressgov_api_key}
try:
response = self.session.get(actions_url, headers=headers, timeout=30)
if response.status_code == 200:
data = response.json()
if 'actions' in data:
for action in data['actions']:
actions.append({
'action_date': action.get('actionDate'),
'action_code': self._map_action_code(action.get('text', '')),
'text': action.get('text', ''),
'committee_code': action.get('committee', {}).get('systemCode', '')
})
except Exception as e:
logger.debug(f"Error fetching actions for {congressgov_id}: {e}")
return actionsdef _map_action_code(self, action_text: str) -> str:
"""Map action text to standardized action codes."""
text = action_text.upper()
if 'PASSED' in text and 'HOUSE' in text:
return 'PASSED_HOUSE'
elif 'PASSED' in text and 'SENATE' in text:
return 'PASSED_SENATE'
elif 'ENACTED' in text:
return 'ENACTED'
elif 'VETOED' in text:
return 'VETOED'
elif 'INTRODUCED' in text:
return 'INTRODUCED'
elif 'REFERRED' in text:
return 'REFERRED'
else:
return 'OTHER'
-- Bill success rate by policy area
SELECT b.policy_area,
COUNT(*) as total_bills,
COUNT(CASE WHEN a.action_code = 'ENACTED' THEN 1 END) as enacted_bills,
ROUND(COUNT(CASE WHEN a.action_code = 'ENACTED' THEN 1 END) 100.0 / COUNT(), 2) as success_rate
FROM bills b
LEFT JOIN actions a ON b.bill_id = a.bill_id AND a.action_code = 'ENACTED'
WHERE b.congress = 119
AND b.policy_area IS NOT NULL
GROUP BY b.policy_area
HAVING total_bills > 5
ORDER BY success_rate DESC;
-- Average time from introduction to enactment
SELECT b.policy_area,
AVG(DATEDIFF(
MAX(CASE WHEN a.action_code = 'ENACTED' THEN a.action_date END),
MAX(CASE WHEN a.action_code = 'INTRODUCED' THEN a.action_date END)
)) as avg_days_to_enactment
FROM bills b
JOIN actions a ON b.bill_id = a.bill_id
WHERE b.congress = 119
AND a.action_code IN ('INTRODUCED', 'ENACTED')
GROUP BY b.bill_id, b.policy_area
HAVING avg_days_to_enactment IS NOT NULL
ORDER BY avg_days_to_enactment;
---
This comprehensive bill status tracking system provides complete visibility into the legislative process and enables accurate determination of which bills have been passed and enacted into law.