Skip to main content

Endpoint

curl -X POST http://localhost:8000/validate \
  -F "[email protected]"

POST /validate

Validates a SaaS pricing model provided as a YAML file and returns the configuration space of all valid subscription combinations. Implementation: See org.isa.pricing.csp.controller.CSPController:33
file
file
required
The YAML file containing the pricing model in Pricing2YAML format. Must be uploaded as multipart/form-data.

Response

The endpoint returns a CSPOutput object with the validation results.
messageType
MessageType
required
Indicates the outcome of the validation:
  • SUCCESS - The pricing model is valid and solvable
  • VALIDATION_ERROR - Constraint violation found in the model
  • FILE_ERROR - File path invalid or file not found
  • YAML_ERROR - YAML syntax error
  • PARSER_ERROR - Error parsing the pricing model
configurationSpace
ConfigurationSpace
The set of all valid subscription configurations. Only present when messageType is SUCCESS.
errors
array
List of error messages. Present when messageType is not SUCCESS. Each error describes a constraint violation or parsing issue.
model
string
A string representation of the Choco constraint model (for debugging).
variables
object
Debug information containing extracted features, plans, add-ons, and matrices from the pricing model.

Examples

Success Response

A valid pricing model returns all valid subscription configurations:
{
  "messageType": "SUCCESS",
  "configurationSpace": {
    "subscriptions": [
      {
        "subscription": {
          "plan": "Pro",
          "addOns": []
        },
        "cost": 12.5
      },
      {
        "subscription": {
          "plan": "Pro",
          "addOns": ["Advanced Security"]
        },
        "cost": 20.0
      },
      {
        "subscription": {
          "plan": "Business+",
          "addOns": []
        },
        "cost": 15.0
      },
      {
        "subscription": {
          "plan": "Business+",
          "addOns": ["Advanced Security"]
        },
        "cost": 22.5
      }
    ],
    "cardinality": 4
  },
  "model": "Model[iPricing Validation]",
  "variables": {
    "features": [
      "messagesAccess",
      "voiceAndVideoCalls",
      "customWorkflowSteps",
      "ssoSaml"
    ],
    "plans": ["Pro", "Business+"],
    "addOns": ["Advanced Security"],
    "plansPrices": [12.5, 15.0],
    "addOnsPrices": [7.5]
  }
}

Validation Error Response

An invalid model with constraint violations:
{
  "messageType": "VALIDATION_ERROR",
  "errors": [
    "Plan Starter and plan Basic are exactly the same!"
  ],
  "model": "Model[iPricing Validation]",
  "variables": {
    "features": ["feature1", "feature2"],
    "plans": ["Starter", "Basic"],
    "addOns": []
  }
}

File Error Response

Errors related to file upload or YAML parsing:
{
  "messageType": "FILE_ERROR",
  "errors": [
    "FilePathError: Either the file path is invalid or the file does not exist."
  ]
}

How the Solver Works

The solver processes a pricing model through several stages:

Stage 1: Parsing and Data Extraction

The YAML file is parsed using Pricing4Java into a PricingManager object (org.isa.pricing.csp.parser.Yaml2CSP:55):
Map<String, Object> configFile = yaml.load(inputStream);
YamlUpdater.update(configFile);
PricingManager pricingManager = PricingManagerParser.parseMapToPricingManager(configFile);
The service extracts structured data including features, usage limits, plans, add-ons, and their relationships.

Stage 2: Model Initialization

A Choco Model is created with variables (org.isa.pricing.csp.service.CSPService:196):
Model model = new Model("iPricing Validation");
IntVar selectedPlan = model.intVar("selectedPlan", 0, plans.size() - 1);
IntVar[] selectedAddOns = model.intVarArray("selectedAddOns", addOns.size(), 0, 1);
IntVar subscriptionCost = model.intVar("subscriptionCost", 0, UNLIMITED);
  • selectedPlan: Integer variable representing the chosen plan (index)
  • selectedAddOns: Binary array where 1 means the add-on is selected
  • subscriptionCost: Total cost in cents

Stage 3: Data Integrity Validation

Before posting constraints, the service performs pure data validation (org.isa.pricing.csp.service.CSPService:224):
1

Plan Validation

  • Each plan must have all features defined
  • Each plan must have at least one feature enabled
  • Plans must not be duplicates
2

Add-on Validation

  • Each add-on must have correct feature/usage limit dimensions
  • Availability, dependency, and exclusion matrices must be binary (0 or 1)
  • Each add-on must be available for at least one plan
  • Each add-on must provide at least one feature, usage limit, or extension
3

Feature Validation

  • Features must be reachable (at least one plan or add-on enables them)
  • Linked features must have corresponding positive usage limits
4

Usage Limit Validation

  • Usage limits must be non-negative
  • If a feature is enabled and linked to usage limits, at least one linked limit must be positive

Stage 4: Constraint Posting

The service posts constraints to the Choco model (org.isa.pricing.csp.service.CSPService:650):

Existence Constraint

// At least one plan or add-on must be selected
model.or(
    model.arithm(selectedPlan, ">=", 0),
    model.sum(selectedAddOns, ">", 0)
).post();

Availability Constraint

// Selected add-ons must be available for the selected plan
for (int i = 0; i < addOns.size(); i++) {
    model.ifThen(
        model.arithm(selectedAddOns[i], "=", 1),
        model.element(
            model.intVar(1),
            addOnsAvailableFor[i],  // Binary array
            selectedPlan
        )
    );
}

Dependency Constraint

// If add-on A depends on add-on B, selecting A forces B to be selected
if (addOnsDependsOn[i][j] == 1) {
    model.ifThen(
        model.arithm(selectedAddOns[i], "=", 1),
        model.arithm(selectedAddOns[j], "=", 1)
    );
}

Exclusion Constraint

// If add-on A excludes add-on B, they cannot both be selected
if (addOnsExcludes[i][j] == 1) {
    model.ifThen(
        model.arithm(selectedAddOns[i], "=", 1),
        model.arithm(selectedAddOns[j], "=", 0)
    );
}

Cost Constraint

// Plan cost = price of selected plan
model.element(planPriceVar, planPriceCents, selectedPlan).post();

// Add-on cost = selectedAddOns[i] * price[i]
for (int i = 0; i < addOns.size(); i++) {
    model.scalar(
        new IntVar[]{selectedAddOns[i]},
        new int[]{addOnPriceCents[i]},
        "=",
        addOnCostVars[i]
    ).post();
}

// Total cost = plan cost + sum of add-on costs
model.arithm(subscriptionCost, "=", planPriceVar, "+", totalAddOnsCost).post();

Stage 5: Solving

The Choco solver finds all valid solutions (org.isa.pricing.csp.service.CSPService:944):
Solver solver = model.getSolver();
solver.propagate();  // Reduce domains via constraint propagation
List<Solution> allSolutions = solver.findAllSolutions();
For each solution, the service:
  1. Extracts the selected plan index
  2. Identifies which add-ons are selected (value = 1)
  3. Calculates the total cost
  4. Builds a SubscriptionItem object
If propagation fails (no solutions exist), the explanation engine identifies which constraints are unsatisfiable.

Constraint Programming Concepts

The CSP Service models pricing validation as a Constraint Satisfaction Problem (CSP), which consists of:
  • Variables with finite domains (e.g., selected plan ∈ )
  • Constraints that restrict valid variable combinations
  • A solution assigns values to all variables while satisfying all constraints

Key CSP Techniques Used

Choco uses propagation to reduce variable domains before search. When a constraint is posted, it removes values from variable domains that cannot participate in any valid solution.Example: If add-on A depends on add-on B, and B is not available for plan X, then propagation will set selectedAddOns[A] = 0 when selectedPlan = X.
Constraints can be reified into Boolean variables. This allows encoding “if-then” logic:
model.ifThen(condition, consequence);
Used extensively for add-on availability, dependencies, and exclusions.
Element constraints index into arrays using variables:
model.element(result, array, index);
// result = array[index]
Used to select the plan’s price based on the selected plan variable.
High-level constraints that encapsulate complex logic:
  • sum() - Sum of variables equals/greater than a value
  • scalar() - Weighted sum (used for cost calculation)
  • or() - Disjunction of constraints (at least one must hold)
These are more efficient than decomposing into primitive constraints.
When a model is unsatisfiable, Choco’s explanation engine traces back through constraint propagations to identify the minimal set of constraints causing the conflict.This provides detailed error messages like:
Plan 'Pro' must have the feature 'sso' enabled because 
it has a positive usage limit ('maxSsoConnections') 
linked to that feature.

Performance Optimization

The CSP Service includes several optimizations:

Constant Variable Folding

Instead of creating decision variables for fixed values, the service creates constant variables:
int featureValue = plansFeatures[plan][feature];
IntVar featureVar = model.intVar("plan_" + plan + "_feature_" + feature, 
                                  featureValue, featureValue);
This allows constraint posting without introducing search overhead.

Early Failure Detection

The service performs data validation before constraint posting to fail fast on basic errors:
if (!validateDataIntegrity(wrapper, pricingManager)) {
    return wrapper.output;  // Return immediately without solving
}

Scaled Integer Encoding

Prices and usage limits are scaled to integers:
  • Prices: Converted to cents (multiply by 100)
  • Usage limits: Scaled by 1000
This avoids floating-point constraints, which are slower and less precise.
solver.propagate();  // Fail immediately if unsatisfiable
List<Solution> solutions = solver.findAllSolutions();
Explicit propagation detects unsatisfiability without entering search, saving time.

Integration with Analysis API

The Analysis API calls the CSP Service via HTTP:
Analysis API → CSP Service
const formData = new FormData();
formData.append('file', yamlBuffer, { filename: 'pricing.yml' });

const response = await axios.post(
  `${CHOCO_API_URL}/validate`,
  formData,
  { headers: formData.getHeaders() }
);

const { configurationSpace, errors } = response.data;
The Analysis API uses the configuration space to:
  • Report model validity
  • Count valid configurations (cardinality)
  • Provide detailed error explanations
  • Cache results for subsequent queries

Error Types Reference

Causes:
  • File path does not exist
  • File is not readable
  • File is empty
Example:
{
  "messageType": "FILE_ERROR",
  "errors": [
    "FilePathError: Either the file path is invalid or the file does not exist."
  ]
}
Causes:
  • Invalid YAML syntax
  • Malformed structure
  • Incorrect indentation
Example:
{
  "messageType": "YAML_ERROR",
  "errors": [
    "YAMLError: The YAML file is not in the correct format."
  ]
}
Causes:
  • Invalid Pricing2YAML format
  • Missing required fields
  • Type mismatches
  • References to non-existent features/limits
Examples:
{
  "messageType": "PARSER_ERROR",
  "errors": [
    "FeatureNotFoundException: Feature 'sso' not found in model"
  ]
}
{
  "messageType": "PARSER_ERROR",
  "errors": [
    "InvalidValueTypeException: Feature 'maxUsers' has invalid valueType"
  ]
}
Causes:
  • Constraint violations in the pricing model
  • Logical inconsistencies
  • Duplicate plans or add-ons
  • Unreachable features or usage limits
  • Invalid dependencies or exclusions
Examples:
{
  "messageType": "VALIDATION_ERROR",
  "errors": [
    "Plan Starter and plan Basic are exactly the same!"
  ]
}
{
  "messageType": "VALIDATION_ERROR",
  "errors": [
    "Feature 'ssoSaml' is unreachable. For a BOOLEAN feature, at least one plan or add-on must set it to true."
  ]
}
{
  "messageType": "VALIDATION_ERROR",
  "errors": [
    "Add-On 'Premium Support' must be available for at least one plan"
  ]
}

Testing the Endpoint

You can test the endpoint using sample YAML files from the project:
Test with Slack 2024 pricing
curl -X POST http://localhost:8000/validate \
  -F "file=@data/pricings/yaml/TSC'25/slack/2024.yml"
Test with invalid model (duplicate plans)
curl -X POST http://localhost:8000/validate \
  -F "file=@data/pricings/yaml/synthetic/plans/plan-same-features-same-prices/bad.yml"
Test with circular dependency
curl -X POST http://localhost:8000/validate \
  -F "file=@data/pricings/yaml/synthetic/add-ons/addon-circular-dependency/bad.yml"

CSP Overview

Learn about the CSP Service architecture and capabilities

Analysis API

See how the Analysis API uses the CSP Service

Pricing Models

Understand the YAML pricing model format

Choco Solver Docs

Official Choco Solver documentation

Build docs developers (and LLMs) love