@okrlinkhub/kpi-snapshot
v1.1.5
Published
Componente Convex per KPI snapshot dinamici e configurabili.
Readme
@okrlinkhub/kpi-snapshot
Componente Convex per KPI snapshot dinamici e configurabili: profili, dataSources globali dell'app, membership utenti-profili, regole di calcolo (sum, count, avg, min, max, distinct_count), snapshot manuali o pianificati e tracciamento dei calcoli.
Da questa versione il package può essere usato in due modi:
- come componente standalone, limitandosi a calcolare e storicizzare KPI;
- come componente dell'universo LinkHub, collegando indicatori e valori a
@okrlinkhub/okrhubtramiteexternalId.
Requisiti
- Node.js 18+
- Convex compatibile con i componenti
- Una tua app Convex con tabelle sorgente da cui derivare i KPI
Installazione
Setup standalone:
npm install @okrlinkhub/kpi-snapshotSetup integrato con OKRHub:
npm install @okrlinkhub/kpi-snapshot @okrlinkhub/okrhubCosa fa il package e cosa resta all'app host
kpi-snapshot 1.x si occupa di:
- definizione dei profili e delle regole di calcolo;
- possesso di una sola tabella
dataSources, globale e riusabile da tutti i profili; - periodicita
obbligatoria delladataSourcetramiteschedulePreset`; - membership utenti-profili tramite
profileMembers; - materializzazione globale per
dataSourceId, senza binding persistiti source-profilo; - generazione di export CSV globali automatici per audit/materializzazione e di export custom opzionalmente
profile-scoped, con persistenza su storage Convex; - generazione di
snapshot,snapshotValuese storicovalues; - audit interno
snapshotValue -> sourceExportIds; - evidence CSV automatiche per i
snapshotValuesbase, salvate su storage e collegate tramiteevidence*; - versioning obbligatorio di indicatori base e derivati tramite coppia logica
slug+version; - calcolo di indicatori derivati direttamente nel componente;
- persistenza di report profile-scoped e widget ordinabili, con
slugglobale univoco per deep-link stabili; - widget report di tipo
single_valueechart, con supporto nativo a trend storici e confronti multi-indicatore; - note opzionali su
snapshotValuesederivedSnapshotValues, piu' deep-link opzionali suindicators.referencePageUrlederivedIndicators.referencePageUrl, esposti ai consumer report/UI; - persistenza opzionale di
externalIdsuindicatorsevalues; - helper client-side
exposeApi(...)per ridurre il boilerplate nell'app host.
Novita` 1.1.4
- supporto a
notesopzionale susnapshotValuesederivedSnapshotValues, pensato per annotare direttamente i valori mostrati nei report; - supporto a
referencePageUrlopzionale suindicatorsederivedIndicators, per aprire dal widget una pagina interna rilevante dell'app host.
La tua app host continua a possedere:
- autenticazione e autorizzazione;
- adapter di lettura e normalizzazione dei dati di dominio;
- scheduling dei cron;
- eventuale orchestrazione UI/admin specifica del prodotto;
- metadati di business necessari a OKRHub, ad esempio
companyExternalId,symboleperiodicity.
Contratto di periodicita`
Da questa revisione il modello distingue in modo esplicito tre concetti:
dataSources.schedulePreset: cadence operativa della source (manual,daily,weekly_monday,monthly_first_day);snapshotAt: istante a cui viene calcolato uno snapshot;calculationDefinitions.filters.timeRange: finestra analitica opzionale applicata ai record della source.
Regole operative:
- la periodicita
delladataSourceeobbligatoria e visibile anche quando si collega un indicatore; - le source schedulate (
daily,weekly_monday,monthly_first_day) vengono trattate come dataset operativi limitati all'ultimo anno like-for-like; manualresta l'unica modalita` senza finestra temporale automatica;- un refresh/materializzazione di una source puo` generare snapshot solo per gli indicatori che dipendono da quella source, invece di ricalcolare forzatamente tutto il profilo;
- quando
snapshotAtenoto, il componente leggeanalyticsMaterializedRowstramite l'indice compostoby_data_source_and_occurred_at` invece di filtrare tutto in memoria; - per leggere KPI con cadence diverse, l'host dovrebbe usare l'ultimo valore disponibile per indicatore invece di assumere un unico snapshot globale.
Nota importante sui KPI derivati:
- il motore di calcolo dei derivati resta volutamente
same snapshot only: un derivato viene calcolato solo se tutte le sue dipendenze necessarie sono presenti nello stesso snapshot; - se un KPI derivato combina KPI base alimentati da
dataSourcesconschedulePresetdifferenti, il componente non blocca il salvataggio ma espone un warning esplicito in fase di authoring eupsertDerivedIndicator(...); - il warning ricorda che negli snapshot parziali avviati da una singola source (
triggerSourceKey) il derivato puo` restare non calcolato finche' una delle parti manca nello stesso snapshot.
Modello dati risultante
dataSourcese` globale nell'app: ogni profilo sceglie quali source usare quando definisce KPI o export custom.materializationJobseanalyticsMaterializedRowsdipendono solo dalla source, non dal profilo.calculationDefinitions,indicators,derivedIndicators,snapshotsereportsrestano profile-scoped.indicatorsederivedIndicatorssono versionati: lo stessoslugpuoavere piuversioni nello stesso profilo.- le query runtime e i report risolvono sempre l'ultima
versiondisponibile per ognislug. - ogni report ha uno
slugglobale univoco, così l'host puocostruire route semplici come/analytics/report/[slug]` senza includere il profilo nell'URL. reportWidgetsnon epiulimitata al vecchio modello1 widget = 1 indicatore: un widget puoconteneremembers[]e dichiararewidgetType/chartKind`.analyticsExportsdistingue:- export automatici globali, prodotti da materializzazione o freeze audit;
- export custom legati opzionalmente a un profilo.
- L'host non deve mantenere un catalogo runtime parallelo delle source: entita
, scope, row key e campo data vivono nella tabella componentedataSourceSettings, mentredataSources` salva solo l'istanza concreta scelta dall'admin.
Widget report chart-first
Il modello report del componente ora distingue due primitive:
single_value: card numerica per un solo indicatorechart: widget grafico conchartKind = line | area | bar | pie
Campi principali di reportWidgets:
widgetTypetitle,descriptionlayoutmembers[]con riferimenti indicatori profile-scopedchartKindetimeRangeper i widget grafici
Query runtime dedicate:
getIndicatorHistory: storico di un indicatore attraverso gli snapshotgetSnapshotIndicatorSlice: confronto coerente di piu` indicatori nello stesso snapshotgetReportWidgetDataegetReportWidgetsData: adapter pronti per UI/report builder
Export report condiviso:
- il package espone helper puri
buildReportExportDocument(...),buildReportExportCsv(...)ebuildReportExportFilename(...) - questi helper trasformano
AnalyticsReportDetail+AnalyticsReportWidgetData[]in un payload normalizzato riusabile da consumer diversi - il CSV del report nasce da
flatRowscoerenti tra widgetsingle_value,pieetimeline - il PDF non viene generato nel package: ogni host puo` riusare lo stesso payload per produrre un documento locale senza dipendere dalla UI React specifica
Normalizzazione valori:
- se
indicatorUnite%, le query runtime orientate alla UI (getIndicatorHistory,getSnapshotIndicatorSlice,getReportWidgetData,getReportWidgetsData) moltiplicano automaticamente il valore per100` nel payload di trasporto; - i valori persistiti nelle tabelle del componente restano invariati e continuano a rappresentare la frazione grezza, ad esempio
0.3551; - l'host puo
quindi mostrare direttamente35.51 %` senza dover aggiungere conversioni custom in ogni consumer.
Regole operative:
- i trend leggono gli ultimi
Nsnapshot del singolo indicatore, senza assumere un unico snapshot globale del profilo; - i pie chart richiedono indicatori dello stesso profilo e li confrontano sullo stesso snapshot coerente;
- i contatori
reportUsageCountvengono aggiornati per tutti imembersdel widget, non solo per il caso single KPI.
Catalogo persistito schema-driven
Da questa revisione il flusso corretto e`:
- l'host carica uno o piu
schema.ts` Convex tramite action Node lato app; - il componente memorizza gli schema importati nel registry
schemaImports; - il componente rigenera automaticamente
dataSourceSettingsa partire dadatabaseKey + tableName; - l'import schema non resetta piu` in-line dati materializzati o job; quel wipe vive in un reset job separato e batch-safe;
- la UI di creazione di una
dataSourcelegge solo definizioni generate dal registry; databaseKeydiversi daappsono materializzabili solo se l'host registra un reader esplicito per quel namespace;- i reader interni del componente espongono paginazione a cursore, così l'host può leggere namespace esterni senza
collect()monolitici; - il solo
sourceKindsupportato ematerialized_rows`.
Campi principali di schemaImports:
databaseKey,fileName,checksum,schemaSourcetables[]contableName,tableKey,fields[],indexes[]
Campi principali di dataSourceSettings:
entityTypederivato (databaseKey__tableName)databaseKey,tableName,tableKey,tableLabelallowedScopesallowedRowKeyStrategiesidFieldSuggestions,indexSuggestionsdefaultScopeKey,defaultRowKeyStrategy,defaultDateFieldKeydefaultSelectedFieldKeysfieldCatalog
Campi principali di dataSources lato runtime:
schedulePresetcome cadence operativa obbligatoriadateFieldKeycome campo temporale usato da filtri/materializzazionematerializationIndexKeyrisolto automaticamente dal componente scegliendo il primo indice host-side compatibile condateFieldKey
Compatibilita` degli indici temporali:
- un indice e
compatibile se il suo primo campo coincide condateFieldKey` - indici composti come
['createdAt', 'clinicId']restano validi per il pruning temporale - indici dove
dateFieldKeynon e` il primo campo non vengono usati dal reader
API componente rilevanti:
listSchemaImportsreplaceSchemaImportregenerateCatalogFromSchemasdeleteSchemaImportlistCatalogResetJobsstartCatalogResetlistDataSourceSettings
Linea guida cron per tutti i consumer
Per mantenere il componente semplice da distribuire e replicare su consumer diversi, kpi-snapshot non registra cron runtime e non richiede componenti cron dedicati.
Ogni consumer deve definire 3 cron statici nel proprio convex/crons.ts:
- uno per le source con preset
daily - uno per le source con preset
weekly_monday - uno per le source con preset
monthly_first_day
Il package mantiene:
- i preset di schedule nella tabella
dataSources - le query di supporto per target schedulati e profili impattati (
listScheduledRefreshTargets,listProfileSlugsBySourceKey) - il contratto dei metadati necessari per materializzare e mostrare la cadence della source
L'host continua a possedere:
- la pipeline batch-safe di materializzazione
- i reader sui database reali
- l'orchestrazione finale che, dopo la materializzazione, invoca
createSnapshotRun
Esempio consigliato:
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.cron(
"kpi-snapshot-refresh-daily",
"0 0 * * *",
internal.analyticsExports.runScheduledRefreshes,
{ schedulePreset: "daily" }
);
crons.cron(
"kpi-snapshot-refresh-weekly-monday",
"0 0 * * 1",
internal.analyticsExports.runScheduledRefreshes,
{ schedulePreset: "weekly_monday" }
);
crons.cron(
"kpi-snapshot-refresh-monthly-first-day",
"0 0 1 * *",
internal.analyticsExports.runScheduledRefreshes,
{ schedulePreset: "monthly_first_day" }
);
export default crons;Suggerimento implementativo host-side:
- chiedi al componente i target con
listScheduledRefreshTargets({ schedulePreset }) - materializza ogni source con il reader host-side
- dopo la materializzazione, invoca
createSnapshotRunper ogni profilo impattato passandotriggerSourceKey - usa
getSnapshotRunStatus({ snapshotRunId })per polling finché il run non arriva acompletedoerror
In questo modo il refresh schedulato di una source aggiorna solo i KPI coerenti con quella cadence.
Setup minimo nell'app host
1. Registra il componente
In convex/convex.config.ts:
import { defineApp } from "convex/server";
import kpiSnapshot from "@okrlinkhub/kpi-snapshot/convex.config.js";
const app = defineApp();
app.use(kpiSnapshot, { name: "kpiSnapshot" });
export default app;2. Crea un wrapper Convex nella tua app
Il package esporta un helper exposeApi(...) che genera query e mutation pass-through già pronte. Tu devi solo applicare il tuo controllo accessi.
Template pronto, incluso nel package:
Esempio minimo:
import { components } from "./_generated/api";
import { exposeApi } from "@okrlinkhub/kpi-snapshot";
async function requireAdmin(ctx: { db: any }) {
// Implementa qui il tuo controllo accessi.
}
export const {
listProfiles,
listProfileDefinitions,
listProfileDataSources,
createSnapshotProfile,
upsertDataSource,
upsertIndicator,
upsertCalculationDefinition,
transferIndicatorAcrossProfiles,
listSnapshotValues,
getSnapshotValueEvidenceDownloadUrl,
} = exposeApi(components.kpiSnapshot, {
auth: async (ctx) => {
await requireAdmin(ctx as any);
},
});3. Parsing schema e materializzazione
L'app host:
- parse gli
schema.tscon una action Node separata dal componente; - salva il risultato nel componente e rigenera il catalogo persistito;
- materializza tramite un reader registry host-side leggendo
databaseKey,tableName,fieldCatalog,rowKeyStrategye l'eventualematerializationIndexKeyrisolto automaticamente sulla data source; - quando legge tabelle del namespace
kpiSnapshot, usamaterializationReader.listMaterializableRowsin modalità paginata (cursor,batchSize,continueCursor); - puo
supportare namespace aggiuntivi (es.kpiSnapshot`) solo registrando reader dedicati.
Il formato finale delle righe materializzate resta:
{
rowKey: string;
occurredAt: number;
rowData: Record<string, unknown>;
sourceRecordId?: string;
sourceEntityType?: string;
}Esempio adapter:
const invoices = await ctx.db.query("invoices").collect();
const rows = invoices.map((invoice) => ({
rowKey: invoice.number,
occurredAt: invoice.date,
rowData: {
amount: invoice.amount,
customer: invoice.customer,
date: invoice.date,
},
}));L'host aggiorna la data source del componente chiamando il proprio workflow di materializzazione batch-safe.
La source resta globale; il profileSlug serve solo se vuoi anche concatenare uno snapshot profilo-specifico subito dopo il refresh.
4. Crea uno snapshot orchestrato dalla tua app host
Il componente non ha cron interni. La tua app host puo':
- materializzare da UI
- materializzare dai 3 cron statici del consumer
- chiamare
createSnapshotin modo esplicito quando vuole un run storico dedicato
Nel flusso consigliato, la materializzazione host aggiorna la source e il componente fa partire automaticamente createSnapshotRun:
await ctx.runMutation(api.kpiSnapshot.replaceMaterializedRows, {
sourceKey: "invoices",
rows,
})
await ctx.runAction(api.kpiSnapshot.createSnapshot, {
profileSlug: "finance",
})Durante createSnapshot orchestrato dalla tua app:
- legge le righe materializzate correnti del profilo;
- crea un
snapshotRunItemper ogni definizione attiva; - genera automaticamente una evidence CSV per ogni
snapshotValuebase usando le righe realmente filtrate dalla regola; - congela una volta sola il dataset di ogni source usata nello snapshot;
- salva l'export frozen in
analyticsExports; - mantiene la tracciabilita' fino a
snapshotValue -> sourceExportIds -> analyticsExportse, se presente,snapshotValue -> evidenceRef -> _storage.
createSnapshotRun non esegue piu' tutto in una singola mutation: crea subito snapshot + snapshotRun, mette il job in coda e prosegue in background con stati osservabili:
queuedloadingprocessingderivingfreezingcompletederror
Tipicamente la tua app usa createSnapshot:
- da una pagina admin;
- da un cron definito in
convex/crons.ts; - da una workflow mutation o action della tua app.
Integrazione opzionale con OKRHub
Se installi anche @okrlinkhub/okrhub, lo stesso helper exposeApi(...) può orchestrare il wiring tra i due componenti.
Template pronto, incluso nel package:
Esempio:
import { components } from "./_generated/api";
import { exposeApi } from "@okrlinkhub/kpi-snapshot";
export const {
createSnapshot,
ensureIndicatorOkrhubLink,
syncValuesToOkrhub,
} = exposeApi(components.kpiSnapshot, {
auth: async (ctx) => {
// Il tuo controllo accessi
},
okrhubComponent: components.okrhub,
okrhub: {
sourceApp: "my-app",
sourceUrl: "",
processSyncQueueByDefault: false,
},
});Flusso consigliato
- La tua app crea o aggiorna il profilo KPI.
- La tua app legge e normalizza i dati sorgente di dominio.
- La tua app collega gli indicatori locali a OKRHub con
ensureIndicatorOkrhubLink(...). - La tua app materializza le righe dominio con un workflow host batch-safe.
- I 3 cron statici del consumer richiamano
runScheduledRefreshesusando i preset della source. - La tua app crea uno snapshot manuale con
createSnapshot(...)solo quando vuole un run storico esplicito. - La tua app invia i nuovi
valuesa OKRHub consyncValuesToOkrhub(...).
Modifiche minime richieste nell'app host
Se vuoi usare il package nel modo raccomandato, le modifiche minime lato app sono queste:
- Registrare
kpiSnapshotinconvex/convex.config.ts. - Creare un file wrapper locale, ad esempio
convex/kpiSnapshot.ts, che usiexposeApi(...). - Implementare il tuo controllo accessi nell'opzione
auth. - Creare almeno una mutation o action che legga le tue tabelle e costruisca
sourcePayloads. - Definire i 3 cron statici del consumer che richiamano
runScheduledRefreshes. - Chiamare la tua action/mutation host
createSnapshotda UI o automazione quando serve uno snapshot manuale.
Se integri anche OKRHub, aggiungi inoltre:
- Registrazione del componente
okrhubnella stessa app. - Passaggio di
components.okrhubaexposeApi(...). - Configurazione di
sourceAppe, se serve,sourceUrl. - Passaggio dei metadati richiesti da OKRHub quando colleghi un indicatore, ad esempio
companyExternalId,symbol,periodicity.
Setup ottimizzato LinkHub universe
La modalità ottimizzata serve quando vuoi che kpi-snapshot non sia solo un motore di calcolo, ma anche una sorgente strutturata per OKRHub.
In questo setup:
indicators.externalIdsalva il collegamento tra l'indicatore locale del componente e l'indicatore OKRHub;values.externalIdsalva l'identificativo del valore creato in OKRHub;listValuesForSyncrestituisce i valori locali ancora non collegati;setIndicatorExternalIdesetValueExternalIdpermettono di backfillare o correggere manualmente i mapping;syncValuesToOkrhubesegue la sincronizzazione applicativa minima senza imporre logica di dominio al componente.
Limite importante: kpi-snapshot non può inventare da solo metadati come companyExternalId, symbol o periodicity. Questi dati devono continuare ad arrivare dalla tua app host quando chiami ensureIndicatorOkrhubLink(...).
API del wrapper exposeApi(...)
Funzioni principali esposte dal helper:
- lettura:
listProfiles,listProfileDefinitions,listProfileDataSources,listDataSources,listDerivedIndicators,simulateSnapshot,listSnapshots,getSnapshotRunStatus,listSnapshotValues,getSnapshotValueEvidenceDownloadUrl,getSnapshotExplain,listSnapshotRunErrors,listExports,getExportDownloadUrl,getDataSourceFilterOptions - report builder:
listReports,getReport,getReportBySlug,createReport,archiveReport,updateReportMeta,addReportWidget,removeReportWidget,reorderReportWidgets,getReportWidgetData,getReportWidgetsData - chart data:
getIndicatorHistory,getSnapshotIndicatorSlice - configurazione:
createSnapshotProfile,upsertDataSource,replaceMaterializedRows,upsertIndicator,rebuildIndicatorReportUsageCounters,upsertCalculationDefinition,upsertDerivedIndicator,transferIndicatorAcrossProfiles,replaceProfileDefinitions,toggleCalculation,updateSnapshotValueNotes
Nota migrazione:
- se hai già report salvati prima di introdurre
reportUsageCount, esegui una voltarebuildIndicatorReportUsageCounters()dopo il deploy per riallineare i contatori storici - esecuzione:
createSnapshotRunasync con polling esplicito dello stato job - mapping esterni:
getIndicatorBySlug,getIndicatorByExternalId,setIndicatorExternalId,getValueByExternalId,listValuesForSync,setValueExternalId - integrazione OKRHub:
ensureIndicatorOkrhubLink,syncValuesToOkrhub
Modello dati del componente
Tabelle principali:
snapshotProfilesdataSourcesanalyticsMaterializedRowsanalyticsExportsindicatorscalculationDefinitionsderivedIndicatorssnapshotsreportsreportWidgetssnapshotRunssnapshotRunItemssnapshotValuesderivedSnapshotValuescalculationTracesvalues
Campi rilevanti per integrazione:
indicators.externalIdopzionaleindicators.referencePageUrlopzionalederivedIndicators.referencePageUrlopzionalevalues.externalIdopzionalesnapshotValues.notesopzionalederivedSnapshotValues.notesopzionalesnapshotValues.evidenceRef,evidenceFileName,evidenceRowCount,evidenceGeneratedAt,evidenceMimeType,evidenceSha256snapshotValues.sourceExportIdssnapshotRunItems.sourceExportIdsderivedSnapshotValues.sourceExportIds
Nei payload runtime orientati ai report (getSnapshotIndicatorSlice, getReportWidgetData, getReportWidgetsData), ogni item espone anche:
snapshotValueIdhasEvidenceFileevidenceRowCountnotesreferencePageUrl
Questi campi restano opzionali per non rompere installazioni esistenti e sono usati solo quando vuoi collegare il componente a OKRHub o a un altro sistema esterno.
Per i run snapshot, i campi di stato ora seguono il workflow operativo completo:
snapshots.status:queued | loading | processing | deriving | freezing | completed | errorsnapshotRuns.status:queued | loading | processing | deriving | freezing | completed | errorsnapshotRunSources.status:pending | processing | completed | error
Migrazione da versioni precedenti
Breaking change di versioning
Questa revisione introduce un breaking change intenzionale sul modello dei KPI:
upsertIndicator(...)richiedeversion;upsertDerivedIndicator(...)richiedeversion;upsertCalculationDefinition(...)ereplaceProfileDefinitions(...)richiedono laindicatorVersiontarget;indicatorsederivedIndicatorsnon sono piutabelle con unoslugunivoco per profilo, ma famiglie di versioni identificate da(profileId, slug, version)`;snapshotValues,snapshotRunItems,integrationValuesederivedSnapshotValuessalvano laversionusata durante il calcolo.
Dato che kpi-snapshot non ha ancora installazioni attive, non e` previsto alcun compat layer.
Regola operativa consigliata
Quando un utente scopre un errore nella formula:
- non modificare la versione esistente;
- crea una nuova versione dello stesso
slug; - collega le nuove
calculationDefinitionsalla nuova versione; - lascia che report e query
latestrisolvano automaticamente la versione piu` recente.
In questo modo:
- lo
slugresta stabile per UI e report; - il componente evita sovrascritture in place;
- gli snapshot futuri usano la versione corrente;
- i valori di sync possono conservare i metadati della versione usata al momento del calcolo.
profileSlugper limitare il backfill a un profilo
Copy o move tra profili
Il componente espone transferIndicatorAcrossProfiles(...) per copiare o spostare un KPI base o derivato tra due profili diversi.
Vincoli importanti:
mode: "copy"lascia invariato il sorgente e crea il KPI nel profilo di destinazione;mode: "move"esegue prima tutte le validazioni e rimuove il sorgente solo se la copia e` valida;- per KPI base vengono copiate anche tutte le
calculationDefinitionscollegate; - per KPI derivati, tutte le dipendenze devono gia` esistere nel profilo target;
- il componente blocca sempre collisioni di
slug; - il componente blocca anche collisioni di
labeledescriptiontra KPI base e derivati nello stesso profilo, per evitare ambiguita` in UI; - non vengono mai applicate rinominazioni automatiche silenziose.
Ricette regole pronte
Conteggio totale righe
await upsertCalculationDefinition({
profileSlug: "finance",
indicatorSlug: "count_all",
sourceKey: "invoices",
operation: "count",
enabled: true,
});Somma di un campo numerico
await upsertCalculationDefinition({
profileSlug: "finance",
indicatorSlug: "amount_total",
sourceKey: "invoices",
operation: "sum",
fieldPath: "amount",
normalization: { round: 2 },
enabled: true,
});Conteggio con filtro
await upsertCalculationDefinition({
profileSlug: "finance",
indicatorSlug: "count_selected_categories",
sourceKey: "invoices",
operation: "count",
filters: [{ field: "category", op: "in", value: ["software", "services"] }],
enabled: true,
});Media con soglia
await upsertCalculationDefinition({
profileSlug: "finance",
indicatorSlug: "avg_large_invoices",
sourceKey: "invoices",
operation: "avg",
fieldPath: "amount",
filters: [{ field: "amount", op: "gt", value: 1000 }],
normalization: { round: 2, coalesce: 0 },
enabled: true,
});Note rapide:
countpuò funzionare anche senzafieldPathsum,avg,min,max,distinct_countrichiedono in generefieldPath- per debug: usa prima
simulateSnapshot, poicreateSnapshot, infinegetSnapshotExplain
FAQ
Il componente sincronizza automaticamente verso OKRHub?
No. Il package fornisce helper per farlo bene, ma il momento in cui eseguire la sync resta una decisione della tua app host.
Posso usare kpi-snapshot senza OKRHub?
Sì. L'integrazione con OKRHub è completamente opzionale.
Perché servono ancora modifiche nella mia app?
Perché solo la tua app conosce auth, cron, dati di dominio e identificativi business come companyExternalId.
Tipi
import type { ComponentApi } from "@okrlinkhub/kpi-snapshot";
import { exposeApi } from "@okrlinkhub/kpi-snapshot";Test con convex-test
import { convexTest } from "convex-test";
import { register } from "@okrlinkhub/kpi-snapshot/test";
const t = convexTest(schema, modules);
register(t, "kpiSnapshot");Sviluppo locale
Dalla root del repo:
npm install
npm run build:codegen
npm run typecheckL'example app usa example/convex e importa da @okrlinkhub/kpi-snapshot/convex.config.js.
