---
module: transactions.customer-journey
title: Customer Journey
tab: Claude
order: 40
audience: dev
---

Terse act-without-guessing reference. Component: `hiveops-transactions/frontend/src/components/Transactions/CustomerJourney.svelte`.

## Ownership

- **Frontend + proxy:** `hiveops-transactions` (stateless BFF, port 8098, no DB, no Kafka).
- **Data owner:** `hiveops-journal` (DB `hiveiq_journal`) — transactions, cheques, cassette config, persisted Adoons verdict.
- **AI verdict:** `hiveops-ai` (Adoons), reached **through** `hiveops-incident` (`AdoonsController`), not directly.
- **ATM/vendor list:** `hiveops-devices`.

## Endpoints (frontend path via proxy base `…/api`  →  real target)

| Method + frontend path | Target service · real path |
|------------------------|----------------------------|
| `GET /adoons/status` | incident `GET /api/adoons/status` (probes ai `/actuator/health`) |
| `POST /adoons/analyze-journey` | incident `/api/adoons/analyze-journey` → ai `/api/adoons/analyze-journey` |
| `PATCH /journal/transactions/{id}/adoons-analysis` | journal `PATCH /api/journal/transactions/{id}/adoons-analysis` |
| `GET /journal/cheques/by-sequence?atmId&txnId` | journal `GET /api/journal/cheques/by-sequence` |
| `GET /journal/cheques/{id}/preview` | journal `GET /api/journal/cheques/{id}/preview` (PNG) |
| `GET /journal/cassette-config?atmId` | journal `GET /api/journal/cassette-config` |
| `GET /atms` | devices |

Request body for analyze: `{transactionId, rawLines, atmId, atmVendor}`. Save PATCH body = `JourneyAnalysis` `{verdict, narrative, steps[]}`.

## Tables (hiveiq_journal)

- `journal_transactions` — read row + `rawLines`; write `adoonsAnalysis` (JSON string column).
- `journal_transactions_archive` — cold 90-day copy, same shape.
- `cheque_image` — deposit cheque metadata + PNG file path.
- Cassette config = **no table**; parsed live from latest `REPLENISHMENT` tx `# CUR DENO` block.

## Topics

- **None on this path.** Customer Journey is fully synchronous HTTP. (journal has ingest-side producers, irrelevant here.)

## Roles

- Proxy `/api/**`: `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','QDS_ADMIN','BCOS_ADMIN')`.
- Journal endpoints: `@PreAuthorize("isAuthenticated()")`; institution isolation via `InstitutionContext.resolveScope()` in service layer.
- incident→ai hop: `X-Internal-Secret`, not user JWT.

## Gotchas

- Timeline is built **in the browser** from `rawLines`; there is no backend "journey" endpoint. Empty rawLines → "No event timeline found".
- Adoons button hidden unless `GET /adoons/status` → `{available:true}` (i.e. ai `/actuator/health` reachable from incident).
- `analyze-journey` read timeout = 270s in incident; failure → HTTP 503 → UI "Adoons is currently unavailable."
- Verdict save is **fire-and-forget** (`.catch(()=>{})`); persistence failures are invisible in the UI — check journal logs for `Error saving Adoons analysis for transaction {id}`.
- Adoons-save PATCH returns **403** if the tx's institution isn't in caller scope. `cassette-config` is **not** institution-scoped.
- Cheques load only for `transactionType==='DEPOSIT'` with a non-null `sequenceNumber`; lookup key is `atmId`+`sequenceNumber` (as `txnId`). SEQ `0` is stored null → no match.
- Denomination display resolved client-side (`parseDenominationsFromRaw`): JB records → `cassette-config` map → single-denom inference; falls back to raw note counts.
- Proxy forwards only `Authorization` + `Content-Type`; strips other headers/cookies.
- `adoonsAnalysis` is stored as a JSON **string**; frontend `JSON.parse`s when the API returns a string.

## Do NOT

- Do NOT add a journey/timeline endpoint to `hiveops-transactions` — it owns no data; put reads in journal.
- Do NOT call `hiveops-ai` directly for Adoons from the frontend — always go proxy → incident → ai (JWT boundary + `X-Internal-Secret`).
- Do NOT write transaction/cheque/cassette data via the transactions service — it is a pure proxy.
- Do NOT assume the verdict persisted just because the UI shows it — the save can fail silently.
- Do NOT drop `InstitutionContext` checks when touching journal journey endpoints — that scope is the only cross-institution guard.
- Do NOT expect Kafka involvement in read/save of a journey.
