Calculation type
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.
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.
| Key | Meaning |
|---|---|
| ExpressionBuilder | List<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.
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.