UNPKG

business-as-code

Version:

Primitives for expressing business logic and processes as code

284 lines 7.66 kB
/** * Business goals definition and tracking * * Uses org.ai Goal types for standardized goal definitions across the ecosystem. */ /** * Convert a business-as-code GoalDefinition to an org.ai Goal * * @param definition - Business goal definition * @param id - Unique identifier for the goal * @returns org.ai Goal object */ export function toOrgGoal(definition, id) { const result = { id, name: definition.name, description: definition.description || definition.name, target: 100, progress: definition.progress || 0, status: definition.status || 'not-started', }; if (definition.targetDate) { result.targetDate = definition.targetDate; result.deadline = definition.targetDate; } if (definition.owner) result.owner = definition.owner; if (definition.category) result.category = definition.category; if (definition.metrics) result.metrics = definition.metrics; if (definition.dependencies) result.dependencies = definition.dependencies; if (definition.metadata) result.metadata = definition.metadata; return result; } /** * Convert an org.ai Goal to a business-as-code GoalDefinition * * @param goal - org.ai Goal object * @returns Business goal definition */ export function fromOrgGoal(goal) { const result = { name: goal.name, progress: typeof goal.progress === 'number' ? goal.progress : 0, }; if (goal.description) result.description = goal.description; const cat = goal.category; if (cat) result.category = cat; if (goal.targetDate) result.targetDate = goal.targetDate; else if (goal.deadline) result.targetDate = goal.deadline; if (goal.owner) result.owner = goal.owner; if (goal.metrics) result.metrics = goal.metrics; const st = goal.status; if (st) result.status = st; if (goal.dependencies) result.dependencies = goal.dependencies; if (goal.metadata) result.metadata = goal.metadata; return result; } /** * Define a business goal with metrics and tracking * * @example * ```ts * const goals = Goals([ * { * name: 'Launch MVP', * description: 'Ship minimum viable product to early customers', * category: 'strategic', * targetDate: new Date('2024-06-30'), * owner: 'Product Team', * metrics: ['User signups', 'Feature completion rate'], * status: 'in-progress', * progress: 65, * }, * { * name: 'Achieve Product-Market Fit', * description: 'Validate product value with target customers', * category: 'strategic', * targetDate: new Date('2024-12-31'), * owner: 'CEO', * metrics: ['NPS > 50', 'Churn < 5%', '100+ paying customers'], * status: 'in-progress', * progress: 30, * dependencies: ['Launch MVP'], * }, * ]) * ``` */ export function Goals(definitions) { return definitions.map((goal) => validateAndNormalizeGoal(goal)); } /** * Define a single goal */ export function Goal(definition) { return validateAndNormalizeGoal(definition); } /** * Validate and normalize a goal definition */ function validateAndNormalizeGoal(goal) { if (!goal.name) { throw new Error('Goal name is required'); } return { ...goal, category: goal.category || 'operational', status: goal.status || 'not-started', progress: goal.progress || 0, metrics: goal.metrics || [], dependencies: goal.dependencies || [], metadata: goal.metadata || {}, }; } /** * Update goal progress */ export function updateProgress(goal, progress) { if (progress < 0 || progress > 100) { throw new Error('Progress must be between 0 and 100'); } // Auto-update status based on progress let status; if (progress === 0) { status = 'not-started'; } else if (progress === 100) { status = 'completed'; } else { status = 'in-progress'; } const result = { ...goal, progress, }; if (status !== undefined) result.status = status; return result; } /** * Mark goal as at-risk */ export function markAtRisk(goal) { return { ...goal, status: 'at-risk', }; } /** * Mark goal as completed */ export function complete(goal) { return { ...goal, status: 'completed', progress: 100, }; } /** * Check if goal is overdue */ export function isOverdue(goal) { if (!goal.targetDate) return false; return new Date() > goal.targetDate && goal.status !== 'completed'; } /** * Get goals by category */ export function getGoalsByCategory(goals, category) { return goals.filter((g) => g.category === category); } /** * Get goals by status */ export function getGoalsByStatus(goals, status) { return goals.filter((g) => g.status === status); } /** * Get goals by owner */ export function getGoalsByOwner(goals, owner) { return goals.filter((g) => g.owner === owner); } /** * Calculate overall progress across all goals */ export function calculateOverallProgress(goals) { if (goals.length === 0) return 0; const totalProgress = goals.reduce((sum, goal) => sum + (goal.progress || 0), 0); return totalProgress / goals.length; } /** * Check for circular dependencies */ export function hasCircularDependencies(goals) { const goalMap = new Map(goals.map((g) => [g.name, g])); function checkCircular(goalName, visited = new Set()) { if (visited.has(goalName)) return true; const goal = goalMap.get(goalName); if (!goal || !goal.dependencies) return false; visited.add(goalName); for (const dep of goal.dependencies) { if (checkCircular(dep, new Set(visited))) { return true; } } return false; } return goals.some((goal) => checkCircular(goal.name)); } /** * Get goals in dependency order */ export function sortByDependencies(goals) { const goalMap = new Map(goals.map((g) => [g.name, g])); const sorted = []; const visited = new Set(); function visit(goalName) { if (visited.has(goalName)) return; const goal = goalMap.get(goalName); if (!goal) return; visited.add(goalName); // Visit dependencies first if (goal.dependencies) { for (const dep of goal.dependencies) { visit(dep); } } sorted.push(goal); } for (const goal of goals) { visit(goal.name); } return sorted; } /** * Validate goals */ export function validateGoals(goals) { const errors = []; for (const goal of goals) { if (!goal.name) { errors.push('Goal name is required'); } if (goal.progress && (goal.progress < 0 || goal.progress > 100)) { errors.push(`Goal ${goal.name} progress must be between 0 and 100`); } if (goal.dependencies) { const goalNames = new Set(goals.map((g) => g.name)); for (const dep of goal.dependencies) { if (!goalNames.has(dep)) { errors.push(`Goal ${goal.name} depends on unknown goal: ${dep}`); } } } } if (hasCircularDependencies(goals)) { errors.push('Circular dependencies detected in goals'); } return { valid: errors.length === 0, errors, }; } //# sourceMappingURL=goals.js.map