Files
hiveops-guide/backend/src/main/resources/content/analytics/transactions/architect.md
T
johannes f21c783d8c fix(guide): gate Architect + Claude tabs to BCOS_ADMIN only (#16)
The 162 Architect/Claude docs were tagged 'audience: dev'. The 'dev' tier
resolves to isInternal() = ROLE_MSP_ADMIN || ROLE_BCOS_ADMIN, so every
MSP_ADMIN could read full internal architecture: source paths, DB tables,
service ports, NGINX routes. Verified live on bcos.dev as msp_a@msp1.bcos.dev.

Retag them to 'audience: bcos' (isBcos, BCOS_ADMIN only). Content-only change;
the 'dev' tier mapping is deliberately left alone so the 15 Internal, 35
Overview and 66 Testing docs stay MSP-visible.

Also add platform.guide-access documenting the audience tiers and the
per-role visibility matrix, so the access model is queryable from the Guide
instead of re-derived from source each time.

Verified: MSP module count unchanged (116 across 17 apps); no module loses
all its tabs. Guide is bcos.dev only, not deployed to production.

Closes #16

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:55:07 -04:00

5.2 KiB

module, title, tab, order, audience
module title tab order audience
analytics.transactions Transactions Architect 30 bcos

How the Transactions tab is built. It is a synchronous pull-through aggregation, not an event-driven or snapshot-backed view. Cross-link [platform.service-ownership], [platform.data-architecture], [platform.kafka].

Services involved

Service Role for this feature
hiveops-analytics-frontend Serves the dashboard SPA (analytics.bcos.cloud, port 5174). The Transactions tab = JournalCoverageTab.svelte.
hiveops-analytics (port 8089) Aggregator. Exposes GET /api/analytics/journal-coverage, forwards the caller JWT to journal, reshapes 3 journal responses into one JournalCoverageResponse. Owns no data for this tab.
hiveops-journal (port 8087) Source of truth. Owns journal_transactions and journal_gaps in DB hiveiq_journal.

Ownership boundary: transaction + gap data belongs exclusively to hiveops-journal; analytics is a read-only consumer. See [platform.service-ownership].

Data flow (request path)

Browser (Transactions tab)
  → GET /api/analytics/journal-coverage        [hiveops-analytics : JournalCoverageController]
      JournalCoverageService.buildJournalCoverage(callerToken)
        via journalWebClient (JOURNAL_SERVICE_URL), forwarding "Bearer <callerToken>":
        ├─ GET /api/journal/transactions/summary?dateFrom=today&dateTo=today   → {total, successful, failed}
        ├─ GET /api/journal/gaps?status=OPEN                                    → [gaps...]
        └─ GET /api/journal/transactions/top-atms?dateFrom=today&dateTo=today&limit=10 → [{atmId, transactionCount}]
  ← JournalCoverageResponse { dailyVolume[today], openGapsTotal, atmsWithGaps[≤20], parseSuccessRateToday, topAtmsByVolume }

Notes on the aggregation logic (JournalCoverageService):

  • dailyVolume is built with today only (single call; the multi-bar chart therefore renders one bar).
  • parseSuccessRateToday = successful * 100.0 / total (0 when total == 0).
  • atmsWithGaps = open gaps grouped by atmId, counted, sorted desc, capped at 20 (UI slices to 10).
  • topAtmsByVolume = journal's top-ATMs list (limit 10; journal hard-caps page size at 50).

Kafka — NOT in this request path

This tab is a live synchronous pull; no Kafka topic is read or written to serve journal-coverage. For context on the surrounding platform (relevant to other analytics tabs, not this one):

  • hiveops-analytics consumes journal.file-parsed (JournalFileParsedConsumer), hiveops.incidents.created/updated/resolved (IncidentEventConsumer) to drive snapshots (analytics_snapshots) and produces analytics.snapshot-ready.
  • hiveops-journal uses journal.parse-requests.{INSTITUTION} (worker parse) and produces journal.file-parsed.
  • The Transactions tab does not read snapshots (analytics_snapshots) — it queries journal live every load. See [platform.kafka].

DB tables

Read (in hiveiq_journal, owned by hiveops-journal):

Table Used for Via
journal_transactions today's total / APPROVED / DECLINED counts; top ATMs by tx count JournalTransactionRepository.countByDateRange..., findTopAtmsByTransactionCount
journal_gaps open gap count + gaps-per-ATM /api/journal/gaps?status=OPEN

Written: none by this feature. journal_gaps rows are created by journal's own scheduled gap detection job (daily 06:00 UTC): for each ATM, any date between its first journal and yesterday with no journal_files row becomes an OPEN gap row. analytics_snapshots / ai_insights in hiveiq_analytics are unrelated to this tab.

See [platform.data-architecture] for the hot/cold split (journal_transactions vs journal_transactions_archive, 90-day boundary) — the today-only queries here always hit the hot table.

Key endpoints

Method Path Service Auth
GET /api/analytics/journal-coverage analytics authenticated JWT (no role gate)
GET /api/journal/transactions/summary?dateFrom=&dateTo= journal JWT; institution-scoped
GET /api/journal/gaps?status=OPEN journal isAuthenticated(); institution-scoped
GET /api/journal/transactions/top-atms?dateFrom=&dateTo=&limit= journal isAuthenticated(); page size capped 50
PUT /api/journal/gaps/{id}/request-fetch journal isAuthenticated() — marks a gap FETCH_REQUESTED (agent re-upload)

Design characteristics

  • No executor→Kafka→record-store pattern here. That pattern applies to snapshot/insight generation, not to this pull-through tab.
  • Auth is caller-forwarded, not a service account: analytics extracts the Authorization: Bearer header and re-sends it to journal (WebClientConfig / journalWebClient). Downstream scope = caller scope.
  • Fault tolerance is silent degradation: each downstream call is wrapped in try/catch that logs a warning and returns empty on failure, so the tab renders zeros rather than an error.
  • Caching: journal's summary is @Cacheable("txSummary") keyed institutions:dateFrom:dateTo; gaps and top-ATMs are uncached.