---
module: transactions.customer-journey
title: Customer Journey
tab: Internal
order: 20
audience: internal
---

Ops/support view of the Customer Journey drawer (the per-transaction replay in the Transactions app). This feature reads and writes **no data of its own** — the `hiveops-transactions` backend is a stateless proxy; the real data lives in **hiveops-journal**, and the AI verdict is computed by **hiveops-ai** (Adoons) via **hiveops-incident**.

## Who can open it

- Any authenticated Transactions user. The proxy (`hiveops-transactions` `SecurityConfig`) gates `/api/**` with `hasAnyRole('USER','CUSTOMER','ADMIN','MSP_ADMIN','QDS_ADMIN','BCOS_ADMIN')` — **not** the `MSP_ADMIN`/`BCOS_ADMIN`-only pattern used elsewhere.
- Journal endpoints behind it require only `isAuthenticated()`.
- **Institution scoping is enforced downstream** in journal: cheque lookups (`ChequeImageService.listByTxnId`) and the Adoons-save PATCH filter by the caller's institution scope. A user only sees journeys/cheques for their own institution(s).

## What the drawer actually calls (all via the transactions proxy, base `…/api`)

| UI action | Frontend call | Lands on |
|-----------|---------------|----------|
| Open drawer (vendor lookup) | `GET /atms` | devices service |
| Adoons button visibility | `GET /adoons/status` | incident → checks hiveops-ai `/actuator/health` |
| Ask/Re-analyze Adoons | `POST /adoons/analyze-journey` | incident → hiveops-ai |
| Save verdict | `PATCH /journal/transactions/{id}/adoons-analysis` | journal (`journal_transactions`) |
| Cheque list (deposits) | `GET /journal/cheques/by-sequence?atmId&txnId` | journal (`cheque_image`) |
| Cheque image | `GET /journal/cheques/{id}/preview` | journal |
| Note denominations (withdrawals) | `GET /journal/cassette-config?atmId` | journal |

## First things to check when it misbehaves

1. **Whole drawer empty / "No event timeline found"** — the timeline is built **client-side** from `transaction.rawLines`. If rawLines is empty, the transaction row parsed without a raw block. Check the source journal file in journal (`GET /journal/files`, then `/files/{id}/raw`) and re-parse if needed.
2. **"Ask Adoons" button missing** — `GET /adoons/status` returned `{available:false}`. That means hiveops-ai's `/actuator/health` was unreachable *from incident*. Check hiveops-ai is up and the incident→ai URL (`hiveops.ai.url`, default `http://hiveiq-ai:8085`) is correct.
3. **"Adoons is currently unavailable."** — `POST /adoons/analyze-journey` returned 503. Causes: hiveops-ai down, missing/incorrect `X-Internal-Secret` between incident and ai, or the model call exceeded the 270s read timeout in incident's `AdoonsController`.
4. **"Failed to analyze transaction."** — any non-503 error from the analyze call (bad payload, proxy/auth failure).
5. **Verdict shows but doesn't persist / reappears blank on reopen** — the save is **fire-and-forget** in the frontend (`saveAdoonsAnalysis(...).catch(() => {})`); save errors are swallowed and never surfaced. If a verdict won't stick, check journal logs for the PATCH (`Error saving Adoons analysis for transaction {id}`) — a 403 means the transaction's institution isn't in the caller's scope.
6. **No cheque images on a deposit** — images only load when `transactionType === 'DEPOSIT'` **and** `sequenceNumber` is present, and the lookup keys on `atmId` + `sequenceNumber` (as `txnId`). SEQ `0` is stored as null by the parser, so those deposits never match. Also confirm the agent actually uploaded cheque images for that ATM.
7. **Denominations show as raw "Cassette2 = 5 notes" instead of "$20 × 5"** — denomination inference failed. Order tried: JB sub-records in raw → `cassette-config` map (from the ATM's last REPLENISHMENT) → single-denomination inference. If `cassette-config` returns `[]`, that ATM has no parseable `# CUR DENO` block in its most recent REPLENISHMENT transaction.

## Common failure modes → fix

- **502/CORS on the whole app** — nginx is the CORS authority; the proxy disables app-level CORS. Check the `transactions.bcos.cloud` server block and the shared cors include, not the app.
- **All journeys 401** — proxy forwards only `Authorization` + `Content-Type` headers; a stale/expired JWT or a dropped Authorization header breaks every downstream call.
- **Cassette config right for one ATM, wrong for another** — it derives from that ATM's *latest* REPLENISHMENT only; a stale or mis-parsed replenishment yields wrong denominations.

## Escalate to engineering (see [architect] tab) when

- Adoons verdicts consistently 503 across ATMs (ai/incident wiring).
- Cheque/PAN data appears for the wrong institution (scoping regression in journal).
- rawLines present but timeline parsing wrong for a vendor format (Nautilus vs Hyosung session parsing).
