---
module: transactions.processing-rules
title: Journal Processing Rules
tab: Claude
order: 40
audience: dev
---

Terse act-without-guessing reference. Owned by **hiveops-journal** (upload role). Transactions app is only the UI + proxy.

## Ownership

- Feature owner service: **hiveops-journal** (`AdminController`, `JournalRulesService`, `JournalParser`).
- UI: `hiveops-transactions/frontend/src/components/Settings/ProcessingRules.svelte` (calls `journalAdminAPI` in `lib/api.ts`).
- Data lives in a **YAML file** (`JOURNAL_RULES_PATH` → `journal-rules.yml`), NOT a DB table.

## Endpoints (all on hiveops-journal, all `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`)

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/api/journal/admin/rules/structured` | Get `{version, transactionTypes[], systemEvents[]}` |
| PUT | `/api/journal/admin/rules/structured` | Full-replace rules from DTO, write YAML, hot-reload |
| GET | `/api/journal/admin/rules` | Raw YAML (text/plain) |
| PUT | `/api/journal/admin/rules` | Save raw YAML (text/plain), hot-reload |
| POST | `/api/journal/admin/reload-rules` | Reload YAML from disk |
| GET | `/api/journal/admin/types` | Journal type names |
| POST | `/api/journal/admin/reparse-all` | Reparse files; opt `?atmId=` `?journalType=` (`ATM`/`NH_TCR`) |
| GET/PUT | `/api/journal/admin/tcr-rules`, `/tcr-rules/raw`, POST `/tcr-rules/reload` | TCR variant (separate `tcr-rules.yml`) |

- Frontend base: `window.__APP_CONFIG__.apiUrl` (dev default `http://localhost:8098/api` → Transactions BFF), paths prefixed `/journal/...`. BFF/nginx proxies `/api/journal/**` to journal service verbatim.

## DTO (`StructuredRulesDTO`)

- `version:int`, `transactionTypes:[{name, jprContains, opCodeMatches:[]}]`, `systemEvents:[{pattern, eventType}]`.
- Save is a **FULL REPLACE** of `transaction_types` map + `system_events` list. Array order = match priority (first match wins).
- YAML keys: `jpr_contains`, `op_code_matches`, `system_events[].pattern`, `system_events[].event_type`.

## Tables & topics

- YAML file: `journal-rules.yml` at `JOURNAL_RULES_PATH` (`journal.rules.path`).
- Tables (DB `hiveiq_journal`): `journal_files`, `journal_transactions` (written by parser). Config screen touches NO table.
- `journal_events` = **incident DB**, written only by `CassetteInventoryService`, not by these rules.
- Topics: `journal.parse-requests.<INSTITUTION>` (dispatch), `hiveops.journal.transactions.recorded` (WITHDRAWAL/DEPOSIT/REVERSAL only), `journal.replenishments`, `journal.file-parsed`.
- Event-type picklist (13) is hard-coded in `ProcessingRules.svelte` `EVENT_TYPES`, not from backend.

## Gotchas

- `AdminController` is `@ConditionalOnProperty(journal.role=upload, matchIfMissing=true)` → endpoints exist ONLY on upload container (`hiveiq-journal`), 404 on `hiveiq-journal-worker-*`.
- PUT structured/raw throws `IllegalStateException`→HTTP 400 if `JOURNAL_RULES_PATH` blank ("cannot save rules"). Reads still work.
- Hot-reload affects only NEW parses. Historical data needs `POST /reparse-all` (deletes + re-queues matching files — heavy).
- System-event match only increments `event_count`; it does NOT create an incident/`journal_events` row. UI copy ("generates an incident event") is aspirational, not current behavior.
- Save = full replace: omitting an existing type/event deletes it from YAML.
- Adoons = branding for hiveops-ai; unrelated to this screen.

## Do NOT

- Do NOT look for a `processing_rules` table — it's a YAML file.
- Do NOT hit worker containers for admin ops.
- Do NOT expect a JWT below MSP_ADMIN/BCOS_ADMIN to work (403 at journal even if BFF passes it).
- Do NOT assume system-event patterns raise incidents — they only bump a counter today.
- Do NOT PATCH/merge — the API only supports full-object replace via PUT.
- Do NOT edit `journal-rules.yml` and forget hot-reload/reparse for existing files.
- Do NOT confuse this with TCR rules (`/tcr-rules*`, `tcr-rules.yml`, `TcrProcessingRules.svelte`).
