pgx
PGX — Lightweight Postgres for AI agents. Written in Rust. Thousands of isolated databases in one runtime. 🐘
Translation pending
Track Philadelphia City Council legislation in plain English — bill summaries, sponsors, voting records and 17 council member profiles. Free, independent, no ads.
Translation pending
DataPython · TypeScriptGitHub @cainesventuresOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Open Common Ground — Philadelphia City Council Tracker
A free, citizen-friendly tracker for Philadelphia City Council legislation. Every bill gets a plain-language name and summary, 17 AI perspectives, related news articles, and filterable category tags — so any Philadelphian can understand what their City Council is actually doing.Read the full README ↗Live at opencommonground.com
A free, citizen-friendly tracker for Philadelphia City Council legislation. Every bill gets a plain-language name and summary, 17 AI perspectives, related news articles, and filterable category tags — so any Philadelphian can understand what their City Council is actually doing.
| Layer | Tech |
|---|---|
| Frontend | Next.js 15 (App Router), TailwindCSS, TypeScript — hosted on Vercel |
| Maps | Leaflet + react-leaflet (OpenStreetMap tiles, no API key needed) |
| Backend | FastAPI (async Python), SQLAlchemy ORM — hosted on Railway |
| Database | SQLite + WAL mode, replicated via Litestream → Backblaze B2 |
| AI | Plug-and-play: Ollama (default, free), Claude, or OpenAI — set via env vars |
| Ingestion | Playwright headless browser scraper (Philadelphia Legistar) |
| Auth | Google OAuth 2.0 + JWT (dev-only bypass available) |
| Resend (weekly digest emails) | |
| Payments | Stripe Checkout (one-time donations) |
See GETTING_STARTED.md for the full walkthrough.
Ollama auto-starts: If Ollama is installed but not running when you trigger an AI action, the backend will start it automatically and retry.
pip install -r requirements.txt -c constraints.txt
playwright install chromium
cd frontend && npm install
cp .env.example .env
Minimum required in .env:
DATABASE_URL=sqlite:///./common_ground.db
JWT_SECRET=your_random_secret
ENVIRONMENT=development
# AI provider (defaults to Ollama — free, local, private)
AI_PROVIDER=ollama
AI_MODEL=llama3.1:8b
AI_BASE_URL=http://localhost:11434
Google OAuth is optional during development — use the small Dev button on the navbar instead (only visible in development mode).
alembic upgrade head
# Terminal 1 — backend
uvicorn main:app --reload
# Terminal 2 — frontend
cd frontend && npm run dev
The admin panel at /admin is restricted to users with subscription_tier = 'dev'. In development, use the Dev Login button on the navbar — it automatically creates and logs in a dev user.
The panel has three sections:
A single unified workflow that processes bills through up to four steps in sequence. Configure which steps to run, set a date scope, and start — a live SSE progress bar streams results as each bill is processed. A Stop button halts the run at any time, and pipeline state persists across navigation.
| Step | Default | What it does |
|---|---|---|
| Step 1: Sponsors | Off | Backfills missing sponsor data from Legistar |
| Step 2: Analyze | On | Fetches full bill text, generates plain title, auto-tags, runs AI analysis (summary, impact, bill type, 3 base perspectives) |
| Step 3: Perspectives | On | Generates all 17 perspectives; multi-select for specific perspective types |
| Step 4: News | Off | Fetches Google News articles for each bill |
Date scope — filter which bills are processed by year, month, or a custom date range.
Force re-analyze — toggle to reprocess bills that have already been analyzed.
All AI features use the same plug-and-play provider — switch between Ollama, Claude, or OpenAI via env vars with no code changes. If Ollama is not running when an AI action is triggered, the backend starts it automatically.
Short, human-friendly bill names generated by AI. Shown prominently on cards with the official legal title beneath it labeled LEGAL TITLE:.
AI assigns 1–3 category tags from a fixed list:
housing · zoning · transportation · public safety · budget · education · environment · health · parks · business · infrastructure · labor · technology · social services
Tags appear as filterable dropdowns on the home feed and legislation page, ordered by frequency. Tag counts update dynamically to reflect all active filters.
Run via the pipeline (Step 2). Generates:
Each bill can be viewed through 17 lenses:
| Group | Perspectives |
|---|---|
| Political | progressive, conservative, libertarian, socialist, centrist |
| Policy | economic, civil_liberties, environmental, public_health, urban_planning |
| Demographic | working_class, business, youth, elderly, neighborhood |
| Special | christian_ethicist, conspiracy_theorist |
The 3 base perspectives are generated during analysis. The remaining 14 are generated on demand from the bill detail page and cached. Each perspective shows: position (support/oppose/neutral/mixed), key_arguments, concerns, and a 50-word assessment.
A perspectives tally at the top of the section shows support/oppose/neutral/mixed counts with percentages and a stacked bar. Click any row to expand the full perspective inline.
On each bill's detail page, related news articles are pulled from Google News RSS using the bill's topic tags and keywords. Articles are fetched during the pipeline (Step 4) and can be refreshed individually.
Logged-in users can bookmark any bill from the home feed, legislation browser, or bill detail page. Saved bills are accessible at /my-bills.
Users can opt in to weekly digest emails from their /profile page. The digest is sent by the Weekly Digest utility in the admin panel and includes up to 10 recently analyzed bills with summaries, impact levels, and links to perspectives. Powered by Resend.
Requires RESEND_API_KEY in .env.
Available at /dashboard for dev users. Shows:
The /donate page lets supporters make one-time contributions via Stripe Checkout ($5 / $10 / $20 / $50 / $100, plus a custom "Other" amount). Successful payments are recorded to the donations table and redirect to /donate/success.
Requires STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, and STRIPE_WEBHOOK_SECRET in .env.
The /councilmembers page shows:
Each member's detail page shows:
# Ollama (default — free, local, private)
AI_PROVIDER=ollama
AI_MODEL=llama3.1:8b
AI_BASE_URL=http://localhost:11434
# Claude (Anthropic)
AI_PROVIDER=claude
AI_MODEL=claude-sonnet-4-6
AI_API_KEY=sk-ant-...
# OpenAI (or any compatible API)
AI_PROVIDER=openai
AI_MODEL=gpt-4o
AI_BASE_URL=https://api.openai.com/v1
AI_API_KEY=sk-...
GET /api/legislation/search?q=...&level=local&analyzed=true&tag=housing&impact=high&sponsor=...&status=...
GET /api/legislation/tag-counts?sponsor=...&status=...&impact=... Tag frequency counts (filter-aware)
GET /api/legislation/year-counts?q=...&analyzed=...&tag=...&impact=...&status=...&sponsor=...
GET /api/legislation/month-counts?q=...&analyzed=...&tag=...&impact=...&status=...&sponsor=...
GET /api/legislation/{id} Bill detail (includes news_links, perspectives eager-loaded)
POST /api/legislation/{id}/analyze Trigger analysis + news fetch (dev tier)
POST /api/legislation/{id}/fetch-news Fetch news for one bill (dev tier)
POST /api/legislation/fetch-news-all Fetch news for all local bills (dev tier)
POST /api/legislation/plain-titles Generate plain titles for all bills (dev tier)
POST /api/legislation/tag-all Auto-tag all untagged bills (dev tier)
POST /api/legislation/sync-statuses Re-fetch Legistar status for in-flight bills (dev tier)
GET /api/legislation/stream/pipeline SSE: unified pipeline (steps, force_analyze, perspective_types, year, month, date_from, date_to)
GET /api/legislation/stream/backfill-sponsors SSE: backfill missing sponsor data
GET /api/legislation/{id}/perspectives All perspectives for a bill
POST /api/legislation/{id}/perspectives/{type} Generate one perspective (90s timeout)
POST /api/legislation/{id}/vote Cast a vote
GET /api/councilmembers List all council members
GET /api/councilmembers/{id} Council member detail + sponsored bills (paginated)
GET /api/councilmembers/districts-geojson Philadelphia district boundaries (proxied)
POST /api/councilmembers/scrape Scrape phlcouncil.com profiles (dev tier)
GET /api/auth/google Redirect to Google OAuth consent screen
GET /api/auth/google/callback OAuth callback — issues JWT, redirects to frontend
GET /api/auth/me Current user profile
POST /api/auth/dev-login Dev-only login bypass (development only)
POST /api/users/me/track/{bill_id} Toggle save/unsave a bill
GET /api/users/me/tracked-bills List saved bills (full detail)
GET /api/users/me/tracked-bill-ids List saved bill IDs (fast lookup)
PATCH /api/users/me/preferences Update digest opt-in preference
POST /api/users/send-digest Send weekly digest to opted-in users (dev tier)
GET /api/hearings/upcoming Bills with upcoming hearings
POST /api/hearings/refresh Scrape City Council calendar for hearing dates (dev tier)
GET /api/metrics Site-wide metrics
GET /api/donations/config Stripe publishable key
POST /api/donations/checkout Create Stripe Checkout session
POST /api/donations/webhook Stripe webhook (payment confirmation)
Full interactive docs at http://localhost:8000/docs.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | No | Defaults to SQLite |
JWT_SECRET | Yes | Auth token signing key |
ENVIRONMENT | No | development (default) or production |
AI_PROVIDER | No | ollama (default), claude, openai |
AI_MODEL | No | Model name (e.g. llama3.1:8b) |
AI_BASE_URL | No | Provider base URL |
AI_API_KEY | No | API key (blank for Ollama) |
GOOGLE_CLIENT_ID | Prod only | Google OAuth |
GOOGLE_CLIENT_SECRET | Prod only | Google OAuth |
FRONTEND_URL | Prod only | Public frontend URL for CORS |
APP_URL | Prod only | Public backend URL for OAuth redirect |
FRONTEND_BASE_URL | Prod only | Used in email digest links (defaults to localhost:3000) |
RESEND_API_KEY | Optional | Enables weekly email digests (resend.com) |
EMAIL_FROM | No | Sender address for digest emails |
STRIPE_SECRET_KEY | Optional | Enables donation checkout (Stripe dashboard) |
STRIPE_PUBLISHABLE_KEY | Optional | Stripe frontend key |
STRIPE_WEBHOOK_SECRET | Optional | Stripe webhook signature verification |
Common_Ground/
├── app/
│ ├── api/
│ │ ├── legislation_routes.py # Bill CRUD, search, analyze, perspectives, voting, news, pipeline SSE
│ │ ├── councilmember_routes.py # Council member profiles + district GeoJSON proxy
│ │ ├── auth_routes.py # Google OAuth + dev login
│ │ ├── user_routes.py # Bill tracking, preferences, digest trigger
│ │ ├── metrics_routes.py # Site-wide metrics
│ │ └── donation_routes.py # Stripe checkout + webhook
│ ├── integrations/
│ │ ├── legistar.py # Legistar REST client (non-Philly cities)
│ │ └── legistar_scraper.py # Playwright scraper (Philadelphia)
│ ├── models/
│ │ └── __init__.py # SQLAlchemy ORM models
│ ├── services/
│ │ ├── ai_provider.py # Plug-and-play AI abstraction (auto-starts Ollama)
│ │ ├── bill_research_service.py # Summary, impact, tags analysis
│ │ ├── email_service.py # Weekly digest email builder + sender (Resend)
│ │ ├── legislation_service.py # Ingestion, search, auto-tagging
│ │ ├── news_service.py # Google News RSS fetcher
│ │ ├── perspectives_service.py # 17 perspective prompts + generation
│ │ └── councilmember_service.py # Playwright scraper for phlcouncil.com
│ ├── auth.py # JWT + tier enforcement
│ └── config.py # Pydantic settings
├── frontend/
│ ├── app/
│ │ ├── page.tsx # Home — bill feed with filters + metrics strip
│ │ ├── legislation/ # Bill browser + detail + perspectives + news
│ │ ├── councilmembers/ # Council member list + detail pages
│ │ ├── contexts/
│ │ │ └── pipeline-context.tsx # Global SSE pipeline state (persists across navigation)
│ │ ├── layout.tsx # Root layout — wraps app with PipelineProvider
│ │ ├── my-bills/ # Saved bills list (requires login)
│ │ ├── donate/ # Donation page + success page
│ │ ├── dashboard/ # Metrics dashboard (dev tier)
│ │ ├── profile/ # User profile + digest toggle
│ │ └── admin/ # Ingestion, pipeline, utilities panel
│ ├── components/
│ │ ├── Navbar.tsx
│ │ ├── PerspectivesPanel.tsx # Tally + expandable perspective cards
│ │ └── DistrictMap.tsx # Leaflet map — single district or full city
│ └── lib/
│ ├── api.ts # Typed API client
│ └── auth.ts # JWT helpers
├── alembic/ # Database migrations
├── main.py # FastAPI entry point
└── .env
ENVIRONMENT=production and DEBUG=falseAPP_URL, FRONTEND_URL, FRONTEND_BASE_URL to your public domainsJWT_SECRET (python -c "import secrets; print(secrets.token_hex(32))")AI_PROVIDER and AI_API_KEY for production model (or use Ollama locally for the data pipeline)subscription_tier = 'dev' in the DBnext.config.ts remote patterns for any additional image domainsRESEND_API_KEY and EMAIL_FROM to enable weekly digestsSTRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET to enable donationsPOST /api/donations/webhook) in the Stripe dashboardBill ingestion and AI analysis are fully manual — triggered from the admin panel or via publish.ps1. The production workflow is:
1. Run local ingest from admin panel (scrapes Legistar for new bills)
2. Run local AI analysis (Ollama enriches new bills)
3. Litestream syncs the enriched DB to Backblaze B2 automatically
4. Run `railway redeploy` — Railway restores from B2 on startup
This design keeps AI costs at zero during early growth. See GETTING_STARTED.md → Deployment for the full setup.