Metadata-Version: 2.4
Name: letscomplai
Version: 0.1.1
Summary: Python SDK for LetsComplai PII/PHI redaction and compliance gateway
License-Expression: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: responses>=0.18.0; extra == "dev"
Requires-Dist: build>=1.2.0; extra == "dev"
Requires-Dist: twine>=5.0.0; extra == "dev"
Dynamic: license-file

# LetsComplai Python SDK

Python SDK for the LetsCompl.ai PII/PHI redaction engine and compliance API gateway.

## Installation

```bash
pip install letscomplai
```

## Usage

### Local Redaction

```python
from letscomplai import redact

payload = {
    "name": "The patient John Doe was diagnosed.",
    "email": "test@example.com",
    "ssn": "000-12-3456",
}

result = redact(payload)
print(result["redacted"])
# {
#     "name": "patient [REDACTED_NAME] was diagnosed.",
#     "email": "[REDACTED_EMAIL]",
#     "ssn": "[REDACTED_SSN]"
# }
print(result["hits"])  # ['SSN', 'EMAIL', 'NAME']
```

### Remote Evaluation

```python
from letscomplai import LetsComplaiClient

client = LetsComplaiClient(api_key="your-api-key")

result = client.evaluate({
    "patient_notes": "Mr. John Smith has a history of asthma.",
})
print(result)
# result["verdict"]: "approved" | "blocked" | "error"
# result["policyVerdict"]: what the policy actually decided, independent of enforcement mode
# result["enforcement"]: "ENFORCE" | "MONITOR" — the mode in effect for this call
```

### Enforcement mode

Every evaluation response carries three related fields:

| Field | Meaning |
|---|---|
| `verdict` | What your code should do: `approved`, `blocked`, or `error`. |
| `policyVerdict` | What the policy actually decided, independent of enforcement mode. |
| `enforcement` | The mode in effect for this call: `ENFORCE` or `MONITOR`. |

Under `ENFORCE`, `verdict` and `policyVerdict` are always identical.

In monitor mode, a blocking policy verdict does not stop your action: `verdict`
is `approved` while `policyVerdict` is `blocked`. The evaluation, citation, and
audit record are produced exactly as they would be under enforcement.

**Check `policyVerdict`, not `verdict`, when you want to know what your policy
decided.** A monitor-mode response with `verdict: "approved"` does not mean the
payload passed your rules. For example, a workspace configured for `MONITOR`
whose policy would have blocked the payload returns:

```python
{"verdict": "approved", "policyVerdict": "blocked", "enforcement": "MONITOR"}
```

### Monitor mode and gateway availability

Monitor mode is evaluated server-side. If the gateway is unreachable, the SDK
never learns your workspace's mode, so `guard_action`'s `unavailable` option
governs the outcome — and its default is `"deny"`.

To keep a monitor-mode integration from denying actions during a network
failure, set `unavailable="allow"` explicitly:

```python
client.guard_action(payload, lambda: do_the_thing(), unavailable="allow")
```

Quota exhaustion is also unaffected by enforcement mode: an account over its
evaluation limit receives HTTP 429 in both modes.

By default, `evaluate()` redacts PII/PHI **locally, before the payload is sent to the gateway** — values matching our recognized patterns are stripped before anything leaves your process. Redaction is pattern-based, not exhaustive: a value in a format outside our documented patterns (e.g. an unhyphenated SSN, IBAN, DOB, address, or non-US phone number) will still be sent as-is. See [Data Handling](https://www.letscompl.ai/docs?doc=data-handling) for the full pattern list and known gaps. Pass `redact=False` as an explicit opt-out to send the raw, unredacted payload:

```python
client.evaluate(payload, redact=False)  # explicit opt-out — sends the raw payload
```

`evaluate()` also accepts `rules` (a list of rule keys to restrict evaluation to a subset instead of the full workspace policy) and `pinned_versions` (a dict mapping ruleset key to a historical version number, for audit replay):

```python
client.evaluate(payload, rules=["ftc_budget_cap"], pinned_versions={"ftc_budget_cap": 2})
```

Passing `rules` makes the response's `policyScope` `"filtered"` instead of `"full"`, with `evaluatedRules` listing exactly which rule keys ran. **A `"filtered"` response is not a complete compliance decision** — it only reflects the requested subset, not the workspace's full active policy. Omit `rules` to evaluate everything.

### Guarding an action

```python
from letscomplai import LetsComplaiClient

client = LetsComplaiClient(api_key="your-api-key")
result = client.guard_action(
    {"action": "payout", "amount": 1200},
    lambda: payments.create_payout(amount=1200),
)
```

`guard_action` raises `ComplianceBlockedError` if the verdict is `blocked`, and (by default) `ComplianceUnavailableError` if the gateway is unreachable, before the action callable runs.

### Async client

```python
from letscomplai import AsyncLetsComplaiClient

client = AsyncLetsComplaiClient(api_key="your-api-key")
result = await client.evaluate({"action": "payout", "amount": 1200})
await client.aclose()
```

`AsyncLetsComplaiClient` mirrors `LetsComplaiClient`'s `evaluate`/`guard_action`/`redact_local` methods for asyncio applications.

> LetsCompl.ai is a technical enforcement tool, not a legal compliance service. Verdicts do not constitute legal advice.

For timeout, fail-open/fail-closed, retry, SLA, and local-enforcement behavior, see [docs/operations/client-availability.md](https://github.com/akventures-ai/letscomplai/blob/main/docs/operations/client-availability.md).
