Zoomed Image

How Rules Are Evaluated

Software Asset Management Guide
Customizing the Calculation

How Rules Are Evaluated

Before you change any rule, you need to know the order the engine processes them. This page walks through what the engine does on each iteration and what the rule blocks contribute.

The Engine's Loop

The engine processes one product at a time. For each product:

  1. Load data sources — query the candidate licenses, the consumptions, and the relationship data for this product
  2. Apply calculated fields — evaluate the Set expressions to derive working values for both consumptions and licenses
  3. Apply rules — for each license/consumption pair, run the requirements (exclusions) and the affinity rules (scoring)
  4. Allocate — sort license candidates by affinity score, grant in order, respect capacity

Steps 2 and 3 happen for every license/consumption pair. Step 4 happens once per product.

The Four Rule Blocks

The Configure dialog separates rules into four blocks. Each block has a specific role and runs at a specific point in the loop.

Data Sources

Defines where the engine gets its inputs. The default sources:

Source From Purpose
LicenseableProduct Calculate Software License Products Set of products to process
License Calculate Software License Entitlements Candidate licenses for each product
Consumption Calculate Software License Consumptions Demand to be covered
SoftwareRelationship Calculate Software License Software Relationships Defining-title links
ProductRelationship Calculate Software License Product Relationships Upgrade chains, downgrade rights
ExistingAward Calculate Software License Existing Awards Direct assignments to honor

You normally do not change these — they are the supported data feeds. Custom data sources are an advanced topic and require AMSX scripting.

Calculated Fields

Set expressions that derive values onto the consumption or license records before requirements/affinity run. The defaults:

Set Consumption.PrefersServerLicense = IIF(Consumption.CPUCores >= 16, 1, 0)
Set Consumption.PrefersCoreLicense   = IIF(Consumption.CPUCores <= 8, 1, 0)
Set License.IsServerLicense          = IIF(License.IsCoreLicense = 0, 1, 0)
Set License.IsCoreLicense            = IIF(License.IsCoreLicense = 1, 1, 0)

These let later rules talk in terms of derived booleans — "does this consumption prefer a core license?" — without having to embed the threshold logic in every affinity rule. The License.IsCoreLicense self-reference looks tautological but is there to force the field present and integer-typed on every license row; without it, licenses with a null underlying flag would break the affinity comparison.

You add calculated fields when you need to introduce a synthetic value (e.g., "is this license adobe-gated?") that requirements or affinity rules will then compare against.

Requirements

Hard exclusions. The default:

Requirement Consumption.LocationID within License.LocationID

If the requirement is not satisfied, the license is excluded entirely from consideration for this consumption. Requirements run before affinity scoring — there is no point scoring a license that has been excluded.

Affinity Rules

Scoring contributions. Each matching rule adds its weight to the license/consumption pair's total. The default rules and their weights are listed in Affinity Weights and the Default Rule Set.

After all affinity rules have run, the engine sorts candidate licenses by total score (highest first) and allocates in order until capacity is exhausted or all consumptions are covered.

Order Within a Block

Within each block, rule order generally does not matter — affinity is additive (every matching rule contributes), requirements are AND-combined (any failing requirement excludes), and calculated fields cannot reference other calculated fields' computed values within the same iteration.

The exception is the calculated fields block: a Set cannot depend on another Set from the same block, because both run in a single pass. If you need chained derivations, use compound expressions in a single Set:

// WRONG — second Set cannot see first Set's value reliably
Set Consumption.IsBigServer = IIF(Consumption.CPUCores >= 16, 1, 0)
Set Consumption.WantsProcLicense = IIF(Consumption.IsBigServer = 1, 1, 0)

// RIGHT — combine into one expression
Set Consumption.WantsProcLicense = IIF(Consumption.CPUCores >= 16, 1, 0)

What Calculated Fields Can Reference

Calculated fields can reference:

  • Any column on the entity (Consumption, License, etc.) returned by the data source
  • Sentinel constants (numbers, strings, booleans)
  • The result of IIF, ISNULL, basic arithmetic, and string operations

They cannot reference:

  • Other calculated fields from the same iteration
  • The result of requirements or affinity rules
  • The current loop's product or score

A Worked Iteration

For one product (Microsoft 365 Apps for Business, 50-seat license, one consumption on a London asset):

Step What Happens
1. Load Engine pulls 1 license and 1 consumption from the sources
2. Calc fields Consumption.PrefersServerLicense = 0 (assume small CPU). License.IsServerLicense = 1
3. Requirements Location requirement: consumption is within license location → eligible
4. Affinity Department exact match: +3000. Location exact: +800. Custodian: +1000. Total: 4800
5. Allocate Highest score wins. License has capacity. Grant posted

Multiply this loop by every product, every consumption, every candidate license, and you have the full calculation.