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>
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):
dailyVolumeis built with today only (single call; the multi-bar chart therefore renders one bar).parseSuccessRateToday = successful * 100.0 / total(0 whentotal == 0).atmsWithGaps= open gaps grouped byatmId, 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 producesanalytics.snapshot-ready. - hiveops-journal uses
journal.parse-requests.{INSTITUTION}(worker parse) and producesjournal.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: Bearerheader 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")keyedinstitutions:dateFrom:dateTo; gaps and top-ATMs are uncached.