# PF Onboarding — Implementation Plan

**Date:** 2026-02-13
**Design Doc:** `2026-02-13-pf-onboarding-nextcode-design.md`
**Target Repo:** `/monetarie/core/`
**Branch:** `main` (committed directly, no PR)
**Commit:** `cdafe9c` — `feat: PF onboarding with Nextcode biometric verification`
**Status:** ALL 8 STEPS COMPLETE (2026-02-13)
**Files:** 33 (13 modified + 20 new), +3,931/-135 lines

---

## Overview

Implement the PF (Pessoa Fisica) client onboarding flow with Nextcode API integration in the Core Banking service. The frontend UI already exists (10-step wizard); this plan covers backend API, Nextcode integration, database, Oban workers, WebSocket, and frontend wiring.

---

## Step 1: Database Migration

**Goal:** Create `onboarding_applications` and `onboarding_events` tables.

**Files to create/modify:**
- `backend/priv/repo/migrations/{timestamp}_create_onboarding_tables.exs`

**Details:**
- `onboarding_applications`: UUID PK, CPF, personal info, address, occupation, contact, verification results (bureau_data JSONB, ocr_data JSONB, face match results), document paths, status enum, metadata
- `onboarding_events`: UUID PK, FK to applications, event_type, event_data JSONB, actor tracking
- Indexes on cpf, status, inserted_at, and application_id

**Verification:** `mix ecto.migrate` succeeds, tables created in database

---

## Step 2: Ecto Schemas + Changesets

**Goal:** Define Ecto schemas with proper validation changesets.

**Files to create:**
- `backend/lib/monetarie/onboarding/application.ex` — OnboardingApplication schema
- `backend/lib/monetarie/onboarding/event.ex` — OnboardingEvent schema

**Details:**
- OnboardingApplication: typed fields, cast/validate for each step (personal_changeset, address_changeset, occupation_changeset, contact_changeset, document_changeset, selfie_changeset)
- OnboardingEvent: simple insert-only schema with event_type validation
- CPF format validation (11 digits, checksum)
- Status transitions validated (draft → personal_info → documents_uploaded → verifying → auto_approved/pending_review → approved/rejected)

**Verification:** Schema compiles, changesets validate correctly in IEx

---

## Step 3: Nextcode HTTP Client + API Wrappers

**Goal:** HTTP client for all 5 Nextcode API endpoints with circuit breaker and retry.

**Files to create:**
- `backend/lib/monetarie/nextcode/client.ex` — Base HTTP client (Req-based)
- `backend/lib/monetarie/nextcode/bureau.ex` — Bureau PF: `GET /bureau/natural-person/{cpf}`
- `backend/lib/monetarie/nextcode/ocr.ex` — Full OCR v4: `POST /full-ocr/v4?federalRevenueNumber={cpf}`
- `backend/lib/monetarie/nextcode/face_match.ex` — Face Match v2: `POST /face-match/v2`
- `backend/lib/monetarie/nextcode/liveness.ex` — Liveness: `POST /face-match-for-liveness/{requestId}`
- `backend/lib/monetarie/nextcode/response_parser.ex` — Normalize API responses

**Files to modify:**
- `backend/config/config.exs` — Add `:nextcode` config block
- `backend/config/runtime.exs` — Read env vars (NEXTCODE_API_URL, NEXTCODE_API_KEY, etc.)

**Details:**
- Client uses `Req` library (already in mix.exs)
- Auth: `Authorization: ApiKey {key}` header
- multipart/form-data for file uploads (OCR, Face Match)
- 30s timeout, 2 retries for transient errors
- Circuit breaker pattern (can reuse existing if available, or simple GenServer)
- Response parser extracts `enhanced` section from OCR, normalizes all responses to `{:ok, data} | {:error, reason}`
- Feature flag: `NEXTCODE_ENABLED` — when false, return mock responses for dev

**Verification:** Each API wrapper can be called from IEx with test data against homolog endpoint

---

## Step 4: Onboarding Context Module + Decision Engine

**Goal:** Public API for onboarding CRUD + auto-approval decision engine.

**Files to create:**
- `backend/lib/monetarie/onboarding/onboarding.ex` — Context module (create, update_*, submit, approve, reject, list)
- `backend/lib/monetarie/onboarding/decision_engine.ex` — Auto-approve/review logic
- `backend/lib/monetarie/onboarding/validators/cpf_validator.ex` — CPF checksum validation

**Details:**
- `Onboarding.create(cpf)` — Creates draft application, validates CPF format + uniqueness
- `Onboarding.update_personal(id, params)` — Updates personal info step
- `Onboarding.update_address/occupation/contact` — Step-specific updates
- `Onboarding.upload_document(id, file)` — Stores file, enqueues Oban OCR job
- `Onboarding.upload_selfie(id, file, liveness_request_id)` — Stores file, enqueues Face Match + Liveness jobs
- `Onboarding.submit(id)` — Runs decision engine, sets status
- `Onboarding.approve(id, admin_id)` / `reject(id, admin_id, reason)` — Admin actions
- Decision engine checks: bureau match, OCR confidence, face match %, liveness, CPF validity, PEP flag
- All state changes create onboarding_events records

**Verification:** Context functions work correctly via IEx, decision engine auto-approves/rejects based on criteria

---

## Step 5: API Controller + Routes

**Goal:** REST API endpoints for onboarding flow.

**Files to create:**
- `backend/lib/monetarie_web/controllers/onboarding_controller.ex` — 10 user endpoints
- `backend/lib/monetarie_web/controllers/admin/onboarding_controller.ex` — 4 admin endpoints

**Files to modify:**
- `backend/lib/monetarie_web/router.ex` — Add onboarding routes under v2 (user) and v1/admin scopes

**Details:**
- User endpoints (under JWT auth + rate limiting):
  - `POST /api/v2/onboarding` — Create (CPF only)
  - `GET /api/v2/onboarding/:id` — Get status + all data
  - `PUT /api/v2/onboarding/:id/personal` — Update personal info
  - `PUT /api/v2/onboarding/:id/address` — Update address
  - `PUT /api/v2/onboarding/:id/occupation` — Update occupation
  - `PUT /api/v2/onboarding/:id/contact` — Update contact
  - `POST /api/v2/onboarding/:id/document` — Upload document (multipart)
  - `POST /api/v2/onboarding/:id/selfie` — Upload selfie (multipart)
  - `POST /api/v2/onboarding/:id/submit` — Final submission
  - `GET /api/v2/onboarding/bureau/:cpf` — Bureau PF lookup (sync)
- Admin endpoints (under admin auth):
  - `GET /api/v1/admin/onboarding` — List (paginated, filterable)
  - `GET /api/v1/admin/onboarding/:id` — Detail
  - `POST /api/v1/admin/onboarding/:id/approve` — Approve
  - `POST /api/v1/admin/onboarding/:id/reject` — Reject with reason
- All responses follow existing JSON format conventions
- File uploads accept multipart/form-data, max 10MB server-side
- Rate limit: 3 onboarding attempts per CPF per 24h

**Verification:** All endpoints respond correctly via curl/Postman against local dev server

---

## Step 6: Oban Background Workers

**Goal:** Async processing for OCR, Face Match, and Liveness verification.

**Files to create:**
- `backend/lib/monetarie/workers/ocr_verification.ex` — Calls Full OCR v4, updates application
- `backend/lib/monetarie/workers/face_match_verification.ex` — Calls Face Match v2, updates application
- `backend/lib/monetarie/workers/liveness_verification.ex` — Calls Face Match for Liveness, updates application

**Files to modify:**
- `backend/config/config.exs` — Add Oban queue `:nextcode` with concurrency 5

**Details:**
- Each worker: receives `application_id`, fetches file from storage, calls Nextcode API, updates application record, creates onboarding_event, broadcasts WebSocket notification
- Retry: 3 attempts with exponential backoff (1s, 4s, 16s)
- On final failure: set status to `verification_failed`, create event, notify admin channel
- Workers are idempotent — can safely re-run

**Verification:** Oban jobs enqueue and execute correctly, results stored in DB

---

## Step 7: WebSocket Channel

**Goal:** Real-time notifications for async verification results.

**Files to create:**
- `backend/lib/monetarie_web/channels/onboarding_channel.ex` — Join `onboarding:{application_id}`, push events

**Files to modify:**
- `backend/lib/monetarie_web/channels/user_socket.ex` — Add `channel "onboarding:*", OnboardingChannel`

**Details:**
- Channel auth: JWT token verification (same as existing socket auth)
- User can only join their own application channel (verify ownership)
- Events pushed: `bureau_fetched`, `ocr_completed`, `ocr_failed`, `face_match_completed`, `face_match_failed`, `liveness_completed`, `liveness_failed`, `decision`
- Each event includes relevant data for frontend to update UI
- Oban workers broadcast via `Phoenix.PubSub` → Channel pushes to client

**Verification:** WebSocket connects, joins channel, receives events when Oban jobs complete

---

## Step 8: Frontend Wiring

**Goal:** Connect existing onboarding views to new backend API + Nextcode Liveness SDK.

**Files to modify (Banking app):**
- `apps/banking/src/views/onboarding/PersonalView.vue` — CPF blur → Bureau PF fetch → auto-fill
- `apps/banking/src/views/onboarding/DocumentView.vue` — Upload → POST /document, show OCR results via WS
- `apps/banking/src/views/onboarding/SelfieView.vue` — Integrate Liveness SDK (Web), wire Face Match + Liveness
- `apps/banking/src/views/onboarding/ReviewView.vue` — Show verification badges
- `apps/banking/src/views/onboarding/PendingView.vue` — Show auto-approved vs pending-review

**Files to create:**
- `apps/banking/src/composables/useOnboardingApi.ts` — API service for all onboarding endpoints
- `apps/banking/src/composables/useOnboardingSocket.ts` — Phoenix Channel subscription

**Files to modify (Admin app):**
- `apps/admin/src/views/kyc/AdminKycListView.vue` — Replace mock data with real API
- `apps/admin/src/views/kyc/AdminKycDetailView.vue` — Real data + approve/reject actions

**Details:**
- PersonalView: On CPF field blur, call `GET /api/v2/onboarding/bureau/{cpf}`, auto-fill name/mother_name/birth_date
- DocumentView: On file upload, POST multipart to `/api/v2/onboarding/:id/document`, subscribe to WS channel, show spinner → OCR results (classification type, match indicators)
- SelfieView: Initialize Nextcode Liveness SDK (npm package or script tag), on completion get requestId, upload selfie + requestId to `/api/v2/onboarding/:id/selfie`, wait for WS events
- ReviewView: Display all verification badges (Bureau: matched/mismatched, OCR: confidence %, Face Match: confidence %, Liveness: pass/fail)
- Admin views: Real API calls with pagination, search, approve/reject buttons

**Verification:** Full E2E flow works in browser: CPF → auto-fill → upload doc → OCR results → selfie → liveness → submit → auto-approve/review

---

## Execution Order

Steps 1-2 are sequential (migration before schemas). Steps 3-4 can be parallelized. Steps 5-7 depend on 3-4. Step 8 depends on 5-7.

```
Step 1 (Migration) ──► Step 2 (Schemas) ──┬──► Step 5 (Controller) ──► Step 8 (Frontend)
                                           │
                       Step 3 (Nextcode) ──┤──► Step 6 (Oban Workers)
                                           │
                       Step 4 (Context) ────┴──► Step 7 (WebSocket)
```

**Parallelization opportunities:**
- Steps 3 + 4 can run in parallel (no dependency between Nextcode client and Context module)
- Steps 5 + 6 + 7 can run in parallel (Controller, Workers, and WebSocket are independent)
- Step 8 requires all previous steps

---

## Environment Setup Checklist

- [ ] Request Nextcode API key for homolog environment — user to provide today (2026-02-13)
- [ ] Create GCS bucket `monetarie-onboarding-docs` in `southamerica-east1`
- [ ] Add K8s secrets: `nextcode-api-key`, `nextcode-api-url`
- [ ] Add env vars to `deploy/backend.yaml`
- [ ] Install Nextcode Liveness SDK (Web) in banking app

---

## Completion Summary (2026-02-13)

All 8 steps completed and committed to `main` as `cdafe9c`.

### Files Created (20 new)
| File | Purpose |
|------|---------|
| `backend/priv/repo/migrations/20260510100000_create_onboarding_tables.exs` | DB migration |
| `backend/lib/monetarie/onboarding/application.ex` | OnboardingApplication Ecto schema |
| `backend/lib/monetarie/onboarding/event.ex` | OnboardingEvent Ecto schema |
| `backend/lib/monetarie/onboarding/onboarding.ex` | Context module (16 public functions) |
| `backend/lib/monetarie/onboarding/decision_engine.ex` | 7 automated verification checks |
| `backend/lib/monetarie/onboarding/validators/cpf_validator.ex` | CPF mod-11 checksum |
| `backend/lib/monetarie/nextcode/client.ex` | Base HTTP client (Req, ApiKey auth) |
| `backend/lib/monetarie/nextcode/bureau.ex` | Bureau PF CPF lookup |
| `backend/lib/monetarie/nextcode/ocr.ex` | Full OCR v4 document processing |
| `backend/lib/monetarie/nextcode/face_match.ex` | Face Match v2 comparison |
| `backend/lib/monetarie/nextcode/liveness.ex` | Liveness verification |
| `backend/lib/monetarie/nextcode/response_parser.ex` | API response normalization |
| `backend/lib/monetarie/workers/ocr_verification.ex` | Oban OCR worker |
| `backend/lib/monetarie/workers/face_match_verification.ex` | Oban Face Match worker |
| `backend/lib/monetarie/workers/liveness_verification.ex` | Oban Liveness worker |
| `backend/lib/monetarie_web/channels/onboarding_channel.ex` | Phoenix Channel |
| `backend/lib/monetarie_web/controllers/onboarding_controller.ex` | User-facing REST API (10 actions) |
| `backend/lib/monetarie_web/controllers/admin/onboarding_controller.ex` | Admin KYC review (4 actions) |
| `apps/banking/src/composables/useOnboardingApi.ts` | 10 API methods |
| `apps/banking/src/composables/useOnboardingSocket.ts` | Phoenix Channel WebSocket client |

### Files Modified (13)
| File | Changes |
|------|---------|
| `backend/config/config.exs` | Oban `:nextcode` queue + Nextcode API config |
| `backend/lib/monetarie_web/channels/user_socket.ex` | OnboardingChannel registration |
| `backend/lib/monetarie_web/router.ex` | 10 user + 4 admin onboarding routes |
| `apps/banking/src/composables/useOnboarding.ts` | applicationId, backendStatus state |
| `apps/banking/src/views/onboarding/PersonalView.vue` | Bureau PF auto-fill on CPF |
| `apps/banking/src/views/onboarding/AddressView.vue` | API integration |
| `apps/banking/src/views/onboarding/OccupationView.vue` | API integration |
| `apps/banking/src/views/onboarding/EmailView.vue` | API integration |
| `apps/banking/src/views/onboarding/PhoneView.vue` | API integration |
| `apps/banking/src/views/onboarding/DocumentView.vue` | Upload triggers OCR worker |
| `apps/banking/src/views/onboarding/SelfieView.vue` | Upload triggers Face Match + Liveness |
| `apps/banking/src/views/onboarding/ReviewView.vue` | Submit triggers Decision Engine |
| `apps/banking/src/views/onboarding/PendingView.vue` | WebSocket real-time verification timeline |

### Deployment Status
- **Commit:** `cdafe9c` pushed to `origin/main`
- **Nextcode API Key:** Pending (user to provide 2026-02-13)
- **GKE Deploy:** Pending
- **DB Migration:** Pending (`bin/monetarie rpc 'Monetarie.Release.migrate()'`)
