Skip to content
 
 

Repository files navigation

Seattle Councilmatic

A website for tracking Seattle City Council legislation, built on the Councilmatic platform.

🎯 What It Does

Seattle Councilmatic makes it easy to:

  • Find your Seattle City Council representative
  • Track legislation (bills, resolutions, ordinances)
  • See upcoming council meetings and agendas
  • Follow votes on issues you care about

Live site: http://localhost:8000 (development)


🏗️ Architecture

Components

┌─────────────────┐
│  seattle.gov    │  Official city council website
│  legistar.com   │  Legislative management system
└────────┬────────┘
         │ Scrapers pull data
         ▼
┌─────────────────┐
│  Pupa Scrapers  │  Extract & Transform data
│  (seattle/)     │  
└────────┬────────┘
         │ Writes to OCD format
         ▼
┌─────────────────┐
│  PostgreSQL     │  Open Civic Data models
│  + PostGIS      │  (opencivicdata_*)
└────────┬────────┘
         │ Synced to
         ▼
┌─────────────────┐
│ Councilmatic    │  Django models
│   Models        │  (councilmatic_core_*)
└────────┬────────┘
         │ Indexed by
         ▼
┌─────────────────┐
│ Elasticsearch   │  Full-text search
└────────┬────────┘
         │ Powers
         ▼
┌─────────────────┐
│  Django Web UI  │  User-facing website
│  (seattle_app/) │
└─────────────────┘

Directory Structure

seattle-councilmatic/
├── seattle/              # Pupa scrapers (data collection)
│   ├── __init__.py      # Jurisdiction definition
│   ├── people.py        # Council member scraper
│   ├── events.py        # Meeting scraper (TODO)
│   └── bills.py         # Legislation scraper (TODO)
├── seattle_app/         # Django application (presentation)
│   ├── management/      # Custom management commands
│   ├── templates/       # HTML templates
│   ├── static/          # CSS, JS, images
│   ├── models.py        # Custom model extensions
│   └── settings.py      # Django configuration
├── scripts/             # Helper scripts
│   └── update_seattle.sh # One-command data update
├── docker-compose.yml   # Container orchestration
├── Dockerfile           # Container definition
└── requirements.txt     # Python dependencies

🚀 Getting Started

Prerequisites

Initial Setup

  1. Clone the repository:
   git clone [your-repo-url]
   cd seattle-councilmatic
  1. Configure environment:
   cp .env.example .env
   # Edit .env and add your DJANGO_SECRET_KEY
   # Generate one with: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
  1. Build containers:
   docker compose build
  1. Initialize database:
   # Run Django migrations
   docker compose run --rm app python manage.py migrate
   
   # Initialize Pupa tables
   docker compose run --rm app pupa dbinit us
   
   # Create admin user
   docker compose run --rm app python manage.py createsuperuser
  1. Load initial data:
   docker compose run --rm app ./scripts/update_seattle.sh
  1. Start the application:
   docker compose up
  1. Visit the site:

🔄 Daily Development Workflow

Update Data

# Full update (all scrapers)
docker compose run --rm app ./scripts/update_seattle.sh

# Update specific scraper
docker compose run --rm app ./scripts/update_seattle.sh people

View Logs

# All services
docker compose logs -f

# Specific service
docker compose logs -f app
docker compose logs -f webpack

Django Management Commands

# Run any Django command
docker compose run --rm app python manage.py [command]

# Examples:
docker compose run --rm app python manage.py shell
docker compose run --rm app python manage.py dbshell
docker compose run --rm app python manage.py sync_councilmatic

Restart Services

# Restart all
docker compose restart

# Restart specific service
docker compose restart app

🛠️ Technical Details

Data Flow

  1. Scraping (pupa update seattle)

    • Scrapers in seattle/ fetch data from source websites
    • Data is validated and cached locally
    • Written to OpenCivicData tables (opencivicdata_*)
  2. Syncing (python manage.py sync_councilmatic)

    • Bridges OCD models → Councilmatic models
    • Handles multi-table inheritance pattern
    • Creates required fields (slugs, etc.)
  3. Indexing (python manage.py update_index)

    • Populates Elasticsearch for full-text search
    • Powers the site's search functionality

Why Two Sets of Models?

Django-councilmatic 5.x uses multi-table inheritance rather than proxy models:

  • OCD Models (opencivicdata_person): Canonical data from scrapers
  • Councilmatic Models (councilmatic_core_person): Extended with web-specific fields (slugs, headshots, etc.)

They're linked via foreign key, requiring the sync step.

Important Settings

In seattle_app/settings.py:

# Must match your Jurisdiction.name in seattle/__init__.py
OCD_CITY_COUNCIL_NAME = 'Seattle City Council'

# Both scraper and app must be in INSTALLED_APPS
INSTALLED_APPS = [
    # ...
    'opencivicdata.core.apps.BaseConfig',
    'councilmatic_core',
    'seattle_app',  # Django app
    'seattle',      # Pupa scrapers
]

🧪 Testing

Run Tests

docker compose run --rm app python manage.py test

Check Scraper Output

# Scrape without importing (for testing)
docker compose run --rm app pupa update seattle people --scrape

# Check what was scraped
docker compose run --rm app ls -la _data/

Validate Data

docker compose run --rm app python manage.py shell
from opencivicdata.core.models import Person, Organization
from councilmatic_core.models import Person as CouncilPerson

# Check data counts
print(f"OCD People: {Person.objects.count()}")
print(f"Councilmatic People: {CouncilPerson.objects.count()}")
print(f"Organizations: {Organization.objects.count()}")

# Verify memberships
council = Organization.objects.get(name='Seattle City Council')
print(f"Council members: {council.memberships.count()}")

🐛 Troubleshooting

"No data showing on website"

  1. Check if data exists in database:
   docker compose run --rm app python manage.py shell -c "from opencivicdata.core.models import Person; print(Person.objects.count())"
  1. Run sync command:
   docker compose run --rm app python manage.py sync_councilmatic
  1. Rebuild search index:
   docker compose run --rm app python manage.py rebuild_index --noinput

"Containers won't start"

Check port conflicts:

# Check if ports are in use
lsof -i :8000  # Django
lsof -i :5432  # PostgreSQL
lsof -i :9200  # Elasticsearch
lsof -i :3000  # Webpack

"Out of memory errors"

Increase Docker Desktop memory allocation:

  • Settings → Resources → Memory → Set to at least 4GB

"Database migrations fail"

Reset and re-initialize:

docker compose down -v  # WARNING: Deletes all data!
docker compose up -d postgres
docker compose run --rm app python manage.py migrate
docker compose run --rm app pupa dbinit us

📚 Key Documentation Links


🤝 Contributing

Adding New Scrapers

  1. Create scraper file in seattle/ (e.g., events.py)

  2. Register in jurisdiction (seattle/__init__.py):

   from .events import SeattleEventScraper
   
   class Seattle(Jurisdiction):
       scrapers = {
           "people": SeattlePersonScraper,
           "events": SeattleEventScraper,  # Add here
       }
  1. Test the scraper:
   docker compose run --rm app pupa update seattle events --scrape
  1. Run full import:
   docker compose run --rm app ./scripts/update_seattle.sh events

Code Style

  • Follow PEP 8 for Python
  • Use Django conventions
  • Add docstrings to functions
  • Comment complex logic

📝 License

[Your chosen license]


🙏 Acknowledgments

Built with:

Data sources:

About

Track Seattle City Council legislation, committee meetings, and council member votes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages