---
module: transactions.reparse
title: Journal Reparse
tab: Claude
order: 40
audience: dev
---

## Ownership
- **Owner service: `hiveops-journal`** (NOT transactions). Transactions is a stateless proxy only.
- UI: `hiveops-transactions/frontend/src/components/Settings/JournalReparse.svelte`; API layer `journalAdminAPI` in `frontend/src/lib/api.ts`.
- Backend controllers: `AdminController` (reparse/admin), `JournalApiController` (files) in `hiveops-journal`.

## Endpoints (METHOD + path, journal service; hit via transactions proxy `/api/journal/**`)
| Method | Path | Role | Notes |
|--------|------|------|-------|
| POST | `/api/journal/admin/reparse-all?atmId=&journalType=` | `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` | returns `{message,queued,total}` |
| GET | `/api/journal/files?atmId=&journalType=&page=&size=` | `isAuthenticated()` | `PageResponse<JournalFileRecord>` |
| POST | `/api/journal/files/{id}/reparse` | `isAuthenticated()` | single file |
| POST | `/api/journal/admin/reconcile` | `MSP_ADMIN`/`BCOS_ADMIN` | re-queue PENDING/PARSING |
| POST | `/api/journal/admin/parse-sync?fileId=` | `MSP_ADMIN`/`BCOS_ADMIN` | synchronous, diagnostics only |

- `journalType` values: `ATM` (→ `findByJournalTypeNot("NH_TCR")`) or `NH_TCR` (→ TCR only). Blank = all files.
- `atmId` is uppercased server-side before matching.
- Proxy forwards ONLY `Authorization` + `Content-Type` headers; strips others. Proxy `/api/**` role is broad; real gate is in journal.

## Tables (DB `hiveiq_journal`)
- `journal_files` — status reset here (`parse_status`, `transaction_count`, `parse_error`, `last_parsed_size`).
- `journal_transactions` — DELETED at reparse start, re-inserted by worker.
- (archive: `journal_transactions_archive` — not on reparse path.)

## Kafka topics
- `journal.parse-requests.{KEY}` and `journal.parse-requests.UNASSIGNED` — parse jobs; produced by upload role, consumed by worker role (`JournalParseConsumer`, one topic per worker).
- `journal.file-parsed` — emitted after parse; consumed by `CassetteInventoryService` + `JournalGapDetectionService`.

## Flow (terse)
1. `reparse-all` (upload role): select files → per file `deleteByJournalFile` + set PENDING + `kafkaTemplate.send(journal.parse-requests.{KEY}, atmId, fileId)`.
2. worker `JournalParseConsumer`: PARSING → parse → PARSED (or ERROR) → publish `journal.file-parsed`.
3. Status transitions PENDING→PARSING→PARSED are async; UI polls `GET /files` every 15s.

## Roles
- reparse/reconcile/parse-sync/all `AdminController` = `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`.
- `GET /files`, per-file `/reparse` = `isAuthenticated()`.

## Gotchas
- Reparse is **delete-then-rebuild**: transactions gone until a worker re-parses. If the institution's worker is down, files sit PENDING with 0 transactions indefinitely.
- Upload role only PRODUCES to Kafka; worker role PARSES. Missing/stopped worker = nothing advances.
- Files with no institution mapping route to `.UNASSIGNED` → need `hiveiq-journal-worker-unassigned` running.
- Self-heal: startup `reconcileStranded` + on-demand `/reconcile` + `@Scheduled` 30-min recovery for stuck PENDING/PARSING. Wait ≤30 min OR call `/reconcile` before deeper debugging.
- `parseJournalFileAsync` swallows Kafka send failures (logs warn) — file already reset to PENDING; `queued` counter may overstate what actually enqueued when Kafka is down.
- ATM vs TCR is a UI tab → `journalType` param; wrong tab = "no files" confusion.

## Do NOT
- Do NOT treat transactions service as the backend — it owns no data; go to `hiveops-journal`.
- Do NOT invent a per-file reparse under `/admin` — single file is `POST /api/journal/files/{id}/reparse`.
- Do NOT run fleet-wide reparse-all (blank atmId) casually — deletes all transactions of that type first; thousands of files, minutes.
- Do NOT expect synchronous results from `reparse-all` — it only enqueues. Use `parse-sync` (single file) for immediate parse.
- Do NOT add auth assumptions from the proxy — journal's `@PreAuthorize` is the source of truth (`MSP_ADMIN`/`BCOS_ADMIN` for reparse).
