---
module: analytics.transactions
title: Transactions
tab: Internal
order: 20
audience: internal
---

> Ops / support / admin view for the **Transactions** tab (analytics dashboard, tab id `journal`, label "Transactions"). This tab is a **read-only aggregation** — there are no write actions or admin buttons on it.

## What this tab actually is

- UI: `hiveops-analytics/frontend` → `App.svelte` tab `{ id: 'journal', label: 'Transactions' }` → renders `JournalCoverageTab.svelte`.
- Data: one call, `analyticsAPI.getJournalCoverage()` → `GET /api/analytics/journal-coverage` on **hiveops-analytics** (port 8089).
- Source of truth is **hiveops-journal**, not analytics. Analytics is a pass-through aggregator that forwards the caller's JWT to journal and reshapes the result.

## Roles required

- **`GET /api/analytics/journal-coverage`** — authenticated JWT only. No role gate: analytics `SecurityConfig` is `.requestMatchers("/actuator/health","/api/public/**").permitAll().anyRequest().authenticated()`. There is **no** `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` on this endpoint.
- Downstream journal endpoints (`/api/journal/transactions/summary`, `/gaps`, `/transactions/top-atms`) require an authenticated JWT (`@PreAuthorize("isAuthenticated()")`; the summary endpoint has no method annotation and relies on the journal filter chain).
- Results are **institution-scoped** in journal via `InstitutionContext.resolveScope()` — a user sees only their own institution's transactions/gaps. MSP/BCOS admins with multi-institution scope see aggregated counts.

## First things to check when it misbehaves

1. **All tiles show 0 / empty ("No data available", "No ATMs with open gaps").**
   - `JournalCoverageService` **swallows downstream errors** and returns empty maps/lists (→ zeros). There is no user-facing error. Check analytics logs for the warnings it logs instead:
     - `JournalCoverage: failed to fetch summary for {date}: ...`
     - `JournalCoverage: failed to fetch open gaps: ...`
     - `JournalCoverage: failed to fetch top ATMs: ...`
   - Loki (services VM, `localhost:3100`) or `docker compose logs hiveiq-analytics`.
2. **Is hiveops-journal up and reachable?** Analytics calls it via `journalWebClient` (`JOURNAL_SERVICE_URL`, default `http://hiveops-journal:8087`). If journal is down or the URL is wrong, every tile zeros out.
3. **JWT / scope.** Analytics forwards the *caller's* Bearer token to journal — there is no service account. If the user's token is expired or lacks institution scope, journal returns empty/filtered data and the tab looks broken for that user only.
4. **No data for today is normal early in the day.** Everything on this tab is **today-only** (`LocalDate.now()`): daily volume, parse rate, and top-ATMs all use `dateFrom=dateTo=today`. Open gaps is the only cumulative number. Empty early-morning numbers usually mean "no journals ingested yet," not a fault.
5. **Numbers look stale.** Journal caches the summary: `JournalSummaryService.getTransactionSummary` is `@Cacheable("txSummary")` keyed on `institutions:dateFrom:dateTo`. Today's total/approved/declined can lag until the cache entry turns over.

## Common failure modes → fix

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| Whole tab zeros, warnings in analytics log | journal down / wrong `JOURNAL_SERVICE_URL` / journal 5xx | Restore journal, verify env, re-check `docker compose ps hiveiq-journal` |
| Tab zeros for one user only | that user's JWT expired or institution scope empty | Re-login; confirm the account has an institution and journal data exists for it |
| "Today's Journal Volume" shows a single bar | **by design** — service builds `dailyVolume` with today only ("Single call — today only") | Not a bug; multi-day history is not wired for this tab |
| Parse Success Rate looks like an approval rate | it *is* — see gotcha below | Interpret accordingly |
| Open gaps stuck high | gap detection runs daily 06:00 UTC; gaps clear only when the missing `journal_files` row appears | Use per-ATM "Request Fetch" (`PUT /api/journal/gaps/{id}/request-fetch`) to prompt the agent to re-upload |

## Gotchas that bite support

- **"Parse Success Rate" is not a parse rate.** It is computed as `successful / total * 100`, where journal defines `successful = COUNT(host_result='APPROVED')` and `total = all transactions`. So it reflects **transaction approval**, not journal-parse health. The green/red "Successful/Failed" bars are likewise APPROVED vs DECLINED host results — declines are normal customer behavior, not ingest failures. (See uncertainties — this labeling is misleading.)
- **"Other" transactions** (visible on the separate overview `TransactionCard`, not this tab) = `total − approved − declined` (PIN verifications, timeouts, etc.).
- Analytics never writes anything for this tab. There is nothing to reset in `hiveiq_analytics` for a Transactions problem — always look at **hiveops-journal**.
