open-navigator / scripts /datasources /localview /check_meeting_data.py
jcbowyer's picture
Clean HuggingFace deployment without binary files
e59d91d
Raw
History Blame Contribute Delete
10.5 kB
#!/usr/bin/env python3
"""
Check Current Meeting Data Status for Priority States
Analyzes what meeting data exists and what needs to be loaded/scraped.
Usage:
python scripts/localview/check_meeting_data.py
python scripts/localview/check_meeting_data.py --states AL,GA,IN,MA,MT,WA,WI
"""
import argparse
import sys
from pathlib import Path
from typing import List, Dict
from datetime import datetime
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from loguru import logger
try:
import polars as pl
POLARS_AVAILABLE = True
except ImportError:
POLARS_AVAILABLE = False
logger.warning("polars not available - install with: pip install polars")
# Priority states for development
PRIORITY_STATES = ['AL', 'GA', 'IN', 'MA', 'WA', 'WI']
def check_openstates_events(states: List[str]) -> Dict[str, Dict]:
"""Check OpenStates legislative events (committee hearings, etc.)"""
logger.info("📋 Checking OpenStates Legislative Events...")
results = {}
gold_dir = Path("data/gold/states")
for state in states:
state_dir = gold_dir / state
events_file = state_dir / "events_events.parquet"
participants_file = state_dir / "events_participants.parquet"
state_data = {
"has_events": events_file.exists(),
"has_participants": participants_file.exists(),
"event_count": 0,
"participant_count": 0,
"date_range": None
}
if POLARS_AVAILABLE and events_file.exists():
try:
df = pl.read_parquet(events_file)
state_data["event_count"] = len(df)
if "start_date" in df.columns:
dates = df["start_date"].drop_nulls()
if len(dates) > 0:
state_data["date_range"] = f"{dates.min()} to {dates.max()}"
except Exception as e:
logger.warning(f"Could not read {events_file}: {e}")
if POLARS_AVAILABLE and participants_file.exists():
try:
df = pl.read_parquet(participants_file)
state_data["participant_count"] = len(df)
except Exception as e:
logger.warning(f"Could not read {participants_file}: {e}")
results[state] = state_data
# Print summary
print("\n" + "="*70)
print("📋 OPENSTATES LEGISLATIVE EVENTS (Committee Hearings, Sessions)")
print("="*70)
for state, data in results.items():
print(f"\n{state}:")
if data["has_events"]:
print(f" ✅ Events: {data['event_count']:,}")
print(f" ✅ Participants: {data['participant_count']:,}")
if data["date_range"]:
print(f" 📅 Date Range: {data['date_range']}")
else:
print(f" ❌ No event data found")
return results
def check_localview_meetings(states: List[str]) -> Dict[str, Dict]:
"""Check LocalView municipal meetings (city council, county boards)"""
logger.info("🏛️ Checking LocalView Municipal Meetings...")
results = {}
gold_dir = Path("data/gold/states")
meetings_global = Path("data/gold/meetings/meetings_transcripts.parquet")
# Check state-specific files
for state in states:
state_dir = gold_dir / state
meetings_file = state_dir / "meetings_local.parquet"
state_data = {
"has_meetings": meetings_file.exists(),
"meeting_count": 0,
"date_range": None
}
if POLARS_AVAILABLE and meetings_file.exists():
try:
df = pl.read_parquet(meetings_file)
state_data["meeting_count"] = len(df)
if "meeting_date" in df.columns:
dates = df["meeting_date"].drop_nulls()
if len(dates) > 0:
state_data["date_range"] = f"{dates.min()} to {dates.max()}"
except Exception as e:
logger.warning(f"Could not read {meetings_file}: {e}")
results[state] = state_data
# Check global meetings file
global_exists = meetings_global.exists()
global_count = 0
global_range = None
if POLARS_AVAILABLE and global_exists:
try:
df = pl.read_parquet(meetings_global)
global_count = len(df)
if "meeting_date" in df.columns:
dates = df["meeting_date"].drop_nulls()
if len(dates) > 0:
global_range = f"{dates.min()} to {dates.max()}"
# Count by state
if "state" in df.columns:
state_counts = df.group_by("state").count()
print("\n" + "="*70)
print("🏛️ LOCALVIEW MUNICIPAL MEETINGS (City Council, County Boards)")
print("="*70)
print(f"\nGlobal Dataset: {global_count:,} total meetings")
if global_range:
print(f"Date Range: {global_range}")
print("\nBy State:")
for row in state_counts.iter_rows(named=True):
state = row.get("state", "Unknown")
count = row.get("count", 0)
if state in states:
print(f" {state}: {count:,} meetings")
results[state]["meeting_count"] = count
except Exception as e:
logger.warning(f"Could not read {meetings_global}: {e}")
else:
print("\n" + "="*70)
print("🏛️ LOCALVIEW MUNICIPAL MEETINGS")
print("="*70)
print("\n❌ No LocalView data found")
print("\nTo load LocalView data:")
print("1. Download from: https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/NJTBEM")
print("2. Save to: data/cache/localview/")
print("3. Run: python scripts/datasources/localview/localview_ingestion.py")
return results
def check_youtube_cache(states: List[str]) -> Dict[str, Dict]:
"""Check cached YouTube scraping data"""
logger.info("📹 Checking YouTube Scraping Cache...")
cache_dir = Path("data/cache/localview")
results = {}
# Check for municipality channels
channels_file = cache_dir / "municipality_channels.csv"
has_channels = channels_file.exists()
if POLARS_AVAILABLE and has_channels:
try:
df = pl.read_csv(channels_file)
print("\n" + "="*70)
print("📹 YOUTUBE SCRAPING CACHE")
print("="*70)
print(f"\nMunicipality Channels: {len(df):,} total")
if "state" in df.columns:
for state in states:
state_channels = df.filter(pl.col("state") == state)
count = len(state_channels)
results[state] = {"channel_count": count}
print(f" {state}: {count} channels")
# Show municipalities
if count > 0 and "municipality" in df.columns:
munis = state_channels["municipality"].to_list()[:5]
print(f" {', '.join(munis)}")
if count > 5:
print(f" ... and {count - 5} more")
# Check for video cache
video_files = list(cache_dir.glob("videos_*.parquet"))
if video_files:
print(f"\n✅ Cached video data: {len(video_files)} files")
for vf in video_files[:5]:
print(f" - {vf.name}")
else:
print("\n⚠️ No cached video data")
except Exception as e:
logger.warning(f"Could not read {channels_file}: {e}")
else:
print("\n" + "="*70)
print("📹 YOUTUBE SCRAPING CACHE")
print("="*70)
print("\n❌ No YouTube channel data found")
print("\nTo discover channels:")
print("python scripts/localview/update_municipality_list.py --states AL,GA,IN,MA,MT,WA,WI")
return results
def print_recommendations(states: List[str]):
"""Print recommended next steps"""
print("\n" + "="*70)
print("🎯 RECOMMENDED NEXT STEPS")
print("="*70)
print("\n1️⃣ Download LocalView Historical Data (2006-2023)")
print(" Visit: https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/NJTBEM")
print(" Save to: data/cache/localview/")
print(" Run: python scripts/datasources/localview/localview_ingestion.py")
print("\n2️⃣ Discover Municipal YouTube Channels")
print(f" python scripts/localview/update_municipality_list.py \\")
print(f" --states {','.join(states)}")
print("\n3️⃣ Scrape Recent Meetings (2024-2026)")
print(" # First, get YouTube API key from Google Cloud Console")
print(" # Add to .env: YOUTUBE_API_KEY=your_key_here")
print(f" python scripts/localview/scrape_youtube_channels.py \\")
print(f" --states {','.join(states)} \\")
print(" --since 2024-01-01")
print("\n4️⃣ Extract Transcripts and Contacts")
print(f" python scripts/localview/extract_transcripts.py \\")
print(f" --states {','.join(states)}")
print("")
print(" python scripts/manage_contacts.py extract \\")
print(f" --states {','.join(states)} \\")
print(" --batch-size 1000")
print("\n" + "="*70)
def main():
parser = argparse.ArgumentParser(
description="Check meeting data status for priority states"
)
parser.add_argument(
'--states',
default=','.join(PRIORITY_STATES),
help=f'Comma-separated state codes (default: {",".join(PRIORITY_STATES)})'
)
args = parser.parse_args()
states = [s.strip().upper() for s in args.states.split(',')]
logger.info(f"Checking meeting data for: {', '.join(states)}")
# Run all checks
openstates = check_openstates_events(states)
localview = check_localview_meetings(states)
youtube = check_youtube_cache(states)
# Print recommendations
print_recommendations(states)
if __name__ == "__main__":
main()