Metadata-Version: 2.4
Name: wedge-health
Version: 0.3.20
Summary: Python SDK for the authenticated Wedge Health clinical automation API
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: google-auth<3,>=2.29
Requires-Dist: requests<3,>=2.31
Provides-Extra: display
Requires-Dist: jinja2<4,>=3.1; extra == "display"
Requires-Dist: pandas<4,>=2; extra == "display"

# Wedge Health Python SDK

The supported application interface is intentionally small. Select the eCW
health system by its friendly connection label, then call actions on that
scoped object:

```python
import os

from wedge import Wedge

client = Wedge(api_key=os.environ["WEDGE_API_KEY"])
opsam = client.ecw.use("opsam-production")

response = opsam.read_patient(
    patient_id="YOUR_PATIENT_ID",
    id_type="mrn",
)
```

The older `from wedge_api_client import WedgeApiClient` import remains
available for compatibility.

The SDK provides a small, typed interface to the authenticated Wedge Health
clinical automation API. Access is provisioned only to approved callers. The
normal client sends one `X-Wedge-API-Key` to the Wedge access broker. It does
not use Google Application Default Credentials, mint JWTs, or give callers any
Google Cloud access. Request and response bodies, patient identifiers, API
keys, and eCW credentials are never logged. The client performs no automatic
HTTP retries.

The one-key staging broker is live at
`https://wedge-api-staging-access-vl5zuw7zfa-wl.a.run.app`, which is the SDK's
fixed default. Connection inventory is deployment-specific. A clinical call
becomes usable only after an administrator creates the selected connection and
grants the calling key its label and exact action.

Infrastructure authorization is not operational approval. Writes and durable
exports must not be invoked without action-specific approval and the required
idempotency controls.

The default HTTP timeout is 10 seconds to establish a connection and 600
seconds to receive a response. The longer response window accommodates a cold
eCW login, including email 2FA, before the requested action runs. Internal
relays stop earlier so the API can still return a sanitized timeout or
unknown-outcome response. Callers may pass `timeout_seconds=` for a shorter
workflow-specific limit, but shortening it can abandon a legitimate cold
reauthentication from the caller's perspective. The SDK never retries an HTTP
request automatically.

## Install

Python 3.11 or newer is required. The canonical distribution name is
`wedge-health`:

```console
python3 -m pip install wedge-health
```

The `wedge-ai` distribution is a compatibility installer. It contains no
import packages and depends on the matching `wedge-health` release, so either
installation name provides the same supported import:

```python
from wedge import Wedge
```

Prefer `wedge-health` in new dependency files and documentation. From this
repository checkout:

```console
python3 -m venv .venv-wedge-api
./.venv-wedge-api/bin/python -m pip install --upgrade pip
./.venv-wedge-api/bin/python -m pip install -e ./clients/python
```

From another private Git repository, pin the dependency to a reviewed commit
instead of a moving branch:

```text
wedge-health @ git+https://github.com/wedge-health/wedge-api.git@REVIEWED_COMMIT_SHA#subdirectory=clients/python
```

The package uses `requests>=2.31,<3`. It temporarily retains
`google-auth>=2.29,<3` only for the explicitly enabled legacy transport; normal
one-key callers do not initialize it.

## Configure approved access

Set the restricted key supplied by a Wedge API administrator in the process
environment:

```console
export WEDGE_API_KEY="..."
```

No base-URL setting is required: the SDK uses the live staging broker shown
above. Normal callers do not install or run `gcloud`, configure Application
Default Credentials, or receive access to Wedge's Google Cloud project.

Never put credentials in source code, committed environment files, command-line
arguments, logs, tickets, or documentation.

## Create an eCW connection once

An administrator supplies credentials once during onboarding. They are sent
over HTTPS to the broker, validated, and stored by the managed backend. They
are not retained by the returned Python object:

```python
import os

from wedge import Wedge

client = Wedge(api_key=os.environ["WEDGE_API_KEY"])
opsam = client.ecw.connect(
    label="opsam-production",
    url=os.environ["OPSAM_ECW_URL"],
    username=os.environ["OPSAM_ECW_USERNAME"],
    password=os.environ["OPSAM_ECW_PASSWORD"],
    two_factor_method="email",
    email_username=os.environ["OPSAM_ECW_EMAIL_USERNAME"],
    email_password=os.environ["OPSAM_ECW_EMAIL_PASSWORD"],
)
```

`url` accepts either an eCW hostname such as `practice.ecwcloud.com` or its
full HTTPS origin. Hostname-only values are normalized to HTTPS; HTTP URLs,
paths, and hosts outside `ecwcloud.com` are rejected.

Creation is durable before the first browser warm-up. If `opsam.status` is
`"reauthentication_required"`, keep the returned scoped object and retry
`opsam.ensure_ready()` after resolving login or 2FA. Do not
resubmit the same label as a new connection.

The returned scoped object exposes only its stable friendly label and initial
status. Internal connection identifiers stay behind the API boundary. Normal
requests route by `opsam-production`.

For authenticator-based 2FA, use `two_factor_method="authenticator"` and pass
`totp_secret=` instead of either email field. The two modes cannot be mixed.

Before a large batch, explicitly verify the selected connection without
reading a patient:

```python
opsam = client.ecw.use("opsam-production")
status = opsam.ensure_ready()
```

This preflight runs a non-PHI authenticated-session probe. If the session is
stale or invalid, the backend reauthenticates it immediately and requires a
successful confirming probe before returning. Calls for the same connection
are serialized so a batch cannot race that refresh. The older
`client.ecw.warm("opsam-production")` spelling remains available as a
compatibility alias.

## Read one patient

Assign the identifier supplied by the calling application's approved workflow:

```python
import os

from wedge import Wedge

client = Wedge(api_key=os.environ["WEDGE_API_KEY"])
opsam = client.ecw.use("opsam-production")

response = opsam.read_patient(
    patient_id="YOUR_PATIENT_ID",
    id_type="mrn",
)
```

An application key can use only the connection labels and exact actions its
administrator assigned. Guessing another customer's label returns the same
generic not-found response as a nonexistent label. Reuse one client instance
and call `client.close()` during shutdown.

## Call another action

Use the human-readable [`API_REFERENCE.md`](../../API_REFERENCE.md). Every
reviewed action has its purpose, complete input table, defaults, constraints,
permission, idempotency rules, response guidance, and a safe Python example.
You do not need to read the raw OpenAPI JSON to use the SDK.

Every reviewed action has a direct, keyword-only method on the selected
connection. Unknown parameters and positional misuse fail closed before an HTTP
request. For an action whose contract requires idempotency, generate and
durably retain one canonical UUID and pass it as `operation_id`. The method
automatically sends the same value as `Idempotency-Key`; an explicitly supplied
`idempotency_key` must match. Reuse that value only to reconcile the same
unknown outcome.

```python
operation_id = "YOUR_OPERATION_UUID"

result = opsam.retrieve_document(
    patient_id="YOUR_PATIENT_ID",
    id_type="mrn",
    document_type="registration",
    operation_id=operation_id,
)
```

## Upload a patient document

Use `upload_patient_document` for a PDF, PNG, or JPEG. The SDK privately asks
Wedge for a five-minute, one-object GCS upload URL, uploads the file directly
to GCS, then files and verifies it in eCW. The file never passes through the
Wedge API. `upload_patient_document_file` remains a compatibility alias, but
new code should use `upload_patient_document`.

```python
result = opsam.upload_patient_document(
    patient_id="YOUR_PATIENT_ID",
    document_bytes=pdf_bytes,
    document_filename="intake.pdf",  # .pdf, .png, .jpg, or .jpeg
    document_folder="Patient Documents",
    dry_run=False,
    confirm=True,
)
```

`document_bytes` must be a PDF, PNG, or JPEG no larger than 10 MiB, and its
filename extension must match its actual contents. The SDK never returns
the temporary GCS URL. Do not add logging around the SDK's internal upload
steps, since the signed URL is a short-lived bearer credential. On a verified
eCW save, Wedge deletes the intake object; the one-day GCS lifecycle rule is a
fallback for abandoned uploads.

## Safe errors

All public exceptions derive from `WedgeApiError`. HTTP failures expose only
reviewed, non-PHI metadata: `status_code`, `code`, `origin`, a sanitized
`request_id`, and a bounded `retry_after_seconds` when the server supplied a
safe retry delay. Response bodies, credentials, private error messages, and
request bodies are never retained:

```python
from wedge import WedgeApiError

try:
    result = opsam.lookup_schedule(
        date="YOUR_APPOINTMENT_DATE",
        resource_id="YOUR_RESOURCE_ID",
    )
except WedgeApiError as error:
    status_code = error.status_code
    code = error.code
    origin = error.origin
    request_id = error.request_id
    retry_after_seconds = error.retry_after_seconds
```

`origin` is present only when the API proved the failure boundary, such as
`"api_gateway"`, `"access_broker"`, `"browser"`, `"wedge_admission"`,
`"ecw_portal"`, or `"ecw_readiness"`. An unknown HTTP 429 is never labeled as
an eCW portal throttle. Logging the fields shown above is safe. Do not log the
request, response, exception locals, client instance, or environment.

## Multiple health systems and limited application keys

An administrator can own many named connections and call reviewed actions on
any ACTIVE connection it owns, while issuing a regular key
that can see only one of them:

```python
opsam_key = client.api_keys.create(
    label="opsam-application",
    connections=["opsam-production"],
    actions=["read_patient", "retrieve_document"],
)
```

The returned application key cannot list, select, or call the administrator's
other connections. Regular application keys cannot create, rotate, or disable
connections.

An administrator can list every API key it owns, along with each assigned
connection and exact action grants. The response never includes a key's
plaintext value, digest, or eCW credentials:

```python
key_inventory = client.api_keys.list()
```

An administrator can inspect all owned labels without retrieving credential
secrets:

```python
inventory = client.ecw.connections.list_admin()
```

The returned value is a normal dictionary:

```python
{
    "connections": [
        {
            "label": "opsam-production",
            "status": "active",
            "credentials": {
                "ecw_hostname": "practice.ecwcloud.com",
                "username_masked": "o****r",
                "two_factor_method": "email",
                "credentials_updated_at": "2026-07-25T22:00:00Z",
            },
        },
        {"label": "legacy-production", "status": "disabled", "credentials": None},
    ]
}
```

Each entry includes `label`, `status`, and either `credentials=None` for a
legacy record or safe metadata containing `ecw_hostname`, a fixed-shape
`username_masked`, `two_factor_method`, and `credentials_updated_at`. The
timestamp reflects creation or credential rotation, not session warm-up.
Complete usernames, passwords, email credentials, TOTP secrets, internal
connection IDs, and secret references are never returned. Application keys
cannot call `list_admin()`; their normal `list()` method still returns only
assigned labels and statuses.

Credential rotation supplies a complete replacement bundle, including the eCW
URL:

```python
client.ecw.rotate_credentials(
    "opsam-production",
    url=os.environ["OPSAM_ECW_URL"],
    username=os.environ["OPSAM_ECW_USERNAME"],
    password=os.environ["OPSAM_ECW_PASSWORD"],
    two_factor_method="authenticator",
    totp_secret=os.environ["OPSAM_ECW_TOTP_SECRET"],
)
```

The previous secret version is not modified, and neither version is returned
by the API.

## Legacy direct-Gateway mode

The old Google API Gateway transport remains temporarily available only when
code explicitly passes `legacy_dual_auth=True`. It may use Google ADC and is
not the supported public API experience. New applications should not enable
it.

## Run tests

Tests use injected fake HTTP sessions and never contact Google, the Gateway, or
either clinical portal:

```console
cd clients/python
../../.venv-wedge-api/bin/python -m unittest discover -s tests -v
```
