services-as-software
Version:
Define services with objectives, key results, and various pricing models
173 lines (171 loc) • 6.18 kB
JavaScript
// src/pricing/index.ts
function calculateCostBasedPrice(pricing, quantity = 1) {
const { costBase, fixedCosts = 0, variableCosts = 0 } = pricing;
return costBase + fixedCosts + variableCosts * quantity;
}
function calculateMarginBasedPrice(pricing, quantity = 1) {
const { costBase, marginPercentage } = pricing;
const cost = costBase * quantity;
const margin = cost * (marginPercentage / 100);
return cost + margin;
}
function calculateActivityBasedPrice(pricing, activities) {
return pricing.activities.reduce((total, activity) => {
const quantity = activities[activity.name] || 0;
return total + activity.rate * quantity;
}, 0);
}
function calculateOutcomeBasedPrice(pricing, outcomes) {
return pricing.outcomes.reduce((total, outcome) => {
const achievedValue = outcomes[outcome.metric] || 0;
if (achievedValue >= outcome.targetValue) {
return total + outcome.price;
}
return total;
}, 0);
}
function calculatePrice(pricing, params = {}) {
const { quantity = 1, activities = {}, outcomes = {} } = params;
switch (pricing.model) {
case "cost-based":
return calculateCostBasedPrice(pricing, quantity);
case "margin-based":
return calculateMarginBasedPrice(pricing, quantity);
case "activity-based":
return calculateActivityBasedPrice(pricing, activities);
case "outcome-based":
return calculateOutcomeBasedPrice(pricing, outcomes);
default:
throw new Error(`Unsupported pricing model: ${pricing.model}`);
}
}
// src/index.ts
function Service(definition) {
validateServiceDefinition(definition);
const service = {
...definition,
/**
* Calculate the price for this service
* @param params Parameters for price calculation
* @returns Calculated price
*/
calculatePrice(params) {
return calculatePrice(definition.pricing, params);
},
/**
* Register the service with the service registry
* @param options Optional configuration for service registration
* @returns Promise resolving to the registered service
*/
async register(options) {
try {
const { Services } = await import("services.do");
const services = new Services(options);
const serviceDefinition = {
name: definition.name,
description: definition.description,
endpoint: `https://api.services.do/implementations/${definition.implementation.type}/${definition.implementation.id}`,
version: definition.implementation.version,
metadata: {
...definition.metadata,
objective: definition.objective,
keyResults: definition.keyResults,
pricing: definition.pricing,
implementation: definition.implementation
}
};
const registeredService = await services.register(serviceDefinition);
return {
...definition,
id: registeredService.id,
status: registeredService.status,
endpoint: registeredService.endpoint,
createdAt: registeredService.createdAt,
updatedAt: registeredService.updatedAt
};
} catch (error) {
console.warn("Failed to register with services.do, using mock implementation", error);
return {
...definition,
id: generateId(),
status: "active",
endpoint: `https://api.services.do/services/${generateId()}`,
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
};
}
},
/**
* Track progress towards key results
* @param results Record of key result updates
*/
trackProgress(results) {
definition.keyResults.forEach((kr) => {
if (kr.description in results) {
kr.currentValue = results[kr.description];
}
});
},
/**
* Check if all key results have been achieved
* @returns Whether all key results have been achieved
*/
isObjectiveAchieved() {
return definition.keyResults.every((kr) => kr.currentValue !== void 0 && kr.target !== void 0 && kr.currentValue >= kr.target);
}
};
return service;
}
function validateServiceDefinition(definition) {
const { name, objective, keyResults, pricing, implementation } = definition;
if (!name) {
throw new Error("Service name is required");
}
if (!objective || !objective.description) {
throw new Error("Service objective with description is required");
}
if (!keyResults || !Array.isArray(keyResults) || keyResults.length === 0) {
throw new Error("At least one key result is required");
}
if (!pricing || !pricing.model) {
throw new Error("Service pricing model is required");
}
if (!implementation || !implementation.type || !implementation.id) {
throw new Error("Service implementation details are required");
}
switch (pricing.model) {
case "cost-based":
if (pricing.costBase === void 0) {
throw new Error("Cost base is required for cost-based pricing");
}
break;
case "margin-based":
if (pricing.costBase === void 0 || pricing.marginPercentage === void 0) {
throw new Error("Cost base and margin percentage are required for margin-based pricing");
}
break;
case "activity-based":
if (!pricing.activities || !Array.isArray(pricing.activities) || pricing.activities.length === 0) {
throw new Error("At least one activity is required for activity-based pricing");
}
break;
case "outcome-based":
if (!pricing.outcomes || !Array.isArray(pricing.outcomes) || pricing.outcomes.length === 0) {
throw new Error("At least one outcome is required for outcome-based pricing");
}
break;
default:
throw new Error(`Unsupported pricing model: ${pricing.model}`);
}
}
function generateId() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
export {
Service,
calculateActivityBasedPrice,
calculateCostBasedPrice,
calculateMarginBasedPrice,
calculateOutcomeBasedPrice,
calculatePrice
};