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:
- Load data sources — query the candidate licenses, the consumptions, and the relationship data for this product
- Apply calculated fields — evaluate the
Setexpressions to derive working values for both consumptions and licenses - Apply rules — for each license/consumption pair, run the requirements (exclusions) and the affinity rules (scoring)
- 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.