@accurix/centrinfo-analytics
v3.0.0
Published
Secure, framework-agnostic SDK for embedding Centrinfo Answers and Liveboards.
Maintainers
Readme
@accurix/centrinfo-analytics
Embed polished Centrinfo Liveboards and Answers in a customer application without exposing a Centrinfo user session, semantic-model details, SQL, or a signing key to the browser.
This package is intentionally separate from @accurix/centrinfo-widget:
- Use
@accurix/centrinfo-widgetfor the floating or full-page conversational chat experience. - Use
@accurix/centrinfo-analyticsfor Liveboards and standalone Answers. - Both surfaces consume the same governed analytics result contract, so KPIs, charts, explanations, filters, and customer permissions remain consistent.
What the embed includes
- Responsive 12-column Liveboard layouts that stack cleanly on mobile.
- Optional dashboard pages and nested tabs, rendered from the same published Liveboard definition used inside Centrinfo.
- KPI, line, multi-line, bar, pie, scatter, funnel, and table results.
- Chart/table switching, chart sorting, always-visible chart zoom controls, card expansion/minimization, fullscreen, and refresh.
- Plain-language explanations that separate facts, possible drivers, directional outlooks, and recommended next steps.
- Approved filter presets, reporting periods, freshness, and safe row counts.
- A built-in governed dashboard assistant for plain-language follow-up questions and multi-answer comparisons, even when the public chat widget is not installed.
- Optional CSV/chart downloads plus polished whole-dashboard PDF and PowerPoint exports.
- Per-card failures, loading skeletons, auto-refresh, and accessible focus states.
- Shadow DOM isolation so a host site's CSS cannot break the Liveboard.
The public renderer deliberately does not display model names, query IDs, dataset names, SQL, or internal diagnostic metadata.
Ask Your Data response contract
askInsight returns the same governed result used by Centrinfo Web. The SDK
does not run a second analytics algorithm or ask the browser to infer meaning
from raw rows. The response includes:
analysisIntent: the selected method, metric, breakdowns, comparison, requested outputs/entities, and status.analysisCoverage: whether every requested aspect has deterministic evidence, plus any missing aspects.businessAnalysis: calculated evidence, tables, charts, confidence, quality, provenance, and the method-specific explanation.explanation: the direct reader-facing answer, measured evidence, bounded business meaning, unproven drivers, recommendations, forecast metadata, limitations, and follow-up questions.chartRecommendation: the backend-selected renderer-neutral chart contract.conversationId: the thread identifier to reuse for follow-up questions.
The built-in mounts display this contract automatically. For a custom UI, use the exported presentation helpers:
import {
CentrinfoAnalyticsClient,
resolveAnalyticsExplanation,
resolveAnalyticsPresentationResult,
type AnalyticsQueryResult,
} from '@accurix/centrinfo-analytics';
const result: AnalyticsQueryResult = await analytics.askInsight(answerId, {
question: 'Segment patients by utilization and describe each group.',
conversationId,
});
// Selects backend-provided derived artifacts for methods such as clustering,
// regression, and association rules. It does not calculate new evidence.
const presentation = resolveAnalyticsPresentationResult(result);
const explanation = result.businessAnalysis?.explanation ?? resolveAnalyticsExplanation(result);
renderResult(presentation);
renderExplanation(explanation);The @accurix/centrinfo/liveboards entry re-exports the same result, intent,
coverage, business-analysis, and explanation types. @accurix/centrinfo-results
is intentionally limited to chart/table/formatting primitives and does not own
the Ask transport or analytics logic.
Security model
Create an embed app in Centrinfo for each customer application. Centrinfo stores its public Ed25519 key and returns the private key once. Keep that private key in your backend or secret manager. Your backend signs a short-lived JWT for the signed-in customer; the browser only receives that token.
Tokens use EdDSA, audience centrinfo-analytics, the embed-app ID as issuer,
and a lifetime of no more than 15 minutes. Include the organization ID, your
stable external user ID, the allowed scopes, and only scalar external context
values. Centrinfo also checks the browser Origin and the app's
Liveboard/Answer allowlist on every request.
Browser usage
import {
CentrinfoAnalyticsClient,
mountAnswer,
mountLiveboard,
mountLiveboardCollection,
} from '@accurix/centrinfo-analytics';
const analytics = new CentrinfoAnalyticsClient({
apiBaseUrl: 'https://api.example.com',
embedAppId: 'YOUR_EMBED_APP_ID',
getAccessToken: async () => {
const response = await fetch('/api/centrinfo-embed-token');
const { token } = await response.json();
return token;
},
});
const liveboard = mountLiveboard(analytics, '#sales-liveboard', {
liveboardId: 'PUBLISHED_LIVEBOARD_ID',
showTables: true,
showExplanations: true,
showDownload: false,
showAskAI: true,
allowTileExpand: true,
});
// Show every published Liveboard assigned to this SDK application.
// This selector sits above each Liveboard's own pages and tabs.
const dashboards = mountLiveboardCollection(analytics, '#customer-dashboards', {
defaultLiveboardId: 'OPTIONAL_INITIAL_LIVEBOARD_ID',
navigation: 'tabs',
showTables: true,
showAskAI: true,
});
const answer = mountAnswer(analytics, '#active-accounts', {
insightId: 'PUBLISHED_ANSWER_ID',
title: 'Active accounts',
});
analytics.on('error', ({ error }) => console.error(error));
// Later, if this view is removed:
liveboard.destroy();
dashboards.destroy();
answer.destroy();Each client instance owns its token lifecycle and events, so multiple organizations or Liveboards can safely coexist on the same page.
Styling
Pass theme to CentrinfoAnalyticsClient when the embedded Liveboard should
match the host application. The Liveboard renders in Shadow DOM, so prefer SDK
theme tokens instead of relying on global CSS overrides.
const analytics = new CentrinfoAnalyticsClient({
apiBaseUrl: 'https://api.example.com',
embedAppId: 'YOUR_EMBED_APP_ID',
getAccessToken,
theme: {
accent: '#2563eb',
accentStrong: '#1d4ed8',
accentSoft: 'rgba(37, 99, 235, 0.1)',
background: '#f8fafc',
surface: '#ffffff',
surfaceMuted: '#f1f5f9',
text: '#101827',
mutedText: '#52647a',
subtleText: '#7a8da3',
border: '#dbe4ee',
radius: '16px',
fontFamily: 'Avenir Next, sans-serif',
},
});mountLiveboardCollection shows a tab or select control for every published
Liveboard assigned to the SDK application. defaultLiveboardId only chooses
the first selected Liveboard. navigationLabel is optional; omit it when the
host application already labels the section.
Web component
The web-component entry auto-registers <centrinfo-liveboard> and
<centrinfo-answer>. Pass the client through JavaScript; never place a signed
token in an HTML attribute.
import { CentrinfoAnalyticsClient } from '@accurix/centrinfo-analytics';
import '@accurix/centrinfo-analytics/web-components';
const client = new CentrinfoAnalyticsClient({
apiBaseUrl: 'https://api.example.com',
embedAppId: 'YOUR_EMBED_APP_ID',
getAccessToken: () =>
fetch('/api/centrinfo-embed-token').then(async (response) => {
const body = await response.json();
return body.token;
}),
});
const liveboard = document.querySelector('centrinfo-liveboard');
liveboard.configure(client, {
liveboardId: 'PUBLISHED_LIVEBOARD_ID',
showTables: false,
showDownload: false,
});<centrinfo-liveboard></centrinfo-liveboard>React
React is an optional peer dependency. The framework-neutral renderer remains the source of truth; the React export is a small lifecycle wrapper around it.
import { CentrinfoLiveboard } from '@accurix/centrinfo-analytics/react';
export function CustomerDashboard() {
return (
<CentrinfoLiveboard
client={analytics}
liveboardId="PUBLISHED_LIVEBOARD_ID"
showTables
showExplanations
allowTileExpand
/>
);
}Customer-facing controls
Visibility remains an organization decision. Resolve the saved organization policy on your server or in your authenticated host app and pass the approved options to the mount:
mountLiveboard(analytics, '#customer-dashboard', {
liveboardId,
showTables: organizationPolicy.allowTables,
showDownload: organizationPolicy.allowDownloads,
showAskAI: organizationPolicy.allowQuestions,
showExplanations: organizationPolicy.allowExplanations,
maxTableRows: organizationPolicy.maxRows,
});These UI controls do not replace backend enforcement. The embed app's resource allowlist, signed customer identity, role restrictions, field allowlists, row security, and row limits remain authoritative.
showDownload enables card and dashboard image exports, card CSV downloads,
and board-level PDF / PowerPoint exports. Reports are generated server-side by
rerunning every card with the same signed customer identity used by the
dashboard; internal organization data is never substituted for the customer's
scoped result.
Dashboard and card actions
The default customer viewer handles refresh, native share/copy-link, image export, CSV download, PDF, PowerPoint, and Ask AI directly. It does not expose alert creation, question refinement, layout editing, copying, scheduling, or deletion.
Privileged workflows are optional callbacks. Configure them only in a trusted host that has already checked the signed-in user's permission:
mountLiveboard(analytics, '#customer-dashboard', {
liveboardId,
showDownload: true,
actions: {
createAlert: ({ tile, insight, result }) => {
permissions.require('analytics.alerts.create');
return alerts.open({ tileId: tile?.id, insightId: insight?.id, result });
},
refineQuestion: ({ insight }) => {
permissions.require('analytics.insights.create');
return questions.refine(insight?.id);
},
alertsAndDelivery: () => router.open('/analytics/alerts'),
analyzeDashboard: ({ liveboard }) => assistant.open(liveboard?.id),
editLayout: ({ liveboard }) => editor.open(liveboard?.id),
refreshSettings: ({ liveboard }) => refreshRules.open(liveboard?.id),
buildReportAndSchedule: ({ liveboard }) => schedules.open(liveboard?.id),
makeCopy: ({ liveboard }) => liveboards.copy(liveboard?.id),
deleteLiveboard: ({ liveboard }) => liveboards.confirmDelete(liveboard?.id),
},
});A callback-owned menu item is hidden when its handler is omitted. Supplying a callback controls visibility but does not grant backend permission; the host and API must still authorize the operation.
Signing claims
Use a server-side Ed25519 JWT library and sign claims shaped like:
{
"iss": "EMBED_APP_ID",
"aud": "centrinfo-analytics",
"sub": "YOUR_EXTERNAL_USER_ID",
"business_id": "optional-tenant-or-account-id",
"context": {
"region": "west",
"account_id": "acct_123",
"email": "[email protected]"
},
"iat": 1784190000,
"exp": 1784190600
}Never put the private key, token, or sensitive context in HTML, query parameters, logs, or local storage.
Customer alerts
An organization can attach an alert rule to a Liveboard exposed by the embed app. A signed-in customer must explicitly opt in before Accurix evaluates or sends that alert for their data:
const currentSubscription = await analytics.getAlertSubscription();
if (!currentSubscription.enabled) {
await analytics.enableEmailAlerts();
}
await analytics.disableAlerts();The token used for these calls must include alerts:subscribe. Put the
customer's verified email and row-security keys in the server-signed context;
the SDK never accepts an email or identity value from browser input. Alert
evaluation reuses that signed context. Customer delivery currently supports
email; Slack and Teams are reserved for linked organization-member identities.
Interactive demo
The demo uses realistic governed mock responses and never needs a real token:
pnpm --filter @accurix/centrinfo-analytics devOpen http://localhost:3006. The demo includes approved filter presets,
multi-series charts, KPI explanations, a limited table, downloads, per-card
refresh, expansion, fullscreen, responsive layouts, and zoom controls.
Release
Run the complete local package gate:
pnpm --filter @accurix/centrinfo-analytics verify:release
pnpm --filter @accurix/centrinfo-analytics test-e2eGitHub Actions publishes automatically when a tag matching
centrinfo-analytics-v<package-version> is pushed. For version 0.1.0:
git tag centrinfo-analytics-v0.1.0
git push origin centrinfo-analytics-v0.1.0The workflow verifies that the tag equals package.json, runs type, lint,
unit, build, package, and browser checks, then publishes with the existing
NPM_TOKEN repository secret. It can also be run manually in dry-run mode.
