business-as-code
Version:
Primitives for expressing business logic and processes as code
297 lines • 8.36 kB
JavaScript
/**
* Key Performance Indicators (KPIs) management
*
* Uses org.ai KPI types for standardized KPI definitions across the ecosystem.
*/
/**
* Convert a business-as-code KPIDefinition to an org.ai KPI
*
* @param definition - Business KPI definition
* @param id - Unique identifier for the KPI
* @returns org.ai KPI object
*/
export function toOrgKPI(definition, id) {
const result = {
id,
name: definition.name,
value: definition.current ?? 0,
target: definition.target ?? 0,
unit: definition.unit || '',
};
if (definition.description !== undefined)
result.description = definition.description;
if (definition.current !== undefined)
result.current = definition.current;
if (definition.category !== undefined)
result.category = definition.category;
if (definition.frequency !== undefined)
result.frequency = definition.frequency;
if (definition.dataSource !== undefined)
result.dataSource = definition.dataSource;
if (definition.formula !== undefined)
result.formula = definition.formula;
if (definition.metadata !== undefined)
result.metadata = definition.metadata;
return result;
}
/**
* Convert an org.ai KPI to a business-as-code KPIDefinition
*
* @param kpi - org.ai KPI object
* @returns Business KPI definition
*/
export function fromOrgKPI(kpi) {
const result = {
name: kpi.name,
unit: kpi.unit,
};
if (kpi.description !== undefined)
result.description = kpi.description;
const cat = kpi.category;
if (cat !== undefined)
result.category = cat;
if (typeof kpi.target === 'number')
result.target = kpi.target;
if (typeof kpi.value === 'number')
result.current = kpi.value;
else if (typeof kpi.current === 'number')
result.current = kpi.current;
if (kpi.frequency !== undefined)
result.frequency = kpi.frequency;
if (kpi.dataSource !== undefined)
result.dataSource = kpi.dataSource;
if (kpi.formula !== undefined)
result.formula = kpi.formula;
if (kpi.metadata !== undefined)
result.metadata = kpi.metadata;
return result;
}
/**
* Define Key Performance Indicators for tracking business metrics
*
* @example
* ```ts
* const businessKPIs = kpis([
* {
* name: 'Monthly Recurring Revenue',
* description: 'Total predictable revenue per month',
* category: 'financial',
* unit: 'USD',
* target: 100000,
* current: 85000,
* frequency: 'monthly',
* dataSource: 'Billing System',
* formula: 'SUM(active_subscriptions.price)',
* },
* {
* name: 'Customer Churn Rate',
* description: 'Percentage of customers lost per month',
* category: 'customer',
* unit: 'percent',
* target: 5,
* current: 3.2,
* frequency: 'monthly',
* dataSource: 'CRM',
* formula: '(churned_customers / total_customers) * 100',
* },
* {
* name: 'Net Promoter Score',
* description: 'Customer satisfaction and loyalty metric',
* category: 'customer',
* unit: 'score',
* target: 50,
* current: 48,
* frequency: 'quarterly',
* dataSource: 'Survey Platform',
* },
* ])
* ```
*/
export function kpis(definitions) {
return definitions.map((kpi) => validateAndNormalizeKPI(kpi));
}
/**
* Define a single KPI
*/
export function kpi(definition) {
return validateAndNormalizeKPI(definition);
}
/**
* Validate and normalize a KPI definition
*/
function validateAndNormalizeKPI(kpi) {
if (!kpi.name) {
throw new Error('KPI name is required');
}
return {
...kpi,
category: kpi.category || 'operations',
frequency: kpi.frequency || 'monthly',
metadata: kpi.metadata || {},
};
}
/**
* Calculate KPI achievement percentage
*/
export function calculateAchievement(kpi) {
if (kpi.target === undefined || kpi.current === undefined)
return 0;
if (kpi.target === 0)
return 100;
return (kpi.current / kpi.target) * 100;
}
/**
* Check if KPI meets target
*/
export function meetsTarget(kpi) {
if (kpi.target === undefined || kpi.current === undefined)
return false;
// For metrics where lower is better (like churn rate)
const lowerIsBetter = ['churn', 'cost', 'time', 'error', 'downtime'].some((term) => kpi.name.toLowerCase().includes(term));
if (lowerIsBetter) {
return kpi.current <= kpi.target;
}
return kpi.current >= kpi.target;
}
/**
* Update KPI current value
*/
export function updateCurrent(kpi, value) {
return {
...kpi,
current: value,
};
}
/**
* Update KPI target
*/
export function updateTarget(kpi, target) {
return {
...kpi,
target,
};
}
/**
* Get KPIs by category
*/
export function getKPIsByCategory(kpis, category) {
return kpis.filter((k) => k.category === category);
}
/**
* Get KPIs by frequency
*/
export function getKPIsByFrequency(kpis, frequency) {
return kpis.filter((k) => k.frequency === frequency);
}
/**
* Get KPIs that meet their targets
*/
export function getKPIsOnTarget(kpis) {
return kpis.filter(meetsTarget);
}
/**
* Get KPIs that don't meet their targets
*/
export function getKPIsOffTarget(kpis) {
return kpis.filter((kpi) => !meetsTarget(kpi));
}
/**
* Calculate overall KPI health score (0-100)
*/
export function calculateHealthScore(kpis) {
if (kpis.length === 0)
return 0;
const onTarget = getKPIsOnTarget(kpis).length;
return (onTarget / kpis.length) * 100;
}
/**
* Group KPIs by category
*/
export function groupByCategory(kpis) {
const groups = new Map();
for (const kpi of kpis) {
const category = kpi.category || 'other';
const existing = groups.get(category) || [];
groups.set(category, [...existing, kpi]);
}
return groups;
}
/**
* Calculate variance from target
*/
export function calculateVariance(kpi) {
if (kpi.target === undefined || kpi.current === undefined)
return 0;
return kpi.current - kpi.target;
}
/**
* Calculate variance percentage from target
*/
export function calculateVariancePercentage(kpi) {
if (kpi.target === undefined || kpi.current === undefined)
return 0;
if (kpi.target === 0)
return 0;
return ((kpi.current - kpi.target) / kpi.target) * 100;
}
/**
* Format KPI value with unit
*/
export function formatValue(kpi, value) {
const val = value ?? kpi.current;
if (val === undefined)
return 'N/A';
const formatted = val.toLocaleString(undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
});
if (!kpi.unit)
return formatted;
switch (kpi.unit.toLowerCase()) {
case 'usd':
case 'eur':
case 'gbp':
return `$${formatted}`;
case 'percent':
case '%':
return `${formatted}%`;
default:
return `${formatted} ${kpi.unit}`;
}
}
/**
* Compare KPI performance over time
*/
export function comparePerformance(current, previous) {
if (current.current === undefined || previous.current === undefined) {
return { change: 0, changePercent: 0, improved: false };
}
const change = current.current - previous.current;
const changePercent = previous.current !== 0 ? (change / previous.current) * 100 : 0;
// Determine if change is an improvement
const lowerIsBetter = ['churn', 'cost', 'time', 'error', 'downtime'].some((term) => current.name.toLowerCase().includes(term));
const improved = lowerIsBetter ? change < 0 : change > 0;
return { change, changePercent, improved };
}
/**
* Validate KPI definitions
*/
export function validateKPIs(kpis) {
const errors = [];
for (const kpi of kpis) {
if (!kpi.name) {
errors.push('KPI name is required');
}
if (kpi.target !== undefined && kpi.target < 0) {
errors.push(`KPI ${kpi.name} target cannot be negative`);
}
if (kpi.current !== undefined && kpi.current < 0) {
errors.push(`KPI ${kpi.name} current value cannot be negative`);
}
}
return {
valid: errors.length === 0,
errors,
};
}
//# sourceMappingURL=kpis.js.map