Backend Aug 22-25: AI analysis, welcome-packet templating, LetterStream, DocuSeal, staff RBAC + tier gate

- analysis.py: deterministic claim scorer + /analyze /approve /letter /advance-tier endpoints (auto-runs on intake)
- packet.py + packet_fields.json: welcome-packet templating engine (6 onboarding docs, field catalog)
- letterstream.py + letters.py: certified-mail send pipeline + letter lifecycle (webhook verified)
- docuseal.py: DocuSeal signing integration
- staff.py/models.py/schema.sql/auth.py: approval actor from staff key, tier gate (APPROVED+ACTIVE+onboarding docs), onboarding_docs table
- frontend/: dependency-free static portal (intake, magic-link login/verify, dashboard)
- landing-mockups/: 4 design-stance mockups + favicons
- legal/: aup/privacy/sms-terms/terms HTML
- docs/: letter-queue scope, letterstream API contract, 6 welcome-packet templates
- review-dre-landing-2026-08-21.md: 3-variant landing feedback sprint
- compliance/DRE_Compliance_Manual.md: updated

Source synced from deployed /opt/dre-portal/app/ (was 4 days ahead of git).
This commit is contained in:
root
2026-08-26 02:26:33 -04:00
parent 7a5603b495
commit 7a62b0b340
46 changed files with 11004 additions and 35 deletions
+136
View File
@@ -0,0 +1,136 @@
# Letter Queue - Backend Scope (draft)
Date: 2026-08-24
Author: Sho'Nuff (draft for Germaine review)
## Current state (ground truth, verified 2026-08-24)
- **Send pipeline is BUILT and verified end-to-end.** Two new modules:
- `/opt/dre-portal/app/letterstream.py` — LetterStream integration (auth, account
status, send single/batch, preauth/doauth, tracking, signature, proof).
- `/opt/dre-portal/app/letters.py` — router: letters queue lifecycle, PDF render
(fpdf2 2.8.8), LetterStream send, and the callback receiver.
- **Live DB tables exist:** `letters` and `letter_events` (added to `schema.sql`,
applied on restart).
- **LetterStream API contract fully recovered** from the user-supplied
`api_fulfillment.pdf` (21 pages): `POST https://www.letterstream.com/apis/` with
`a`=api_id, `h`=hash, `t`=unique_id. Hash = `md5(base64_encode(last6(t) . api_key
. first6(t)))`. Verified three ways: PDF formula + live `AUTHOK` + user screenshot
sample vector.
- **Account live-verified:** balance `$100.00`, `testmode=disabled`. No documents
submitted, no charges incurred.
- Letter *content* still lives on `claims` (subject/body/tier) and is generated by
`analysis.recommend_letter()` (deterministic, no LLM) — the `letters` queue is the
physical-send layer on top.
- **Queue UI is wired and smoke-tested (2026-08-25, task ls7).** `letter-queue.html` is
a functional queue: filterable list, status badges, Approve / Price & Queue /
Confirm & Mail / Track actions, and a New Letter modal (claim picker + structured
recipient + mail class). `GET /api/staff/letters` now joins `claims` to expose
`claim_number`. Verified via live API lifecycle + Node render harness + 9-page HTTP
smoke (all 200).
- LetterStream callback is **NOT enabled yet** — the receiver is live and tested, but
the callback toggle in the LetterStream dashboard stays off until after the first
real send is confirmed.
## Target state
A durable letter queue: many letters per claim, each with a lifecycle
(draft -> approved -> queued -> sent -> delivered/failed), sendable via
LetterStream certified mail, with FDCPA-compliant content and a full audit trail.
## 1. Data model - new `letters` table
| Column | Type | Purpose |
|---|---|---|
| id | TEXT PK | uuid4 |
| claim_id | TEXT FK -> claims.id | owning claim |
| letter_type | TEXT | demand / escalation / validation / custom |
| tier | TEXT | tier the letter was generated for |
| subject | TEXT | letter subject |
| body | TEXT | letter body (markdown or plain) |
| status | TEXT | draft / approved / queued / sending / sent / delivered / failed |
| recipient_name | TEXT | debtor or registered-agent name |
| addr1, addr2, city, state, zip | TEXT | mailing address |
| letterstream_job_id | TEXT | LetterStream job reference (nullable) |
| tracking_number | TEXT | USPS tracking (nullable) |
| sent_at | TEXT | ISO timestamp (nullable) |
| delivered_at | TEXT | ISO timestamp (nullable) |
| error | TEXT | last send error (nullable) |
| created_by | TEXT | RBAC actor name |
| created_at / updated_at | TEXT | ISO timestamps |
Migration: keep the four claim columns as the "current draft" during transition,
then deprecate them once the queue is live. No destructive drop until the queue
is proven in production.
## 2. API
- `GET /api/staff/claims/{n}/letters` - list letters for a claim
- `POST /api/staff/claims/{n}/letters/generate` - generate a draft row via
`recommend_letter()` (inserts, does not overwrite the claim columns)
- `PUT /api/staff/letters/{id}` - edit a draft
- `POST /api/staff/letters/{id}/queue` - mark queued (requires full mailing address)
- `POST /api/staff/letters/{id}/send` - call LetterStream, store job_id + tracking
- `POST /api/letters/webhook` - LetterStream status callback (delivered / failed)
- `GET /api/staff/letters` - global queue across claims (feeds letter-queue.html)
Idempotency: `send` is guarded by status (only `queued` -> `sending`), so a
double-click cannot mail a letter twice. Store `letterstream_job_id` before
marking sent.
## 3. Send pipeline (LetterStream)
Order of work:
1. Verify the existing `LETTERSTREAM_API_KEY` against their API (is it valid,
what account, what products are enabled).
2. Map their REST surface: auth method, endpoint shape, certified vs
first-class, PDF upload vs HTML/plain rendering, return address handling,
tracking + status webhook. Do not assume - confirm from their docs or a test
call.
3. PDF generation: render the letter body (reportlab or weasyprint) with DRE
letterhead, or pass content to LetterStream to render.
4. Address handling: return address (DRE office / PO box) and debtor mailing
address must both be resolved before send.
5. Status sync: webhook or poll updates `status`, `tracking_number`,
`delivered_at`.
## 4. Compliance (FDCPA)
- Every first-contact letter MUST carry the 1692g validation notice: amount of
debt, creditor name, 30-day dispute right, right to request verification.
- No false, deceptive, or misleading language (1692e); no threats of action DRE
does not intend to take.
- Human sign-off gate: a letter cannot move to `queued` until status is
`approved` (actor recorded).
- Full immutable audit log (actor + timestamp + old/new) - the `audit_log`
pattern already exists and extends here.
## 5. UI (letter-queue.html) — DONE 2026-08-25 (ls7)
`/var/www/internal/letter-queue.html` is live:
- filterable list (status) with per-status summary chips
- Approve (DRAFT) / Price & Queue (APPROVED/PREAUTH/ERROR) / Confirm & Mail (PREAUTH) / Reject / Cancel / Track actions
- status badges (DRAFT/APPROVED/PREAUTH/SENT/REJECTED/CANCELLED/ERROR)
- tracking timeline (USPS scan events) in the detail panel
- New Letter modal: claim picker (prefills debtor name), structured recipient, mail class
Reject/cancel implemented 2026-08-25: `POST /api/staff/letters/{id}/reject` (requires `reason`) and
`POST /api/staff/letters/{id}/cancel` (optional `reason`) move DRAFT/APPROVED/PREAUTH/ERROR letters to
REJECTED/CANCELLED, persist the reason in `letters.note`, and write an `audit_log` row. Guards return 409
for SENT/REJECTED/CANCELLED and 422 for a missing reject reason. Queue UI has Reject (red) / Cancel buttons
for all non-terminal states.
## 6. Decisions locked (2026-08-24)
1. **LetterStream key** — valid and live; `$100.00` balance, `testmode=disabled`.
2. **Return address** — Germaine provides it 2026-08-25. Set as
`LETTERSTREAM_RETURN_ADDRESS` in `.env`; drafting does NOT block on it, only
`send` does (clean 409 until configured).
3. **Signatory**`Debt Recovery Experts LLC` (no named individual). Default in code;
overridable via `LETTERSTREAM_SIGNATORY` in `.env`.
4. **Address verification / NCOA** — none. Pull debtor/return-address data from the
client's claim info + Super Search.
5. **FDCPA 1692g notice** — mandatory on first contact (DRE is a third-party debt
collector). Content is generated by `analysis.recommend_letter()`; final validation-
notice wording vs welcome-packet copy still to be finalized (task ls8).