Calculation type

CalculationBuilder

Composes a user-defined ordered list of other Calculation objects and CalculationOperator tokens (arithmetic operators and parentheses) into a per-charge algebraic expression, evaluated numerically and multiplied by the charge's real units to produce the base amount.

172
uses
11
customers
Base
category
Derived
applies at

What it computes

If ExpressionBuilder.Count==1: delegate directly to ApplyBaseCalculation(ExpressionBuilder[0], context) and return (no expression built). Otherwise, BuildExpressions: for each entry — if a Calculation, run ApplyBaseCalculation on a COPY of the context (every charge's Units clamped to Math.Min(1, charge.Units); copy.Details[0] pre-seeded to 0 with calc=null) and, per chargeIndex, append copy.Details[chargeIndex].Amount to that index's expression string if copy.Details[chargeIndex].Calculated, else permanently mark calculatable[chargeIndex]=false; if a CalculationOperator, append its operator string (including parens) to every chargeIndex's expression. Then CalculateExpressions: accountLevelCalculation = (calc.CalculationLevel==Account); for each chargeIndex: skip if IgnoreCharge; if accountLevelCalculation && chargeIndex!=0, SetBase(0); else if !accountLevelCalculation && chargeIndex==0, SetBase(0); else if calculatable[chargeIndex], expressionResult=Calculator.TryCalculateDecimal(expression); if parsed, amount = context.Charges[chargeIndex].Units (the REAL, unclamped units) * expressionResult, SetBase(amount); if not calculatable, no SetBase call at all for that index.

Parameters

KeyMeaning
ExpressionBuilderList<object> alternating Calculation and CalculationOperator entries (TwoTypeListField of BaseCalculationField and EnumField<CalculationOperator>); defines the arithmetic expression tree, e.g. calc1 * calc2 or (calc1 + calc2).

Gotchas

CopyContext clamps every sub-charge's Units to Math.Min(1, actual) — a 0-unit charge is priced with 0 units inside every sub-calculation, while the FINAL multiply-by-units at the end of CalculateExpressions uses the charge's real, unclamped Units from the outer context — so for a multi-term expression like calc1+calc2, each sub-calc is evaluated as if Units=1, summed, and only the sum is scaled by the true unit count once (not each addend independently scaled by units). calculatable[chargeIndex] is a single flag that, once set false by any sub-calculation leaving that index uncalculated, is never reset — a charge that the FIRST sub-calc fails to calculate is skipped for the rest of the builder's output even if later sub-calcs would have priced it. Class declares only IBaseCalculation, never IAdjustment; ApplyAdjustment has no case for it and would hit the default:throw if ever placed in an adjustment list. CalculationLevel is computed (returns the CalculationLevel of the first Calculation found in ExpressionBuilder, via List.Find) rather than a stored/settable field, and the schema's CalculationBuilderCalculationField.Verify() (CustomerSchema.cs:3087) throws at config time if sub-calculations mix Account- and Charge-level, but nothing re-checks that invariant at run time in CalculationBuilderCalculator itself.

In the source

class · /home/cary/src/mdc/MdClarity/MDClarity/MDClarityCore/Backend/CustomerDataModel.cs:556
calculator · /home/cary/src/mdc/MdClarity/MDClarity/MDClarityCore/Backend/PricingEngine/Calculations/CalculationBuilderCalculator.cs
dispatch · /home/cary/src/mdc/MdClarity/MDClarity/MDClarityCore/Backend/CalculationEngine.cs:699-701

CORRECTED — verified CalculationBuilderCalculation (CustomerDataModel.cs:556-605, declares `: Calculation, IBaseCalculation` only) and CalculationBuilderCalculator.cs in full, plus the dispatch switch (ApplyBaseCalculation:699-701, confirmed absent from ApplyAdjustment via grep). Draft's category was 'Both' — wrong, this type has no IAdjustment and never appears in the ApplyAdjustment switch, so it is Base only. Draft's application_level was 'Varies (configurable)' — wrong, there is no Level field on this class at all (unlike FlatFee/FlatPercentOfCharges which genuinely are configurable); CalculationLevel is computed from the first sub-calculation, so 'Derived' is the accurate application_level. Matches critique.md's finding on this type.