pricing4ts
Version:
 Pricing4TS is a TypeScript-based toolkit designed to enhance the server-side functionality of a pricing-driven SaaS by enabling the seamless integration of pricing plans into the application logic. T
218 lines (185 loc) • 11.6 kB
text/typescript
import { AddOn, Plan, Pricing } from '../../types';
import PricingCSP from '../models/minizinc/minizinc';
import { pricing2DZN } from '../utils/dzn-exporter/pricing-dzn-exporter';
import { PricingOperation } from '../models/minizinc/minizinc';
import { ErrorMessage } from 'minizinc';
import { CspSolution } from '../types';
import { explain } from '../utils/minizinc-explanator';
import { getPlanPrices } from '../../main/models/pricing2yaml/plan';
import { getAddOnPrices } from '../../main/models/pricing2yaml/addon';
export interface PricingAnalytics {
numberOfFeatures: number;
numberOfUsageLimits: number;
numberOfPlans: number;
numberOfAddOns: number;
configurationSpaceSize: number;
minSubscriptionPrice: number;
maxSubscriptionPrice: number;
}
export interface AnalyticsOptions {
printDzn: boolean;
}
const defaultAnalyticsOptions: AnalyticsOptions = {
printDzn: false
}
export default class PricingService {
private readonly pricing: Pricing;
constructor(pricing: Pricing){
this.pricing = pricing;
}
async runPricingOperation(pricingOperation: PricingOperation) {
const dznPricing = pricing2DZN(this.pricing);
const model = new PricingCSP();
return model.runPricingOperation(pricingOperation, dznPricing);
}
async getConfigurationSpace(){
const dznPricing = pricing2DZN(this.pricing);
try{
const configurationSpace = [];
const result = await this._getConfigurationSpace(dznPricing);
const allSolutions: CspSolution[] = result.allSolutions!;
for (const solution of allSolutions){
const selectedPlan = Object.keys(this.pricing.plans!)[solution.selected_plan - 1];
const selectedAddons = solution.selected_addons.map((addonIndex, index) => addonIndex === 1 ? Object.keys(this.pricing.addOns!)[index] : null).filter(addon => addon !== null);
const subscriptionFeatures = solution.subscription_features.map((featureIndex, index) => featureIndex === 1 ? Object.keys(this.pricing.features)[index] : null).filter(feature => feature !== null);
const subscriptionUsageLimits = solution.subscription_usage_limits.map((usageLimitIndex, index) => usageLimitIndex === 1 ? Object.keys(this.pricing.usageLimits!)[index] : null).filter(usageLimit => usageLimit !== null);
configurationSpace.push({
selectedPlan: selectedPlan,
selectedAddons: selectedAddons,
subscriptionFeatures: subscriptionFeatures,
subscriptionUsageLimits: subscriptionUsageLimits,
subscriptionCost: solution.subscription_cost
});
}
return configurationSpace;
}catch(e){
throw new Error((e as ErrorMessage).message);
}
}
async getAnalytics(analyticsOptions?: AnalyticsOptions) {
if (!analyticsOptions){
analyticsOptions = defaultAnalyticsOptions;
}else{
analyticsOptions = {...defaultAnalyticsOptions, ...analyticsOptions};
}
try{
const dznPricing = pricing2DZN(this.pricing);
if (analyticsOptions.printDzn){
console.log(dznPricing);
}
// Run the Minizinc models for needed anayltics
const [configurationSpaceResult, minSubscriptionPriceResult, maxSubscriptionPriceResult] = await Promise.all([
this._getConfigurationSpace(dznPricing),
this._getMinSubscriptionPrice(dznPricing),
this._getMaxSubscriptionPrice(dznPricing)
]).catch(e => {
throw new Error(explain(e, this.pricing));
});
// Extract the list of features and usage limits by type
const informationFeatures = Object.values(this.pricing.features).filter(f => f.type === "INFORMATION");
const integrationFeatures = Object.values(this.pricing.features).filter(f => f.type === "INTEGRATION");
const domainFeatures = Object.values(this.pricing.features).filter(f => f.type === "DOMAIN");
const automationFeatures = Object.values(this.pricing.features).filter(f => f.type === "AUTOMATION");
const managementFeatures = Object.values(this.pricing.features).filter(f => f.type === "MANAGEMENT");
const guaranteeFeatures = Object.values(this.pricing.features).filter(f => f.type === "GUARANTEE");
const supportFeatures = Object.values(this.pricing.features).filter(f => f.type === "SUPPORT");
const paymentFeatures = Object.values(this.pricing.features).filter(f => f.type === "PAYMENT");
// Extraction of the analytics
const numberOfFeatures: number = Object.values(this.pricing.features).length;
const numberOfInformationFeatures: number = informationFeatures.length;
const numberOfIntegrationFeatures: number = integrationFeatures.length;
const numberOfIntegrationApiFeatures: number = integrationFeatures.filter(f => f.integrationType === "API").length;
const numberOfIntegrationExtensionFeatures: number = integrationFeatures.filter(f => f.integrationType === "EXTENSION").length;
const numberOfIntegrationIdentityProviderFeatures: number = integrationFeatures.filter(f => f.integrationType === "IDENTITY_PROVIDER").length;
const numberOfIntegrationWebSaaSFeatures: number = integrationFeatures.filter(f => f.integrationType === "WEB_SAAS").length;
const numberOfIntegrationMarketplaceFeatures: number = integrationFeatures.filter(f => f.integrationType === "MARKETPLACE").length;
const numberOfIntegrationExternalDeviceFeatures: number = integrationFeatures.filter(f => f.integrationType === "EXTERNAL_DEVICE").length;
const numberOfDomainFeatures: number = domainFeatures.length;
const numberOfAutomationFeatures: number = automationFeatures.length;
const numberOfBotAutomationFeatures: number = automationFeatures.filter(f => f.automationType === "BOT").length;
const numberOfFilteringAutomationFeatures: number = automationFeatures.filter(f => f.automationType === "FILTERING").length;
const numberOfTrackingAutomationFeatures: number = automationFeatures.filter(f => f.automationType === "TRACKING").length;
const numberOfTaskAutomationFeatures: number = automationFeatures.filter(f => f.automationType === "TASK_AUTOMATION").length;
const numberOfManagementFeatures: number = managementFeatures.length;
const numberOfGuaranteeFeatures: number = guaranteeFeatures.length;
const numberOfSupportFeatures: number = supportFeatures.length;
const numberOfPaymentFeatures: number = paymentFeatures.length;
const numberOfUsageLimits: number = this.pricing.usageLimits ? Object.values(this.pricing.usageLimits).length : 0;
const numberOfRenewableUsageLimits: number = this.pricing.usageLimits ? Object.values(this.pricing.usageLimits).filter(ul => ul.type === "RENEWABLE").length : 0;
const numberOfNonRenewableUsageLimits: number = this.pricing.usageLimits ? Object.values(this.pricing.usageLimits).filter(ul => ul.type === "NON_RENEWABLE").length : 0;
const numberOfPlans: number = this.pricing.plans ? Object.values(this.pricing.plans).length : 0;
const numberOfFreePlans: number = this.pricing.plans ? Object.values(this.pricing.plans).filter(p => p.price === 0).length : 0;
const numberOfPaidPlans: number = this.pricing.plans ? Object.values(this.pricing.plans).filter(p => typeof(p.price) === "number" ? p.price > 0 : true).length : 0;
const numberOfAddOns: number = this.pricing.addOns ? Object.values(this.pricing.addOns).length : 0;
const numberOfReplacementAddons: number = this.pricing.addOns ? Object.values(this.pricing.addOns).filter(a => a.features || a.usageLimits).length : 0;
const numberOfExtensionAddons: number = this.pricing.addOns ? Object.values(this.pricing.addOns).filter(a => a.features || a.usageLimits ? false : a.usageLimitsExtensions).length : 0;
const configurationSpaceSize: number = configurationSpaceResult.statistics.nSolutions;
const minSubscriptionPrice: number = configurationSpaceSize !== 0 ? this._computeConfigurationPrice(minSubscriptionPriceResult.solution!.output.json!) : NaN;
const maxSubscriptionPrice: number = configurationSpaceSize !== 0 ? this._computeConfigurationPrice(maxSubscriptionPriceResult.solution!.output.json!) : NaN;
return {
numberOfFeatures: numberOfFeatures,
numberOfInformationFeatures: numberOfInformationFeatures,
numberOfIntegrationFeatures: numberOfIntegrationFeatures,
numberOfIntegrationApiFeatures: numberOfIntegrationApiFeatures,
numberOfIntegrationExtensionFeatures: numberOfIntegrationExtensionFeatures,
numberOfIntegrationIdentityProviderFeatures: numberOfIntegrationIdentityProviderFeatures,
numberOfIntegrationWebSaaSFeatures: numberOfIntegrationWebSaaSFeatures,
numberOfIntegrationMarketplaceFeatures: numberOfIntegrationMarketplaceFeatures,
numberOfIntegrationExternalDeviceFeatures: numberOfIntegrationExternalDeviceFeatures,
numberOfDomainFeatures: numberOfDomainFeatures,
numberOfAutomationFeatures: numberOfAutomationFeatures,
numberOfBotAutomationFeatures: numberOfBotAutomationFeatures,
numberOfFilteringAutomationFeatures: numberOfFilteringAutomationFeatures,
numberOfTrackingAutomationFeatures: numberOfTrackingAutomationFeatures,
numberOfTaskAutomationFeatures: numberOfTaskAutomationFeatures,
numberOfManagementFeatures: numberOfManagementFeatures,
numberOfGuaranteeFeatures: numberOfGuaranteeFeatures,
numberOfSupportFeatures: numberOfSupportFeatures,
numberOfPaymentFeatures: numberOfPaymentFeatures,
numberOfUsageLimits: numberOfUsageLimits,
numberOfRenewableUsageLimits: numberOfRenewableUsageLimits,
numberOfNonRenewableUsageLimits: numberOfNonRenewableUsageLimits,
numberOfPlans: numberOfPlans,
numberOfFreePlans: numberOfFreePlans,
numberOfPaidPlans: numberOfPaidPlans,
numberOfAddOns: numberOfAddOns,
numberOfReplacementAddons: numberOfReplacementAddons,
numberOfExtensionAddons: numberOfExtensionAddons,
configurationSpaceSize: configurationSpaceSize,
minSubscriptionPrice: parseFloat(minSubscriptionPrice.toFixed(2)),
maxSubscriptionPrice: parseFloat(maxSubscriptionPrice.toFixed(2)),
};
}catch(e){
throw new Error((e as ErrorMessage).message);
}
}
async _getConfigurationSpace(dznPricing: string) {
const model = new PricingCSP();
return model.runPricingOperation(PricingOperation.CONFIGURATION_SPACE, dznPricing);
}
async _getMinSubscriptionPrice(dznPricing: string) {
const model = new PricingCSP();
return model.runPricingOperation(PricingOperation.CHEAPEST_SUBSCRIPTION, dznPricing);
}
async _getMaxSubscriptionPrice(dznPricing: string) {
const model = new PricingCSP();
return model.runPricingOperation(PricingOperation.MOST_EXPENSIVE_SUBSCRIPTION, dznPricing);
}
_computeConfigurationPrice(minizincSolution: Record<string, any>): number {
const plansPrices = getPlanPrices(this.pricing.plans);
const addOnPrices = getAddOnPrices(this.pricing.addOns);
let configurationPrice = 0;
if (plansPrices.length > 0){
configurationPrice += plansPrices[minizincSolution.selected_plan - 1];
}
if (addOnPrices.length > 0){
for (let i = 0; i < minizincSolution.selected_addons.length; i++){
const item = minizincSolution.selected_addons[i];
if (item === 1){
configurationPrice += addOnPrices[i];
}
}
}
return configurationPrice;
}
}