openjutsu
영상에서 원클릭으로 인물 교체, ComfyUI·MiniMax H3 기반 오픈소스 로컬 힉스필드 겐주츠 대안, FLUX.3 Klein 재조명 지원
시카고 시의원들이 구역에 전달하는 소식을 추적하고, 구역·교통·주택·지역 문제 뉴스레터를 검색할 수 있습니다
Track what Chicago aldermen are communicating to their wards. Searchable database of newsletters covering zoning, transit, housing, and community issues.
미디어GitHub @StrongTownsChicago온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Searchable archive of newsletters from Chicago aldermen. Built for Strong Towns Chicago.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Searchable archive of newsletters from Chicago aldermen. Built for Strong Towns Chicago.
/ward/N) with the alderman's contact info and newsletters, plus a ward directory (/wards)/reports) counting how many wards' newsletters mentioned each topicCore Tables:
sources - Aldermen and officials (name, ward, email, archive URLs)email_source_mappings - Pattern matching for email → source identificationnewsletters - Full newsletter content with LLM-extracted metadata (topics, summary)Notification Tables:
user_profiles - User settings and notification preferencesnotification_rules - User-defined alert criteria (topics, search terms, wards)notification_queue - Pending notifications awaiting deliverynotification_history - Audit log of sent emailsFull schema details: See backend/SCHEMA.md
Newsletters are stored in dual formats in the newsletters table:
plain_text - Primary format used for search indexing, LLM processing (topics, summaries), and notification keyword matchingraw_html - Preserves formatting for frontend displayBoth formats are independently sanitized during ingestion to remove tracking links and privacy-sensitive content.
cd backend
uv sync
# Create .env with:
# GMAIL_ADDRESS, GMAIL_APP_PASSWORD
# SUPABASE_URL, SUPABASE_SERVICE_KEY
# ENABLE_LLM=true, OLLAMA_MODEL=gpt-oss:20b
# Process unread Gmail newsletters (queues notifications if ENABLE_NOTIFICATIONS=true)
uv run python -m cli.process_emails
# Scrape all aldermen with newsletter_archive_url set
uv run python -m cli.process_scraped_newsletters
# Scrape specific source (source_id, archive_url, optional limit)
uv run python -m cli.process_scraped_newsletters 1 "https://..." 10
# Reprocess newsletters with current LLM prompts
uv run python -m cli.process_llm_metadata --latest 10
# Reapply privacy sanitization to existing newsletters
uv run python -m cli.reprocess_newsletters_privacy --all --update --quiet
# Refresh the /analytics dashboard, ward summaries and /reports: compute, check
# http://localhost:4321/analytics, then commit frontend/public/analytics-data/ and frontend/src/generated/
# and push; the deploy publishes them. The JSON files are public.
# (fetches only newsletters newer than the local snapshot in backend/data/; --offline skips the database,
# --full-refresh rebuilds it from every newsletter: needed once on a fresh checkout; run it when the site is quiet)
uv run python -m cli.compute_analytics
# Test rule matching against recent newsletters (dry run)
uv run python -m cli.try_rule_matcher
# Test and queue notifications
uv run python -m cli.try_rule_matcher --queue
# Send daily digest (dry run)
uv run python -m cli.process_notification_queue --daily-digest --dry-run
# Send today's daily digest
uv run python -m cli.process_notification_queue --daily-digest
# Send specific date's digest
uv run python -m cli.process_notification_queue --daily-digest --batch-id 2026-01-21
# Generate weekly reports for active topics
uv run python -m cli.process_weekly_reports
# Queue notifications for generated reports
uv run python -m cli.weekly_notification_queue
# Send weekly digest emails
uv run python -m cli.process_notification_queue --weekly-digest
# Backend tests
uv run python -m unittest discover -s tests
# Backend database tests, against a running local Supabase stack (see CLAUDE.md)
eval "$(bash ../scripts/supabase-test-env.sh)"
uv run python -m unittest discover -s tests/db_integration -t .
# Frontend tests
cd ../frontend
npm run test # Unit tests
npm run test:e2e # E2E tests (Playwright), on the local Supabase stack: npx supabase start --workdir .. (Docker)
npm run lint
# From the repo root: CI's gates, in CI's order (no argument runs both)
bash scripts/check.sh [backend|frontend]
# Once per clone: fast checks on staged files before each commit
git config core.hooksPath .githooks
cd frontend
npm install
npm run dev
# Deploy: push to GitHub, connect to Cloudflare Pages
Option A: Ollama (local inference)
curl -fsSL https://ollama.com/install.sh | sh
ollama pull gpt-oss:20b
# Use with: --model gpt-oss:20b (default) or --model ollama:gpt-oss:20b
Option B: OpenAI (cloud)
# Add to backend/.env:
# OPENAI_API_KEY=sk-...
# LLM_MODEL=openai:gpt-5
backend/
├── ingest/ # Email and web scraping ingestion
├── processing/ # LLM topic extraction and summarization
├── notifications/ # User alerts and daily digest delivery
├── cli/ # Command-line entry points (ingestion, digests, reprocess, migrate, analytics)
├── tests/ # Test suite
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ ├── db_integration/ # Tests against the local Supabase stack
│ └── fixtures/ # Test data and fixtures
├── config/ # Privacy patterns configuration
├── shared/ # Shared database client and date helpers
└── migrations/ # Database migration SQL files
frontend/
├── src/pages/ # Astro pages (index, search, wards, ward pages, preferences, newsletter detail)
├── src/pages/api/ # API routes for notification management
├── src/lib/ # Supabase client
└── tests/ # Frontend unit tests
Two LLM calls per newsletter for topic extraction and summarization. Supports Ollama (local) and OpenAI (cloud) via provider-prefixed model strings. See backend/processing/llm_client.py for provider dispatch and backend/processing/llm_processor.py for topic definitions.
The system supports two types of notifications:
Users configure these preferences via the frontend. Email ingestion automatically queues daily notifications. Weekly reports are generated via a scheduled process.
Key files:
backend/notifications/rule_matcher.py - Matching logicbackend/processing/weekly_report_generator.py - Weekly report LLM synthesisbackend/notifications/email_sender.py - Email delivery via Resendfrontend/src/pages/preferences.astro - User preference managementfrontend/src/pages/unsubscribe.astro - One-click unsubscribeNewsletter content is automatically sanitized to remove tracking links, unsubscribe URLs, and sensitive content. Privacy rules are defined as Python constants in backend/config/privacy_patterns.py with URL patterns, text patterns, and CSS selectors.
Implementation: backend/ingest/email/email_parser.py:sanitize_content()
Tests: backend/tests/test_sanitization*.py and test_user_cases.py
.github/workflows/email_ingestion.yml).github/workflows/send_notifications.yml).github/workflows/send_weekly_digests.yml)backend/docs/LOCAL_LLM_PROCESSING.md)Search indexing: The production URL is set as site in frontend/astro.config.mjs. The build emits /sitemap-index.xml, which references the static-route sitemap and the dynamic /sitemap-newsletters.xml (one entry per newsletter) and /sitemap-wards.xml (one entry per ward). Submit https://chicago-newsletter-aggregator.open-advocacy.com/sitemap-index.xml in Google Search Console; a Domain property on open-advocacy.com covers the subdomain. Requests on any other host (*.pages.dev, preview deployments) get X-Robots-Tag: noindex from frontend/src/middleware.ts.
GitHub Actions: Both workflows support manual triggering and require secrets configured in repository Settings → Secrets (see workflow files for details).
Backend (.env):
# Gmail IMAP
GMAIL_ADDRESS=
GMAIL_APP_PASSWORD=
# Supabase
SUPABASE_URL=
SUPABASE_SERVICE_KEY=
# LLM Processing (optional)
ENABLE_LLM=true
LLM_MODEL=gpt-oss:20b # Supports provider prefix: openai:gpt-5, ollama:gpt-oss:20b
OLLAMA_MODEL=gpt-oss:20b # Legacy fallback (LLM_MODEL takes precedence)
OPENAI_API_KEY=sk-... # Required when using openai: provider prefix
# Notifications (optional)
ENABLE_NOTIFICATIONS=true
RESEND_API_KEY=re_xxxxx
[email protected]
FRONTEND_BASE_URL=yourbaseurl # Optional
UNSUBSCRIBE_SECRET_KEY=your_secret_key # For signing unsubscribe JWTs
# Privacy (optional)
PRIVACY_STRIP_PHRASES= # Comma-separated phrases to redact
Frontend (.env):
PUBLIC_SUPABASE_URL=
PUBLIC_SUPABASE_ANON_KEY=
PUBLIC_ENABLE_NOTIFICATIONS=
# Server-side Only
SUPABASE_SERVICE_KEY=
UNSUBSCRIBE_SECRET_KEY=