Calculation type
Implements the CMS Multiple Procedure Payment Reduction (MPPR) for diagnostic imaging: qualifying charges are split per-unit into Technical (TC) and/or Professional (26) synthetic components, each component family is independently re-priced and ranked, and a rank-based percentage schedule reduces all but the top-ranked component in each family.
rollups = IMultipleProcedureScheduleRepo.GetCodeScheduleRecords(context, ScheduleType); a charge qualifies if !IgnoreCharge, rollup!=null, no IgnoredModifierTypes code present on ModifierDimensionKey, rollup.Code is in BaseCodes, and it does NOT already carry BOTH TC and 26 modifiers. Each qualifying charge is split per-unit (unit=1..charge.Units) into a componentChargeNumber-tagged entry: charges with neither TC nor 26 ('global') are duplicated into BOTH the TC and 26 component lists; charges with only TC go to the TC list; only-26 go to the 26 list. Each component list is re-priced (CalculateSplitCharges builds a synthetic charge per component using ONLY the resolved base-code dimension member plus the TC/26 modifier, dropping every other code on the original charge, then runs calculationEngine.CalculateAmountAllowedBase) and independently sorted descending by per-unit AmountAllowed or AmountCharged via SortCharges (PEAmount throws 'Unrecognized sort order'). For each affected (original, pre-split) charge: reductionFraction(list, rank) = 1 - percentages[rank]/100 if rank < percentages.Count, else 1 - percentages[last]/100, else 0 if percentages is empty; adjustmentAmount = reductionFraction(TC list, TC rank) * TC-component AmountAllowed + reductionFraction(26 list, 26 rank) * 26-component AmountAllowed (both terms only for global charges; only the relevant term for TC-only/26-only charges); this is raw decimal multiplication with no MoneyMath rounding call. Then: if adjustmentAmount > detail.Amount, Adjust(0.0M); else if adjustmentAmount > 0.0M, Adjust(detail.Amount - adjustmentAmount); else if adjustmentAmount==0.0M and more than one charge was affected overall, Adjust(detail.Amount) unchanged (re-stamp).
| Key | Meaning |
|---|---|
| ScheduleType | Fee/rollup schedule used to fetch each charge's rollup CodeScheduleRecord and, via IScheduleCache.GetScheduleType, the ChargeCodeDimension used to rebuild the split TC/26 synthetic charges (default ProcedureCode). |
| BaseCodes | List of rollup codes (default "4" / DefaultBaseCode) a charge's rollup Code must be in to qualify for the MPPR split. |
| AdjustmentTCPercentages | Ordered percent-to-pay list by rank for Technical-component charges (default [100, 50]); consumed as `1 - value/100` reduction fraction, i.e. it is the fraction to SUBTRACT, not the fraction paid. |
| Adjustment26Percentages | Same as AdjustmentTCPercentages but for Professional-component (26 modifier) charges (default [100]). |
| IgnoredModifierTypes | Modifier codes (default "59") that exempt a charge entirely from this adjustment when present on ModifierDimensionKey. |
| ModifierDimensionKey | Dimension key used to test for TC/26/ignored modifiers on a charge (set via a DimensionReferenceField named 'ModifierDimension' in the UI). |
| SortBy | SortChargesBy enum (AmountAllowed or AmountCharged only) ranking the TC and 26 component lists independently, per-unit, descending; this class implements its own local SortCharges rather than calling the shared CalculationEngine.GetChargeOrder, but with the same per-unit-descending semantics and the same PEAmount-throws behavior. |
Gotchas
1) Unlike MultipleProcedureAdjustment/MultipleSurgeryAdjustment/MultipleEndoscopyAdjustment, this calculator never calls MoneyMath - the percentage*AmountAllowed products and their sum are raw, unrounded decimal arithmetic, another entry in the engine's inconsistent-rounding surface. 2) The percentages configured are payment percentages by name ("100, 50") but GetPercentageFromCalculationPercentagesByUnitNumber converts them to `1 - value/100` REDUCTION fractions before use - a reimplementer who treats the returned value as 'fraction to keep' will invert the adjustment. 3) An empty percentages list yields reduction fraction 0 for every rank (the `else percentages.Any()` branch is skipped, `retPercentage` stays at its 0 initializer) - meaning an unconfigured TC or 26 percentage list results in NO reduction at all (full payment), not a zero payment. 4) The synthetic charge built for re-pricing each TC/26 component carries ONLY the resolved base-code dimension member plus the TC/26 modifier - every other code/dimension on the original charge (other modifiers, place-of-service, etc.) is dropped, so fee-schedule rules keyed on those other dimensions silently don't apply to the re-priced component amount. 5) componentChargeNumber is a single counter shared and incremented across ALL qualifying charges' units (not reset per charge), used only to correlate entries between the AffectedCharges/TC/26 lists after independent sorting - it is not the same as the original charge index.
class · /home/cary/src/mdc/MdClarity/MDClarity/MDClarityCore/Backend/PricingEngine/Adjustments/MultipleRadiologyAdjustment.cs:6
calculator · /home/cary/src/mdc/MdClarity/MDClarity/MDClarityCore/Backend/PricingEngine/Adjustments/Calculators/MultipleRadiologyAdjustmentCalculator.cs:13
dispatch · /home/cary/src/mdc/MdClarity/MDClarity/MDClarityCore/Backend/CalculationEngine.cs:794-798
CONFIRMED - re-read MultipleRadiologyAdjustment.cs and MultipleRadiologyAdjustmentCalculator.cs in full; the draft's formula was unusually precise for this batch and matched the code including the percentage-inversion semantics and the empty-list fallback, apart from omitting the rounding, dimension-dropping, and shared-counter details now added as gotchas.