---
module: transactions.journal-parsing
title: Journal Parsing Rules
tab: Claude
order: 40
audience: dev
---

## Ownership
- **Domain owner:** `hiveops-journal`. `hiveops-transactions` is a stateless proxy only (no DB, no rules).
- UI: `hiveops-transactions/frontend/src/components/Settings/JournalParsing.svelte` + `JournalReparse.svelte`.
- API client: `hiveops-transactions/frontend/src/lib/api.ts` → `journalAdminAPI`.
- Backend controllers: `AdminController` + `JournalApiController` in `hiveops-journal`.

## Endpoints (path as seen by frontend; proxied `/api/journal/**` → journal :8087)
| Method | Path | Role |
|--------|------|------|
| GET | `/api/journal/admin/rules` (text/plain YAML) | MSP_ADMIN/BCOS_ADMIN |
| PUT | `/api/journal/admin/rules` (Content-Type: text/plain) | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/journal/admin/reload-rules` | MSP_ADMIN/BCOS_ADMIN |
| GET | `/api/journal/admin/types` | MSP_ADMIN/BCOS_ADMIN |
| GET/PUT | `/api/journal/admin/rules/structured` | MSP_ADMIN/BCOS_ADMIN |
| GET/PUT | `/api/journal/admin/tcr-rules` | MSP_ADMIN/BCOS_ADMIN |
| GET/PUT | `/api/journal/admin/tcr-rules/raw` (text/plain) | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/journal/admin/tcr-rules/reload` | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/journal/admin/reparse-all?atmId=&journalType=` | MSP_ADMIN/BCOS_ADMIN |
| POST | `/api/journal/admin/reconcile` | MSP_ADMIN/BCOS_ADMIN |
| GET | `/api/journal/files?atmId=&journalType=&page=&size=` | isAuthenticated |

- `journalType` values: `NH_TCR` (TCR) or `ATM` (excludes NH_TCR). Omit = all.
- Return shapes: rules save/reload → `{message, version}`; reparse → `{message, queued, total}`; files → Spring `PageResponse<JournalFileRecord>`.

## Tables (DB `hiveiq_journal`, user `hiveops`)
- `journal_files` — read+written by reparse (status/counts/institution).
- `journal_transactions` — deleted + rebuilt on reparse.
- Related: `journal_transactions_archive`, `journal_gaps`, `atm_institution_map`.

## Kafka
- Producer (upload role): topic `journal.parse-requests.<INSTITUTION_UPPER>`; fallback `journal.parse-requests.UNASSIGNED`. Key = `atmId`, value = `fileId`.
- Consumer (worker role): `JournalParseConsumer` `@KafkaListener` per topic.
- Prefix constant: `JournalKafkaConfig.TOPIC_PARSE_PREFIX = "journal.parse-requests."`.

## Runtime roles (one image, `journal.role`)
- `upload` (default): controllers + producer + scheduled jobs. `AdminController` only exists here.
- `worker`: consumer only. No admin endpoints.

## Gotchas
- Rules are a **YAML file** on the journal container, hot-reloaded in memory — NOT a DB table.
- Reparse is async: 200 `{queued}` only means enqueued to Kafka. Parsing happens on workers.
- Stranded PENDING after deploy → `POST /api/journal/admin/reconcile` (not reload-rules).
- Institution not in `JOURNAL_INSTITUTIONS` → routes to `UNASSIGNED` topic/worker (still parses).
- Transactions proxy forwards ONLY `Authorization` + `Content-Type`; other headers/cookies dropped.
- `saveRules` sends `Content-Type: text/plain` — required, endpoint `consumes=TEXT_PLAIN_VALUE`.

## Do NOT
- Do NOT add rules/reparse endpoints or tables to `hiveops-transactions` — journal owns them.
- Do NOT expect admin endpoints on worker containers (conditional on `journal.role=upload`).
- Do NOT trigger reparse to "fix stranded PENDING" — use `/reconcile`; reparse deletes transactions first.
- Do NOT call journal directly assuming custom `X-*` headers pass through the transactions proxy.
- Do NOT confuse Adoons (`/api/adoons/**`, AI journey narrative) with parsing — unrelated.
