Metadata-Version: 2.4
Name: oak-domain-legal-intake
Version: 0.25.1
Summary: Legal-intake (access-to-justice) domain plugin for the OakQuant timber substrate.
Author-email: Pumulo Sikaneta <pumulo@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: cambium-ai>=0.2.7
Requires-Dist: httpx>=0.24
Requires-Dist: timber-common>=1.1.2
Provides-Extra: dev
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: xhtml2pdf>=0.2.16; extra == 'dev'
Description-Content-Type: text/markdown

# oak-domain-legal-intake

**An access-to-justice legal-intake, triage, and referral domain for the OakQuant
platform — the first non-investments domain built on Timber's plugin system, and its
altruistic B2B2C pillar.**

[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
[![Status: Live in production](https://img.shields.io/badge/status-live_in_production-brightgreen.svg)](#capabilities)
[![PyPI](https://img.shields.io/pypi/v/oak-domain-legal-intake.svg)](https://pypi.org/project/oak-domain-legal-intake/)

> **Live in production across all three layers.** A legal-aid or pro-bono organization gets
> a full intake-to-referral workflow — take a matter in, model the parties, triage and route
> it, track deadlines and activities, screen eligibility, match it to real legal-aid programs
> and crisis resources, assemble applicant-facing documents (including translations), and hand
> the applicant a printable referral packet or wallet card — plus a public, no-login "find
> help" screener anyone can use. It ships as a **discoverable plugin** that registers into
> timber/grove/acorn at startup with **zero edits to any sibling repo**, and is **fully
> ring-fenced by a per-enterprise `legal_intake` capability**: a tenant that isn't provisioned
> for it never sees a nav item, a home widget, or an endpoint.

## What it is

`oak-domain-legal-intake` is a *discoverable* Timber domain plugin. Installing it adds a
legal-intake capability to an OakQuant deployment; leaving it out is a no-op. Like every
`oak-domain-*` package it is fully **additive and opt-in** — it ships its own models,
services, HTTP surface, agent tools, and UI contributions and wires them into the platform's
shared registries at startup, with **no edits to the core Timber, Grove, or Acorn libraries**.

It was the proving ground for OakQuant's domain-plugin pattern: the first domain built on
Timber's plugin system that is *not* the investments domain, demonstrating that a brand-new,
unrelated domain can plug in cleanly through a single entry point per layer.

### Why legal intake — access to justice

Legal aid and pro-bono organizations triage far more people than they can serve, and the
quality and consistency of that triage decides who gets help. This domain exists to make that
triage **fairer, faster, and more accountable** — and to point every person, served or not,
toward real help. Several commitments are encoded directly into the schema and services rather
than left to policy:

- **Help, not gate.** A disposition *routes* or *refers* a person; it never renders a
  "denial". Eligibility and cost outputs are estimates that point toward help — they never tell
  anyone they are out of options.
- **Data minimization.** Operational fields only; no free-text PII columns. Protected
  attributes (used only by the fairness audit) and sensitive eligibility inputs (income,
  household) are isolated in their own stores and are **never read by routing or the learning
  loop**.
- **Meaningful human review.** Assembled documents and translations always land in
  `staff_review` and are never delivered to an applicant until a human approves them.
- **Not legal advice.** The guidance and conversation surfaces give legal *information* and
  navigation only — explicitly guarded, plain-language, never case strategy or outcome
  predictions.
- **Real data only.** The directory seed and every ingested resource are real national
  authorities, hotlines, and their real contact info — nothing fabricated.
- **Append-only logs.** Event and outcome logs support contestability and audit.

## The plugin shape

This package plugs into all three OakQuant layers through **one entry point per layer**, plus
a generic secrets entry point. Each layer discovers only its own entry-point group and imports
only that entry point's module — so the Timber layer never pulls in Grove/FastAPI or Acorn.

| Layer / concern | Entry-point group | Object | Contributes |
|---|---|---|---|
| **Timber** | `timber.domains` | `oak_domain_legal_intake:LegalIntakeDomain` | 14 ORM models + all domain services, registered under `service_registry.domain("legal_intake")` |
| **Grove** | `grove.domains` | `oak_domain_legal_intake.grove_plugin:LegalIntakeGroveDomain` | FastAPI router mounted at `/api/v3/legal-intake` (grove-key + capability + tenant-isolation gated) |
| **Acorn** | `acorn.domains` | `oak_domain_legal_intake.acorn_plugin:LegalIntakeAcornDomain` | Oracle intake-assistant profile + a legal-safe Rustle persona + ~28 gated agent tools |
| **Secrets** | `grove.secret_specs` | `oak_domain_legal_intake:secret_specs` | optional runtime secrets (CourtListener / USCIS), injected from ranger at grove bootstrap — all `required=False`, so a missing key never blocks startup and grove core names no legal vendor |

Beyond entry points, the timber plugin registers **UI, calendar, and conversation
contributions** into the same shared registry — the Legal Intake nav item, the home-page
widgets (triage queue / recent / quick actions / public link / upcoming), the legal calendar
taxonomy, the domain's calendar-event generators, and per-case Rustle access — each gated to
the `legal_intake` capability, so grove core holds no legal-specific nav, page, or calendar
config.

### How discovery works

The domain advertises itself via `timber.domains`. At startup (`initialize_timber`, **Step
8.5**), Timber discovers the package, instantiates `LegalIntakeDomain`, and calls
`register(ctx)` **once, before table creation** — so the models get their tables created in the
following step. `register(ctx)` takes every runtime dependency (db handle, registries) from the
injected `DomainContext` rather than importing core singletons, keeping the dependency direction
one-way. The Grove and Acorn plugins follow the same shape against their own entry-point groups
and contexts, registered when those apps start.

The Grove router mounts **outside** grove's auth middleware and re-asserts three boundaries on
every request: the `X-Grove-API-Key` service-key check, the per-enterprise `legal_intake`
**capability** gate (absent capability → 404), and strict enterprise **isolation** (reads scoped
to the caller's visible tenants, writes stamped with the caller's tenant, cross-tenant → 404).

## Capabilities

Everything below ships in the package and is live in production. Each capability is reachable
three ways — a Grove endpoint under `/api/v3/legal-intake`, an Acorn agent tool, and (where it
has a UI) a home widget or page — all behind the same capability gate.

### Intake & cases

The instrumentation core: an `IntakeCase` (operational, PII-minimized fields only) with an
append-only `IntakeEvent` log, `StaffDecision` records that capture the human's call *and* any
override reason, and a terminal `Disposition` that routes or refers (never denies). The
`IntakeParty` spine models everyone on a matter — applicant, opposing party, dependents,
advocate, interpreter — linking internal parties to real platform users so routing knows who to
contact. Staff work the queue (`/queue`, `/queues/mine`, admin `/admin/queues`); a served
recipient sees only their own cases through a server-enforced self-help view (`/my-cases`).

### Routing & decisions

A `RoutingSuggestionService` proposes where a matter should go, reaching grove's **shared**
routing spine through the `grove_core` bridge (no grove import). Staff record the decision, route
to a real queue or person, and close with a disposition — each step captured for audit and folded
into the case timeline.

### Activities, deadlines & calendar

`CaseActivity` is the "what happens and when" spine — intake interviews, advocate consults,
hearing prep, hearings, filings, follow-ups, and hard **deadlines**. `ActivityService` can seed a
sensible default workplan for a matter, and a deadline activity is auto-seeded whenever a case's
`key_deadline_at` is known. Scheduled activities for an assigned user are emitted onto the shared
calendar and the home "Upcoming" widget by the domain's own calendar-event generators, with no
grove change.

A separate **deterministic deadline calculator** (`DeadlineService`, verified rules only) computes
statute-of-limitations / hearing / appeal windows conservatively — engineered to **never be
wrong-late**. Reachable at `/deadlines/calculate` and per case.

### Directory & eligibility

An HSDS-aligned referral directory (Open Referral Human Services Data Specification):
`LegalAidProgram` and `SupportResource` rows carry organization / service / location /
eligibility / taxonomy so 211, findhelp, LSC-grantee, and any Open Referral feed are
interchangeable. The directory is **deep-link-first** — an entry can resolve the caller's
ZIP/state to a real referral URL at match time — so even a thin table yields real referrals via
the seeded national gateways. `EligibilityService.match` ranks programs for a case and returns a
whole-person bundle, surfacing **crisis resources first** whenever a safety indicator or matching
need is present. Sensitive eligibility inputs live in an isolated `EligibilitySnapshot`;
`estimated_pct_fpl` is computed from a dated, sourced Federal Poverty Guidelines table
(`services/fpl.py`) for auditability. `CostEstimateService` gives a transparent read on
fee-waiver rights and likely out-of-pocket cost after free help. All of it is help-not-gate: an
estimate, never a verdict.

### Guidance & research

`GuidanceService` answers plain-language legal-*information* questions with a **cost-tiered**
strategy — a curated knowledge base first, a generative fallback (cambium, with cost-ledger
discipline) only when needed — and is guarded as *not legal advice*. `research.py` adds
**CourtListener** case-law lookup (anonymous + rate-limited when no key is set, richer with the
optional ranger-injected token). Curated document assembly (`DocumentService`) turns templates —
hardship letter, fee-waiver request, evidence checklist, demand letter — filled with case fields
into a draft that **always** enters `staff_review` and is delivered only after a human approves.

### Advocate tools

`PacketService` composes the directory match, the verified deadline, and a curated "what to bring"
checklist into a printable **referral packet**; `WalletCardService` produces a compact pocket
hand-out. Both render to PDF through Timber's shared `common.render` (the `[pdf]` extra), keeping
the PDF engine out of the plugin. Endpoints serve either JSON or PDF (`/cases/{id}/packet`,
`/cases/{id}/wallet-card`).

### Multilingual

`TranslationService` translates an assembled document into the applicant's language via the
**shared** cambium generator (translation is a prompt + a review discipline, not a new
capability). Supported today: **Spanish, Chinese, Vietnamese, Haitian Creole, and Arabic**. A
machine translation lands as a **new** `staff_review` document behind the same human-verification
gate — it is never delivered until a person approves it.

### Equity & fairness

`FairnessService` runs a **deterministic four-fifths-rule disparate-impact audit** over routing
and disposition outcomes. It is the **only** service permitted to read the isolated protected
attributes, and it does so **for human review only, never for routing**. A `feature_guard`
enforces that boundary the other direction: it strips protected attributes before any operational
feature reaches routing, eligibility ranking, or the learning loop. Reachable at the admin-gated
`/audit/fairness`.

### Data ingestion & upkeep

A keyless **HSDS ingestion pipeline** (`fetch → map → dedupe → Census-geocode → upsert`)
normalizes public Open Referral / HSDS and LSC-grantee feeds into the directory, idempotent by
`(enterprise_id, source_ref)`, real/verifiable feeds only (a no-op when none is configured).
**Resource-upkeep** sweeps keep the directory trustworthy: a **link-health** probe stamps every
directory URL's `health_status`/`verified_at` (a dead link is flagged, never deleted), and a
**program-scan** surfaces public-feed candidates missing from the directory *for human review* —
it never auto-inserts. Both run behind admin endpoints (`/admin/ingest/*`, `/admin/upkeep/*`) and
weekly schedulers.

### Adaptive learning loop

An outcome-driven learning substrate (`cambium.adapt`) records `P(favorable)` at routing time
from **operational features only** (through the `feature_guard`), observes each case's terminal
outcome, and matures to per-feature weights that gently nudge routing and eligibility ranking. It
is small-sample-tempered — deliberately **inert until real case/outcome volume accrues** — so it
never over-fits early data. Admin: `/admin/learning/{status,mature}`.

### Public, no-login self-serve

A `/public/find-help` directory search and a `/public/eligibility-screener` let anyone check for
help with no account, and `/public/{slug}/apply` accepts a public intake. This is the front door
for people who will never log in.

## Install

```bash
pip install oak-domain-legal-intake
```

This pulls in `timber-common>=1.1.2` (the plugin scaffolding — `ServiceRegistry`,
`DomainPlugin` / `DomainContext`, `timber.domains` discovery, the Step-8.5 init hook, and
`common.render` for HTML→PDF), `cambium-ai>=0.2.7` (cost-disciplined generation for guidance and
translation, import-guarded so a cambium-less process still serves the curated tier), and `httpx`
(CourtListener). Once installed, discovery is automatic via the entry points — no configuration
needed.

The optional `dev` extra adds `pytest`, `fastapi` (to exercise the Grove router standalone),
`httpx`, and `xhtml2pdf` (so the packet-render test actually produces a PDF and catches CSS
regressions):

```bash
pip install "oak-domain-legal-intake[dev]"
```

Optional runtime secrets (`COURT_LISTENER_API_TOKEN`, `USCIS_CLIENT_ID`, `USCIS_CLIENT_SECRET`)
are advertised via `grove.secret_specs` and injected from ranger at grove bootstrap; all are
optional and degrade gracefully when unset.

## Entry points reference

```toml
[project.entry-points."timber.domains"]
legal_intake = "oak_domain_legal_intake:LegalIntakeDomain"

[project.entry-points."grove.domains"]
legal_intake = "oak_domain_legal_intake.grove_plugin:LegalIntakeGroveDomain"

[project.entry-points."acorn.domains"]
legal_intake = "oak_domain_legal_intake.acorn_plugin:LegalIntakeAcornDomain"

[project.entry-points."grove.secret_specs"]
legal_intake = "oak_domain_legal_intake:secret_specs"
```

Services are reached at runtime through `service_registry.domain("legal_intake")` — e.g.
`.intake`, `.outcome`, `.audit`, `.party`, `.routing`, `.activity`, `.guidance`, `.document`,
`.eligibility`, `.cost`, `.deadline`, `.fairness`, `.link_health`, `.program_scan`, `.ingest`,
`.packet`, `.wallet_card`, `.translation`, `.learning`.

## License

Apache-2.0. See [LICENSE](LICENSE).
