factcircuit
FactCircuit는 불변 출처와 단계별 검증, 제한된 검색, 재현 가능한 평가를 제공하는 감사 가능한 주장·증거 검증 루프입니다
필라델피아 시의회 법안을 쉬운 영어로 추적합니다—요약·발의자·투표 기록·17명 의원 프로필 제공, 무료·광고 없음
Track Philadelphia City Council legislation in plain English — bill summaries, sponsors, voting records and 17 council member profiles. Free, independent, no ads.
데이터Python · TypeScriptGitHub @cainesventures온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
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.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.