govpal-reach 0.2.4
Civic engagement library: address → reps → delivery
License: MIT
Requires Python: >=3.12

# GovPal Reach

Civic engagement library: discover representatives from an address and deliver messages via email or web forms. Resistbot-like functionality with modular, pluggable architecture.

## Project overview

GovPal Reach maps **address → representatives** (federal, state, local) and supports **message delivery** via email (Postmark) or web forms (Playwright). Users onboard via SMS/OTP (Twilio Verify, or Plivo).

- **Discovery:** Geocode address (Census Geocoder), then look up reps (Google Civic, Open States, LA local).
- **Delivery:** Send messages via email or submit web forms; PGQueuer for durable queue.
- **Identity:** User profiles, PII encryption, SMS OTP (Twilio primary, or Plivo).
- **Orchestrator:** High-level API composing discovery and delivery.

## Quick start

### 1. Install

Requires [uv](https://docs.astral.sh/uv/) (install: `curl -LsSf https://astral.sh/uv/install.sh | sh`).

```bash
uv sync
```

### 2. Configure environment

Copy `.env.example` to `.env` and fill in values. See [SETUP.md](SETUP.md) for how to obtain credentials.

```bash
cp .env.example .env
# Edit .env with your credentials
```

**Email delivery mode (optional):** `GP_DELIVERY_MODE` defaults to **`capture`** (no real sends) so new installs don't send email until you explicitly enable it. Set to `live` to send via Postmark.

- **`capture`** (default) – No real sends; emails are recorded in memory for inspection (e.g. tests, CI).
- **`redirect`** – All recipients are rewritten to the comma-separated addresses in `GP_TEST_RECIPIENTS`; subject is tagged with `[TO: original]`.
- **`live`** – Production behavior via Postmark (set explicitly to send real email).

### 3. Run a simple example

The library is intended to be imported by a host app. Example usage:

```python
from govpal.orchestrator.api import discover_reps_for_address, send_message_to_representatives

# Discover reps for an address
reps = discover_reps_for_address("123 Main St, Los Angeles, CA 90012")

# Send a message to one or more representatives (library adds preamble + closing)
# body = middle content only; subject, profile, phone, and list of reps
results = send_message_to_representatives(
    "Your message body here.",
    "Subject line",
    profile,  # constituent profile (first_name, last_name, email, full_address_for_display)
    phone,
    reps,     # list of rep dicts with contact_email
)
# results = [(rep, success), ...]
```

### 4. Database schema (Postgres + Alembic required)

GovPal Reach requires **PostgreSQL** and **Alembic** for Identity (OTP), queue path, and production use. There is no non-Alembic schema path.

1. Set `DATABASE_URL` in your environment (e.g. in `.env`).
2. In your host app's Alembic setup, add the library's versions path to `version_locations` so one `alembic upgrade head` applies both your app's and GovPal Reach's schema. Example in `env.py`:

   ```python
   from govpal.alembic import get_versions_path
   reach_versions = str(get_versions_path())
   # Prepend or append to version_locations (e.g. [reach_versions, "alembic/versions"])
   config.set_main_option("version_locations", ...)
   ```
3. Run migrations: `alembic upgrade head` (or your app's equivalent, e.g. `python run_migrations.py`).

Migrations are **idempotent**: after updating the govpal-reach library, running `alembic upgrade head` again applies any new schema changes and is safe when nothing has changed. You do not need to check whether the library had schema updates.

## Modules

| Module       | Responsibility                                      |
|--------------|-----------------------------------------------------|
| **Discovery**| Address → reps (Google Civic, Open States, Census)  |
| **Delivery** | Email (Postmark), web forms (Playwright), queue     |
| **Identity** | Users, PII, SMS OTP (Twilio or Plivo)               |
| **Orchestrator** | High-level API: discovery, send_message_to_representatives (preamble + closing) |

## How to run the system

- **As a library:** Import `govpal` in your app; configure env vars; use orchestrator API.
- **Production queue path:** For durable delivery, use the queue path: **create_message** → optional **update_message** → **enqueue_messages_for_address**. Set `GOVPAL_USE_REAL_QUEUE=1` and run the **GovPal delivery worker** so jobs are processed: `uv run pgq run govpal.delivery.worker:create_pgqueuer --max-concurrent-tasks 2`. See [SETUP.md](SETUP.md) (PGQueuer section) for create_message/update_message usage and worker run command.
- **Railway:** Deploy with Postgres; run the queue worker as a separate process or worker dyno; env vars set in Railway dashboard.

## Using in another project

Install GovPal Reach as a dependency from **PyPI** (recommended for released versions):

```bash
# With uv (recommended)
uv add "govpal-reach>=0.2.3"

# Or with pip
pip install govpal-reach
```

To pin to a specific GitHub tag (e.g. before a version is on PyPI or to try a release tag):

```bash
uv add "govpal-reach @ git+https://github.com/YOUR_ORG/govpal-reach@v0.2.3"
```

Replace `YOUR_ORG` and the tag (`v0.2.3`) as needed. The **import name** is `govpal`:

```python
from govpal.orchestrator.api import discover_reps_for_address, create_message, update_message, enqueue_messages_for_address
```

## How to test

```bash
# Unit tests (runs in project .venv)
uv run pytest

# With coverage
uv run pytest --cov=govpal --cov-report=term-missing

# Integration tests (multi-module flows; no DB/API needed for composed-flow tests)
uv run pytest -m integration
# With DATABASE_URL and schema applied, the worker integration test also runs; without it that test is skipped.

# Linter
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
```

Full test suite (lint + pytest + coverage): see [TEST_PLAN_SUMMARY.md](docs/TEST_PLAN_SUMMARY.md).

## Documentation

- [RESEARCH_SUMMARY.md](docs/RESEARCH_SUMMARY.md) – APIs, data sources, architecture research
- [PLAN_SUMMARY.md](docs/PLAN_SUMMARY.md) – Implementation plan, beads, schema, modules
- [TEST_PLAN_SUMMARY.md](docs/TEST_PLAN_SUMMARY.md) – Testing strategy per phase
- [SETUP.md](SETUP.md) – External services, credentials, permissions; **Releasing**: push a `v*` tag and CI publishes to PyPI (Trusted Publishing), see section 13

## License

MIT