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.
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_intakecapability: 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_reviewand 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
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):
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
[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.
Metadata
Release files for oak-domain-legal-intake 0.25.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| oak_domain_legal_intake-0.25.1.tar.gz | 202.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| oak_domain_legal_intake-0.25.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 404.8 kB
Release files / oak_domain_legal_intake-0.25.1.tar.gz
| Download URL | oak_domain_legal_intake-0.25.1.tar.gz |
|---|---|
| Size | 202.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2c702a03f7512368ce5a20eeaa64ce1ed621dee16be7551c0cf40389e85e3fff
|
|
BLAKE2b-256 checksum How to use checksums |
4b7c4649d37b80f63e4706abe7979f9e213b27e8cbaf0d9f98b5a3b4a43d1207
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.
Transparency logRelease files / oak_domain_legal_intake-0.25.1-py3-none-any.whl
| Download URL | oak_domain_legal_intake-0.25.1-py3-none-any.whl |
|---|---|
| Size | 202.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5c954cf4a2bb558c690d0423dd973d9c447528b773569ffd3fb1d9fe368ac6bc
|
|
BLAKE2b-256 checksum How to use checksums |
71a77ba09a3b02f28b6600d0f7b42bd151f4b4dac80acd0eda1b574e5e464c64
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 19, 2026.
Transparency log