Difference between revisions of "MediaWiki:Gadget-calculator-anatomyPhysiology.js"

From WikiAnesthesia
 
(41 intermediate revisions by the same user not shown)
Line 1: Line 1:
/**
* @author Chris Rishel
*/
( function() {
( function() {
     var COOKIE_EXPIRATION = 12 * 60 * 60;
     mw.calculators.addUnitsBases( {
        bpm: {
            toString: function( units ) {
                units = units.replace( 'bpm', 'beats/min' );


    var TYPE_NUMBER = 'number';
                 return units;
    var TYPE_STRING = 'string';
 
    var VALID_TYPES = [
        TYPE_NUMBER,
        TYPE_STRING
    ];
 
    var DEFAULT_CALCULATION_CLASS = 'SimpleCalculation';
    var DEFAULT_CALCULATOR_CLASS = 'SimpleCalculator';
 
    // Polyfill to fetch unit's base. This may become unnecessary in a future version of math.js
    math.Unit.prototype.getBase = function() {
        for( var iBase in math.Unit.BASE_UNITS ) {
            if( this.equalBase( math.Unit.BASE_UNITS[ iBase ] ) ) {
                 return iBase;
             }
             }
         }
         },
 
         hgb: {
        return null;
            toString: function( units ) {
    };
                units = units.replace( 'hgbperdL', '/dL' );
 
                 units = units.replace( /\s?pcthct/, '%' );
 
    mw.calculators = {
        calculators: {},
         calculations: {},
        objectClasses: {},
        units: {},
        unitsBases: {},
        variables: {},
        addCalculations: function( calculationData, className ) {
            className = className ? className : DEFAULT_CALCULATION_CLASS;
 
            var calculations = mw.calculators.createCalculatorObjects( className, calculationData );
 
            for( var calculationId in calculations ) {
                 var calculation = calculations[ calculationId ];
 
                mw.calculators.calculations[ calculationId ] = calculation;


                 mw.calculators.calculations[ calculationId ].setDependencies();
                 return units;
             }
             }
         },
         },
         addCalculators: function( moduleId, calculatorData, className ) {
         o2: {
             className = className ? className : DEFAULT_CALCULATOR_CLASS;
             toString: function( units ) {
 
                 units = units.replace( /\s?pcto2/, '%' );
            for( var calculatorId in calculatorData ) {
                 calculatorData[ calculatorId ].module = moduleId;
 
                // Make sure the calculations have been defined
                for( var iCalculation in calculatorData[ calculatorId ].calculations ) {
                    var calculationId = calculatorData[ calculatorId ].calculations[ iCalculation ];


                    if( !mw.calculators.getCalculation( calculationId ) ) {
                return units;
                        throw new Error( 'Calculator "' + calculatorId + '" references calculation "' + calculationId + '" which is not defined' );
                    }
                }
             }
             }
        },
        temperature: {
            toString: function( units ) {
                units = units.replace( 'deg', '°' );


            var calculators = mw.calculators.createCalculatorObjects( className, calculatorData );
                 return units;
 
            // Initalize the calculators property for the module
            if( !mw.calculators.calculators.hasOwnProperty( moduleId ) ) {
                 mw.calculators.calculators[ moduleId ] = {};
             }
             }
        }
    } );


             // Store the calculators
    mw.calculators.addUnits( {
             for( var calculatorId in calculators ) {
        bpm: {
                mw.calculators.calculators[ moduleId ][ calculatorId ] = calculators[ calculatorId ];
             baseName: 'bpm'
        },
        pcthct: {
            baseName: 'hgb'
        },
        pcto2: {
             baseName: 'o2'
        },
        ghgbperdL: {
            baseName: 'hgb',
            prefixes: 'short',
            definition: '3 pcthct'
        }
    } );


                mw.calculators.calculators[ moduleId ][ calculatorId ].render();
    mw.calculators.addVariables( {
             }
        fiO2: {
            name: 'FiO<sub>2</sub>',
            type: 'number',
            abbreviation: 'FiO<sub>2</sub>',
            minValue: '10 pcto2',
            maxValue: '100 pcto2',
            defaultValue: '21 pcto2',
            maxLength: 3,
            units: [
                'pcto2'
             ]
         },
         },
         addUnitsBases: function( unitsBaseData ) {
         heartRate: {
             var unitsBases = mw.calculators.createCalculatorObjects( 'UnitsBase', unitsBaseData );
             name: 'Heart rate',
 
            type: 'number',
             for( var unitsBaseId in unitsBases ) {
            abbreviation: 'HR',
                 mw.calculators.unitsBases[ unitsBaseId ] = unitsBases[ unitsBaseId ];
            defaultValue: '60 bpm',
             }
            maxLength: 4,
             units: [
                 'bpm'
             ]
         },
         },
         addUnits: function( unitsData ) {
         hgb: {
             var units = mw.calculators.createCalculatorObjects( 'Units', unitsData );
             name: 'Hemoglobin/hematocrit',
 
             type: 'number',
             for( var unitsId in units ) {
            abbreviation: 'HgB',
                if( mw.calculators.units.hasOwnProperty( unitsId ) ) {
            minValue: '3 ghgbperdL',
                    continue;
            maxValue: '25 ghgbperdL',
                }
            defaultValue: '13 ghgbperdL',
 
            maxLength: 4,
                try {
            units: [
                    var unitData = {
                 'pcthct',
                        aliases: units[ unitsId ].aliases,
                 'ghgbperdL'
                        baseName: units[ unitsId ].baseName ? units[ unitsId ].baseName.toUpperCase() : units[ unitsId ].baseName,
             ]
                        definition: units[ unitsId ].definition,
                        prefixes: units[ unitsId ].prefixes,
                        offset: units[ unitsId ].offset,
                    };
 
                    math.createUnit( unitsId, unitData );
                } catch( e ) {
                    console.warn( e.message );
                 }
 
                 mw.calculators.units[ units ] = units[ unitsId ];
             }
         },
         },
         addVariables: function( variableData ) {
         paCO2: {
             var variables = mw.calculators.createCalculatorObjects( 'Variable', variableData );
             name: 'PaCO<sub>2</sub>',
 
            type: 'number',
             for( var variableId in variables ) {
             abbreviation: 'PaCO<sub>2</sub>',
                mw.calculators.variables[ variableId ] = variables[ variableId ];
            minValue: '20 mmHg',
 
            defaultValue: '40 mmHg',
                var cookieValue = mw.calculators.getCookieValue( variableId );
            maxLength: 3,
 
            units: [
                if( cookieValue ) {
                 'mmHg'
                    try {
             ]
                        // isValueValid will throw an error if invalid, so the catch clause is our else condition
                        if( mw.calculators.variables[ variableId ].isValueValid( cookieValue ) ) {
                            mw.calculators.variables[ variableId ].setValue( cookieValue );
                        }
                    } catch( e ) {
                        // Unset the cookie value since for whatever reason it's no longer valid.
                        mw.calculators.setCookieValue( variableId, null );
                    }
                 }
             }
         },
         },
         createCalculatorObjects: function( className, objectData ) {
         paO2: {
             if( !mw.calculators.objectClasses.hasOwnProperty( className ) ) {
             name: 'PaO<sub>2</sub>',
                throw new Error( 'Invalid class name "' + className + '"' );
             type: 'number',
             }
             abbreviation: 'PaO<sub>2</sub>',
 
             minValue: '25 mmHg',
             var objects = {};
            defaultValue: '100 mmHg',
 
            maxLength: 3,
             for( var objectId in objectData ) {
            units: [
                var propertyValues = objectData[ objectId ];
                 'mmHg'
 
            ]
                // Id can either be specified using the 'id' property, or as the property name in objectData
                if( propertyValues.hasOwnProperty( 'id' ) ) {
                    objectId = propertyValues.id;
                }
                 else {
                    propertyValues.id = objectId;
                }
 
                objects[ objectId ] = new mw.calculators.objectClasses[ className ]( propertyValues );
            }
 
            return objects;
         },
         },
         createInputGroup: function( variableIds ) {
         pAtm: {
             var $form = $( '<form>', {
             name: 'Atmospheric pressure',
 
             type: 'number',
             } );
             abbreviation: 'P<sub>atm</sub>',
 
            minValue: '0 mmHg',
             var $formRow = $( '<div>', {
             defaultValue: '760 mmHg',
                class: 'form-row'
            maxLength: 4,
             } ).css( 'flex-wrap', 'nowrap' );
             units: [
 
                 'mmHg'
             for( var iVariableId in variableIds ) {
            ]
                var variableId = variableIds[ iVariableId ];
 
                 if( !mw.calculators.variables.hasOwnProperty( variableId ) ) {
                    throw new Error( 'Invalid variable name "' + variableId + '"' );
                }
 
                $formRow.append( mw.calculators.variables[ variableId ].createInput() );
            }
 
            return $form.append( $formRow );
         },
         },
         getCookieKey: function( variableId ) {
         saO2: {
             return 'calculators-var-' + variableId;
             name: 'SaO<sub>2</sub>',
            type: 'number',
            abbreviation: 'SaO<sub>2</sub>',
            minValue: '25 pcto2',
            maxValue: '100 pcto2',
            defaultValue: '100 pcto2',
            maxLength: 3,
            units: [
                'pcto2'
            ]
         },
         },
         getCookieValue: function( varId ) {
         smvO2: {
             var cookieValue = mw.cookie.get( mw.calculators.getCookieKey( varId ) );
             name: 'SmvO<sub>2</sub>',
 
            type: 'number',
             if( !cookieValue ) {
            abbreviation: 'SmvO<sub>2</sub>',
                return null;
            minValue: '25 pcto2',
             }
             maxValue: '100 pcto2',
 
            defaultValue: '75 pcto2',
             return cookieValue;
            maxLength: 3,
             units: [
                'pcto2'
             ]
         },
         },
         getCalculation: function( calculationId ) {
         raceSpirometry: {
             if( mw.calculators.calculations.hasOwnProperty( calculationId ) ) {
             name: 'Race',
                 return mw.calculators.calculations[ calculationId ];
            type: 'string',
            } else {
            abbreviation: 'Race',
                 return null;
            defaultValue: 'unknown',
            options: {
                 unknown: 'Unknown',
                black: 'Black',
                caucasian: 'Caucasian',
                 mexican: 'Mexican-American'
             }
             }
         },
         },
         getCalculator: function( moduleId, calculatorId ) {
         temperature: {
             if( mw.calculators.calculators.hasOwnProperty( moduleId ) &&
            name: 'Temperature',
                mw.calculators.calculators[ moduleId ].hasOwnProperty( calculatorId ) ) {
            type: 'number',
                 return mw.calculators.calculators[ moduleId ][ calculatorId ];
            abbreviation: 'Temp',
            } else {
            minValue: '20 degC',
                 return null;
            maxValue: '44 degC',
             }
            defaultValue: '37 degC',
             maxLength: 5,
            units: [
                 'degC',
                 'degF'
             ]
         },
         },
         getUnitsByBase: function( value ) {
         weightBasedTidalVolumePerKgMin: {
             if( typeof value !== 'object' || !value.hasOwnProperty( 'units' ) ) {
             name: 'Minimum tidal volume',
                return null;
            type: 'number',
             }
            abbreviation: 'Min TV',
 
             minValue: '3 mL/kgwt',
             var unitsByBase = {};
            maxValue: '12 mL/kgwt',
 
             defaultValue: '6 mL/kgwt',
             for( var iUnits in value.units ) {
            maxLength: 2,
                var units = value.units[ iUnits ];
             units: [
 
                 'mL/kgwt'
                 unitsByBase[ units.unit.base.key.toLowerCase() ] = units.prefix.name + units.unit.name;
             ]
             }
 
            return unitsByBase;
         },
         },
         getUnitsString: function( value ) {
         weightBasedTidalVolumePerKgMax: {
             if( typeof value !== 'object' ) {
             name: 'Maximum tidal volume',
                return null;
             type: 'number',
             }
             abbreviation: 'Max TV',
 
             minValue: '3 mL/kgwt',
             var unitsString = value.formatUnits();
             maxValue: '12 mL/kgwt',
 
            defaultValue: '8 mL/kgwt',
             var reDenominator = /\/\s?\((.*)\)/;
             maxLength: 2,
             var denominatorMatches = unitsString.match( reDenominator );
             units: [
 
                 'mL/kgwt'
            if( denominatorMatches ) {
            ]
                var denominatorUnits = denominatorMatches[ 1 ];
        }
 
    } );
                unitsString = unitsString.replace( reDenominator, '/' + denominatorUnits.replace( ' ', '/' ) );
             }
 
             unitsString = unitsString
                 .replace( /\s/g, '' )
                .replace( /(\^(\d+))/g, '<sup>$2</sup>' );
 
            var unitsBase = value.getBase();


            if( unitsBase ) {
    // Force re-render of ibw and lbw. This is necessary to remove the additional inputs for patient variables from the
                unitsBase = unitsBase.toLowerCase();
    // calculation which are now provided by the patient input toolbar.
    mw.calculators.calculations.ibw.render();
    mw.calculators.calculations.lbw.render();


                if( mw.calculators.unitsBases.hasOwnProperty( unitsBase ) &&
    mw.calculators.addCalculations( {
                    typeof mw.calculators.unitsBases[ unitsBase ].toString === 'function' ) {
        bmi: {
                     unitsString = mw.calculators.unitsBases[ unitsBase ].toString( unitsString );
            name: 'Body mass index',
            abbreviation: 'BMI',
            data: {
                variables: {
                     required: [ 'weight', 'height' ]
                 }
                 }
             } else {
             },
                // TODO nasty hack to fix weight units in compound units which have no base
            digits: 0,
                unitsString = unitsString.replace( 'kgwt', 'kg' );
            units: 'kg/m^2',
                 unitsString = unitsString.replace( 'ug', 'mcg' );
            formula: '<math>\\mathrm{BMI} = \\frac{\\mathrm{mass_{kg}}}{{(\\mathrm{height_{m}}})^2}</math>',
            link: '[[Body mass index]]',
            references: [],
            calculate: function( data ) {
                 return data.weight.toNumber( 'kgwt' ) / Math.pow( data.height.toNumber( 'm' ), 2 );
             }
             }
            return unitsString;
         },
         },
         getValueDecimals: function( value ) {
         bsa: {
             // Supports either numeric values or math objects
            name: 'Body surface area',
             if( mw.calculators.isValueMathObject( value ) ) {
            abbreviation: 'BSA',
                 value = mw.calculators.getValueNumber( value );
            data: {
            }
                variables: {
 
                    required: [ 'weight', 'height' ]
            if( typeof value !== 'number' ) {
                }
                return null;
            },
            digits: 2,
             units: 'm^2',
            formula: '<math>\\mathrm{BSA} = \\sqrt{\\frac{\\mathrm{weight_{kg}}*\\mathrm{height_{cm}}}{3600}}</math>',
            link: false,
             references: [
                'Mosteller RD. Simplified calculation of body-surface area. N Engl J Med. 1987 Oct 22;317(17):1098. doi: 10.1056/NEJM198710223171717. PMID: 3657876.'
            ],
            calculate: function( data ) {
                 return Math.sqrt( data.height.toNumber( 'cm' ) * data.weight.toNumber( 'kgwt' ) / 3600 );
             }
             }
            // Convert the number to a string, reverse, and count the number of characters up to the period.
            var decimals = value.toString().split('').reverse().join('').indexOf( '.' );
            // If no decimal is present, will be set to -1 by indexOf. If so, set to 0.
            decimals = decimals > 0 ? decimals : 0;
            return decimals;
         },
         },
         getValueNumber: function( value, decimals ) {
         systolicBloodPressure: {
             if( typeof value !== 'object' ) {
            name: 'Systolic blood pressure',
                 return null;
            abbreviation: 'SBP',
            }
            data: {
                variables: {
                    required: [ 'age' ]
                }
             },
            type: 'string',
            references: [
                'Baby Miller 6e, ch. 16, pg. 550'
            ],
            calculate: function( data ) {
                 var age = data.age.toNumber( 'yo' );


            // Remove floating point errors
                var systolicMin, systolicMax, diastolicMin, diastolicMax, meanMin, meanMax;
            var number = math.round( value.toNumber(), 10 );


            var absNumber = math.abs( number );
                if( age >= 16 ) {
 
                    systolicMin = 100;
            if( absNumber >= 10 ) {
                    systolicMax = 125;
                 decimals = 0;
                } else if( age >= 13 ) {
            } else {
                    systolicMin = 95;
                 decimals = -math.floor( math.log10( absNumber ) ) + 1;
                    systolicMax = 120;
                } else if( age >= 9 ) {
                    systolicMin = 90;
                    systolicMax = 115;
                 } else if( age >= 6 ) {
                    systolicMin = 85;
                    systolicMax = 105;
                } else if( age >= 3 ) {
                    systolicMin = 80;
                    systolicMax = 100;
                 } else if( age >= 1 ) {
                    systolicMin = 75;
                    systolicMax = 95;
                } else if( age >= 6 / 12 ) {
                    systolicMin = 70;
                    systolicMax = 90;
                } else if( age >= 1 / 12 ) {
                    systolicMin = 65;
                    systolicMax = 85;
                } else {
                    systolicMin = 60;
                    systolicMax = 75;
                }
             }
             }
        }
    } );


            return math.round( number, decimals );
    // Cardiovascular
        },
    mw.calculators.addCalculations( {
        getValueString: function( value, decimals ) {
        vO2: {
            if( !mw.calculators.isValueMathObject( value ) ) {
             name: 'Rate of oxygen consumption (VO<sub>2</sub>)',
                return null;
             abbreviation: 'VO<sub>2</sub>',
             }
             data: {
 
                 calculations: {
            var valueNumber = mw.calculators.getValueNumber( value, decimals );
                    required: [ 'bsa' ]
             var valueUnits = mw.calculators.getUnitsString( value );
                 },
 
                 variables: {
             if( math.abs( math.log10( valueNumber ) ) > 3 ) {
                     optional: [ 'age' ]
                 var valueUnitsByBase = mw.calculators.getUnitsByBase( value );
 
                 var oldSIUnit;
 
                 if( valueUnitsByBase.hasOwnProperty( 'mass' ) ) {
                     oldSIUnit = valueUnitsByBase.mass;
                } else if( valueUnitsByBase.hasOwnProperty( 'volume' ) ) {
                    oldSIUnit = valueUnitsByBase.volume;
                 }
                 }
            },
            units: 'mL/min',
            formula: '<math>\\mathrm{VO_2} = \\begin{cases} 125 * \\mathrm{BSA}  & \\text{if age} < 70 \\\\ 110 * \\mathrm{BSA} & \\text{if age} \\geq 70 \\end{cases}</math>',
            references: [],
            calculate: function( data ) {
                var bsa = data.bsa.toNumber();
                var age = data.age ? data.age.toNumber( 'yr' ) : null;


                 if( oldSIUnit ) {
                 if( age < 70 ) {
                     // This new value should simplify to the optimal SI prefix.
                     return 125 * bsa;
                    // We need to create a completely new unit from the formatted (i.e. simplified) value
                } else {
                    var newSIValue = math.unit( math.unit( valueNumber + ' ' + oldSIUnit ).format() );
                     return 110 * bsa;
 
                    // There is a bug in mathjs where formatUnits() won't simplify the units, only format() will.
                    var newSIUnit = newSIValue.formatUnits();
 
                     if( newSIUnit !== oldSIUnit ) {
                        var newValue = math.unit( newSIValue.toNumber() + ' ' + value.formatUnits().replace( oldSIUnit, newSIUnit ) );
 
                        valueNumber = mw.calculators.getValueNumber( newValue, decimals );
                        valueUnits = mw.calculators.getUnitsString( newValue );
                    }
                 }
                 }
             }
             }
        },
        cardiacOutputFick: {
            name: 'Cardiac output (Fick)',
            abbreviation: 'CO (Fick)',
            data: {
                variables: {
                    required: [ 'saO2', 'smvO2', 'hgb' ]
                },
                calculations: {
                    required: [ 'vO2' ]
                }
            },
            units: 'L/min',
            formula: '<math>\\mathrm{CO_{Fick}}=\\frac{\\mathrm{VO_2}}{(\\mathrm{S_aO_2} - \\mathrm{S_{mv}O_2}) * H_b * 13.4}</math>',
            link: false,
            references: [],
            calculate: function( data ) {
                var vO2 = data.vO2.toNumber( 'mL/min' );
                var saO2 = data.saO2.toNumber() / 100;
                var smvO2 = data.smvO2.toNumber() / 100;
                var hgb = data.hgb.toNumber( 'ghgbperdL' );


            var valueString = String( valueNumber );
                 return vO2 / ( ( saO2 - smvO2 ) * hgb * 13.4 );
 
            if( valueUnits ) {
                 valueString += ' ' + valueUnits;
            }
 
            return valueString;
        },
        getVariable: function( variableId ) {
            if( mw.calculators.variables.hasOwnProperty( variableId ) ) {
                return mw.calculators.variables[ variableId ];
            } else {
                return null;
            }
        },
        hasData: function( dataType, dataId ) {
            if( mw.calculators.hasOwnProperty( dataType ) &&
                mw.calculators[ dataType ].hasOwnProperty( dataId ) ) {
                return true;
            } else {
                return false;
             }
             }
         },
         },
         initialize: function() {
         cardiacIndex: {
             $( '.calculator' ).each( function() {
             name: 'Cardiac index',
                var gadgetModule = 'ext.gadget.calculator-' + $( this ).attr( 'data-module' );
            abbreviation: 'CI',
 
            data: {
                 if( gadgetModule && mw.loader.getState( gadgetModule ) === 'registered' ) {
                 calculations: {
                     mw.loader.load( gadgetModule );
                     required: [ 'bsa', 'cardiacOutputFick' ]
                 }
                 }
             } );
             },
        },
            units: 'L/min/m^2',
        isMobile: function() {
             formula: '<math>\\mathrm{CI}=\\frac{\\mathrm{CO}}{\\mathrm{BSA}}</math>',
             return window.matchMedia( 'only screen and (max-width: 760px)' ).matches;
            link: false,
        },
             references: [],
        isValueMathObject: function( value ) {
             calculate: function( data ) {
            return value && value.hasOwnProperty( 'value' );
                var cardiacOutput = data.cardiacOutputFick.toNumber( 'L/min' );
        },
                 var bsa = data.bsa.toNumber( 'm^2' );
        setCookieValue: function( variableId, value ) {
             mw.cookie.set( mw.calculators.getCookieKey( variableId ), value, {
                expires: COOKIE_EXPIRATION
             } );
        },
        setValue: function( variableId, value ) {
            if( !mw.calculators.variables.hasOwnProperty( variableId ) ) {
                return false;
            }
 
            if( mw.calculators.variables[ variableId ].setValue( value ) ) {
                 mw.calculators.setCookieValue( variableId, value );


                 return true;
                 return cardiacOutput / bsa;
             }
             }
            return false;
         },
         },
         uniqueValues: function( value, index, self ) {
         strokeVolume: {
             return self.indexOf( value ) === index;
             name: 'Stroke volume',
        }
            abbreviation: 'SV',
    };
             data: {
 
                 variables: {
    /**
                     required: [ 'heartRate' ]
    * Class CalculatorObject
                 },
    *
                 calculations: {
    * @param {Object} properties
                     required: [ 'cardiacOutputFick' ]
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.CalculatorObject}
    * @constructor
    */
    mw.calculators.objectClasses.CalculatorObject = function( properties, propertyValues ) {
        propertyValues = propertyValues ? propertyValues : {};
 
        if( properties ) {
             if( properties.hasOwnProperty( 'required' ) ) {
                 for( var iRequiredProperty in properties.required ) {
                     var requiredProperty = properties.required[ iRequiredProperty ];
 
                    if( !propertyValues || !propertyValues.hasOwnProperty( requiredProperty ) ) {
                        console.error( 'Missing required property "' + requiredProperty + '"' );
                        console.log( propertyValues );
 
                        return null;
                    }
 
                    this[ requiredProperty ] = propertyValues[ requiredProperty ];
 
                    delete propertyValues[ requiredProperty ];
                 }
            }
 
            if( properties.hasOwnProperty( 'optional' ) ) {
                 for( var iOptionalProperty in properties.optional ) {
                     var optionalProperty = properties.optional[ iOptionalProperty ];
 
                    if( propertyValues && propertyValues.hasOwnProperty( optionalProperty ) ) {
                        this[ optionalProperty ] = propertyValues[ optionalProperty ];
 
                        delete propertyValues[ optionalProperty ];
                    } else if( typeof this[ optionalProperty ] === 'undefined' ) {
                        this[ optionalProperty ] = null;
                    }
                 }
                 }
             }
             },
 
            units: 'mL',
             var invalidProperties = Object.keys( propertyValues );
             formula: '<math>\\mathrm{SV}=\\frac{\\mathrm{CO}}{\\mathrm{HR}}</math>',
            link: false,
            references: [],
            calculate: function( data ) {
                var cardiacOutput = data.cardiacOutputFick.toNumber( 'mL/min' );
                var heartRate = data.heartRate.toNumber();


            if( invalidProperties.length ) {
                 return cardiacOutput / heartRate;
                 console.warn( 'Unsupported properties defined for ' + typeof this + ' with id "' + this.id + '": ' + invalidProperties.join( ', ' ) );
             }
             }
         }
         }
     };
     } );


     mw.calculators.objectClasses.CalculatorObject.prototype.getProperties = function() {
    // Neuro
         return {
     mw.calculators.addCalculations( {
             required: [],
         brainMass: {
             optional: []
             name: 'Brain mass',
        };
             data: {
    };
                variables: {
 
                    optional: [ 'age', 'gender' ]
    mw.calculators.objectClasses.CalculatorObject.prototype.mergeProperties = function( inheritedProperties, properties ) {
                }
        var uniqueValues = function( value, index, self ) {
            },
             return self.indexOf( value ) === index;
            digits: 0,
        };
            units: 'gwt',
 
             description: 'This calculation will give a more precise estimate of brain mass if age and/or gender are provided.',
        properties.required = inheritedProperties.required.concat( properties.required ).filter( uniqueValues );
            references: [
        properties.optional = inheritedProperties.optional.concat( properties.optional ).filter( uniqueValues );
                'Dekaban AS. Changes in brain weights during the span of human life: relation of brain weights to body heights and body weights. Ann Neurol. 1978 Oct;4(4):345-56. doi: 10.1002/ana.410040410. PMID: 727739.'
 
        return properties;
    };
 
 
 
 
    /**
    * Class UnitsBase
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.UnitsBase}
    * @constructor
    */
    mw.calculators.objectClasses.UnitsBase = function( propertyValues ) {
        var properties = {
            required: [
                'id'
             ],
             ],
             optional: [
             calculate: function( data ) {
                 'toString'
                 var age = data.age ? data.age.toNumber( 'yr' ) : null;
            ]
                var gender = data.gender ? data.gender : null;
        };


        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
                var brainMassFemale = 1290;
    };
                var brainMassMale = 1450;


    mw.calculators.objectClasses.UnitsBase.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
                if( age !== null ) {
                    if( age <= 10 / 365 ) {
                        // <=10 days
                        brainMassFemale = 360;
                        brainMassMale = 380;
                    } else if( age <= 4 * 30 / 365 ) {
                        // Less than 4 months. This is a gap in the reported data of the paper, so linearly interpolate?
                        var ageFactor = 1 - ( 4 * 30 / 365 - age ) / ( 4 * 30 / 365 - 10 / 365 );


 
                        brainMassFemale = 360 + ageFactor * ( 580 - 360 );
 
                        brainMassMale = 380 + ageFactor * ( 640 - 380 );
 
                    } else if( age <= 8 * 30 / 365 ) {
    /**
                        // <=8 months
    * Class Units
                        brainMassFemale = 580;
    * @param {Object} propertyValues
                        brainMassMale = 640;
    * @returns {mw.calculators.objectClasses.Units}
                    } else if( age <= 18 * 30 / 365 ) {
    * @constructor
                        // <=18 months
    */
                        brainMassFemale = 940;
    mw.calculators.objectClasses.Units = function( propertyValues ) {
                        brainMassMale = 970;
        var properties = {
                    } else if( age <= 30 * 30 / 365 ) {
            required: [
                        // <=30 months
                'id'
                        brainMassFemale = 1040;
            ],
                        brainMassMale = 1120;
            optional: [
                    } else if( age <= 43 * 30 / 365 ) {
                'aliases',
                        // <=43 months
                'baseName',
                        brainMassFemale = 1090;
                'definition',
                        brainMassMale = 1270;
                'offset',
                    } else if( age <= 5 ) {
                'prefixes'
                        brainMassFemale = 1150;
            ]
                        brainMassMale = 1300;
        };
                    } else if( age <= 7 ) {
 
                        brainMassFemale = 1210;
        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
                        brainMassMale = 1330;
    };
                    } else if( age <= 9 ) {
 
                        brainMassFemale = 1180;
    mw.calculators.objectClasses.Units.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
                        brainMassMale = 1370;
 
                    } else if( age <= 12 ) {
 
                        brainMassFemale = 1260;
 
                        brainMassMale = 1440;
 
                    } else if( age <= 15 ) {
    /**
                        brainMassFemale = 1280;
    * Class Variable
                        brainMassMale = 1410;
    * @param {Object} propertyValues
                    } else if( age <= 18 ) {
    * @returns {mw.calculators.objectClasses.Variable}
                        brainMassFemale = 1340;
    * @constructor
                        brainMassMale = 1440;
    */
                    } else if( age <= 21 ) {
    mw.calculators.objectClasses.Variable = function( propertyValues ) {
                        brainMassFemale = 1310;
        var properties = {
                        brainMassMale = 1450;
            required: [
                    } else if( age <= 30 ) {
                'id',
                        brainMassFemale = 1300;
                'name',
                        brainMassMale = 1440;
                'type'
                    } else if( age <= 40 ) {
            ],
                        brainMassFemale = 1290;
            optional: [
                        brainMassMale = 1440;
                'abbreviation',
                    } else if( age <= 50 ) {
                'defaultValue',
                        brainMassFemale = 1290;
                'maxLength',
                        brainMassMale = 1430;
                'options',
                    } else if( age <= 55 ) {
                'units'
                        brainMassFemale = 1280;
            ]
                        brainMassMale = 1410;
        };
                    } else if( age <= 60 ) {
 
                        brainMassFemale = 1250;
        mw.calculators.objectClasses.CalculatorObject.call( this, properties, propertyValues );
                        brainMassMale = 1370;
 
                    } else if( age <= 65 ) {
        if( VALID_TYPES.indexOf( this.type ) === -1 ) {
                        brainMassFemale = 1240;
            throw new Error( 'Invalid type "' + this.type + '" for variable "' + this.id + '"' );
                        brainMassMale = 1370;
        }
                    } else if( age <= 70 ) {
 
                        brainMassFemale = 1240;
        // Accept options as either an array of strings, or an object with ids as keys and display text as values
                        brainMassMale = 1360;
        if( Array.isArray( this.options ) ) {
                    } else if( age <= 75 ) {
            var options = {};
                        brainMassFemale = 1230;
 
                        brainMassMale = 1350;
            for( var iOption in this.options ) {
                    } else if( age <= 80 ) {
                var option = this.options[ iOption ];
                        brainMassFemale = 1190;
 
                        brainMassMale = 1330;
                options[ option ] = option;
                    } else if( age <= 85 ) {
            }
                        brainMassFemale = 1170;
 
                        brainMassMale = 1310;
            this.options = options;
                    } else {
        }
                        brainMassFemale = 1140;
 
                        brainMassMale = 1290;
        this.calculations = [];
                     }
 
        if( this.defaultValue ) {
            this.defaultValue = this.prepareValue( this.defaultValue );
        }
 
        this.value = null;
    };
 
    mw.calculators.objectClasses.Variable.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.Variable.prototype.addCalculation = function( calculationId ) {
        if( this.calculations.indexOf( calculationId ) !== -1 ) {
            return;
        }
 
        this.calculations.push( calculationId );
    };
 
    mw.calculators.objectClasses.Variable.prototype.createInput = function( inputOptions ) {
        if( !inputOptions ) {
            inputOptions = {};
        }
 
        inputOptions.class = inputOptions.hasOwnProperty( 'class' ) ? inputOptions.class : '';
        inputOptions.hideLabel = inputOptions.hasOwnProperty( 'hideLabel' ) ? inputOptions.hideLabel : false;
        inputOptions.hideLabelMobile = inputOptions.hasOwnProperty( 'hideLabelMobile' ) ? inputOptions.hideLabelMobile : false;
        inputOptions.inline = inputOptions.hasOwnProperty( 'inline' ) ? inputOptions.inline : false;
        inputOptions.inputClass = inputOptions.hasOwnProperty( 'inputClass' ) ? inputOptions.inputClass : '';
 
        var variableId = this.id;
        var inputId = 'calculator-input-' + variableId;
 
        var inputContainerTag = inputOptions.inline ? '<span>' : '<div>';
 
        var inputContainerAttributes = {
            class: 'form-group mb-0 calculator-container-input'
        };
 
        inputContainerAttributes.class += inputOptions.class ? ' ' + inputOptions.class : '';
        inputContainerAttributes.class += ' calculator-container-input-' + variableId;
 
        var inputContainerCss = {};
 
        // Initialize label attributes
        var labelAttributes = {
            for: inputId,
            html: this.getLabelString()
        };
 
        if( inputOptions.hideLabel || ( inputOptions.hideLabelMobile && mw.calculators.isMobile() ) ) {
            labelAttributes.class = 'sr-only';
        }
 
        var labelCss = {};
 
        if( inputOptions.inline ) {
            inputContainerTag = '<span>';
 
            inputContainerCss[ 'align-items' ] = 'center';
            inputContainerCss[ 'display' ] = 'flex';
            inputContainerCss[ 'height' ] = 'calc(1.5em + 0.75rem + 2px)';
 
            labelAttributes.html += ':&nbsp;';
            labelCss[ 'margin-bottom' ] = 0;
        }
 
        // Create the input container
        var $inputContainer = $( inputContainerTag, inputContainerAttributes ).css( inputContainerCss );
 
        var $label = $( '<label>', labelAttributes ).css( labelCss );
 
        $inputContainer.append( $label );
 
        var value = this.getValue();
 
        if( this.type === TYPE_NUMBER ) {
            // Initialize the primary units variables (needed for handlers, even if doesn't have units)
            var unitsId = null;
            var $unitsContainer = null;
 
            var inputValue = '';
 
            if( mw.calculators.isValueMathObject( value ) ) {
                var number = value.toNumber();
 
                if( number ) {
                     inputValue = number;
                 }
                 }
            } else {
                inputValue = value;
            }
            // Initialize input options
            var inputAttributes = {
                id: inputId,
                class: 'form-control calculator-input calculator-input-text',
                type: 'text',
                autocomplete: 'off',
                inputmode: 'decimal',
                value: inputValue
            };
            // Configure additional options
            if( this.maxLength ) {
                inputAttributes.maxlength = this.maxLength;
            }
            // Add any additional classes to the input
            inputAttributes.class += inputOptions.inputClass ? ' ' + inputOptions.inputClass : '';
            // Add the input id to the list of classes
            inputAttributes.class += ' ' + inputId;
            // If the variable has units, create the units input
            if( this.hasUnits() ) {
                // Set the units id
                unitsId = inputId + '-units';
                var unitsValue = mw.calculators.isValueMathObject( value ) ? value.formatUnits() : null;
                var unitsInputAttributes = {
                    id: unitsId
                };
                // Create the units container
                $unitsContainer = $( '<div>', {
                    class: 'input-group-append'
                } ).css( 'align-items', 'center' );
                if( this.units.length === 1 ) {
                    unitsInputAttributes.type = 'hidden';
                    unitsInputAttributes.value = this.units[ 0 ];


                    $unitsContainer
                if( gender === 'F' ) {
                        .css( 'padding', '0 0.5em' )
                    return brainMassFemale;
                        .append( mw.calculators.getUnitsString( math.unit( '0 ' + this.units[ 0 ] ) ) )
                } else if( gender === 'M' ) {
                        .append( $( '<input>', unitsInputAttributes ) );
                    return brainMassMale;
                 } else {
                 } else {
                     // Initialize the units input options
                     return ( brainMassFemale + brainMassMale ) / 2;
                    unitsInputAttributes.class = 'custom-select calculator-input-select';
 
                    // Add any additional classes to the input
                    unitsInputAttributes.class += inputOptions.inputClass ? ' ' + inputOptions.inputClass : '';
 
                    unitsInputAttributes.class = unitsInputAttributes.class + ' ' + unitsId;
 
                    var $unitsInput = $( '<select>', unitsInputAttributes )
                        .on( 'change', function() {
                            var numberValue = $( '#' + inputId ).val();
 
                            var newValue = numberValue ? numberValue + ' ' + $( this ).val() : null;
 
                            mw.calculators.setValue( variableId, newValue );
                        } );
 
                    for( var iUnits in this.units ) {
                        var units = this.units[ iUnits ];
 
                        var unitsOptionAttributes = {
                            html: mw.calculators.getUnitsString( math.unit( '0 ' + units ) ),
                            value: units
                        };
 
                        if( units === unitsValue ) {
                            unitsOptionAttributes.selected = true;
                        }
 
                        $unitsInput.append( $( '<option>', unitsOptionAttributes ) );
                    }
 
                    $unitsContainer.append( $unitsInput );
                 }
                 }
             }
             }
 
        },
            // Create the input and add handlers
        cerebralBloodVolume: {
            var $input = $( '<input>', inputAttributes )
            name: 'Cerebral blood volume',
                .on( 'input', function() {
             abbreviation: 'CBV',
                    var numberValue = $( this ).val();
            data: {
 
                 calculations: {
                    var newValue = numberValue ? numberValue : null;
                     required: [ 'brainMass' ]
 
                    if( newValue && unitsId ) {
                        newValue = newValue + ' ' + $( '#' + unitsId ).val();
                    }
 
                    mw.calculators.setValue( variableId, newValue );
                } );
 
             // Create the input group
            var $inputGroup = $( '<div>', {
                class: 'input-group'
            } ).append( $input );
 
            if( $unitsContainer ) {
                 $inputGroup.append( $unitsContainer );
            }
 
            $inputContainer.append( $inputGroup );
        } else if( this.type === TYPE_STRING ) {
            if( this.hasOptions() ) {
                var optionKeys = Object.keys( this.options );
 
                if( optionKeys.length === 1 ) {
                     $inputContainer.append( this.options[ optionKeys[ 0 ] ] );
                } else {
                    var selectAttributes = {
                        id: inputId,
                        class: 'custom-select calculator-input calculator-input-select'
                    };
 
                    // Add any additional classes to the input
                    selectAttributes.class += inputOptions.inputClass ? ' ' + inputOptions.inputClass : '';
 
                    var $select = $( '<select>', selectAttributes )
                        .on( 'change', function() {
                            mw.calculators.setValue( variableId, $( this ).val() );
                        } );
 
                    for( var optionId in this.options ) {
                        var displayText = this.options[ optionId ];
 
                        var optionAttributes = {
                            value: optionId,
                            text: displayText
                        };
 
                        if( optionId === value ) {
                            optionAttributes.selected = true;
                        }
 
                        $select.append( $( '<option>', optionAttributes ) );
                    }
 
                    $inputContainer.append( $select );
                 }
                 }
             }
             },
        }
            units: 'mL',
 
            description: '4 mL per 100g of brain mass',
        return $inputContainer;
            references: [
    };
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
 
            ],
    mw.calculators.objectClasses.Variable.prototype.getLabelString = function() {
            calculate: function( data ) {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
                var brainMass = data.brainMass.toNumber( 'gwt' );
    };


    mw.calculators.objectClasses.Variable.prototype.getValue = function() {
                return 4 * brainMass / 100;
        if( this.value !== null ) {
            return this.value;
        } else if( this.defaultValue !== null ) {
            return this.defaultValue;
        } else {
            return null;
        }
    };
 
    mw.calculators.objectClasses.Variable.prototype.getValueString = function() {
        return String( this.getValue() );
    };
 
    mw.calculators.objectClasses.Variable.prototype.hasOptions = function() {
        return this.options !== null;
    };
 
    mw.calculators.objectClasses.Variable.prototype.hasUnits = function() {
        return this.units !== null;
    };
 
    mw.calculators.objectClasses.Variable.prototype.hasValue = function() {
        var value = this.getValue();
 
        if( value === null ||
            ( mw.calculators.isValueMathObject( value ) && !value.toNumber() ) ) {
            return false;
        }
 
        return true;
    };
 
    mw.calculators.objectClasses.Variable.prototype.isValueMathObject = function() {
        return mw.calculators.isValueMathObject( this.value );
    };
 
    mw.calculators.objectClasses.Variable.prototype.isValueValid = function( value ) {
        if( value === null ) {
            return true;
        }
 
        if( this.type === TYPE_NUMBER ) {
            if( typeof value !== 'object' ) {
                value = math.unit( value );
             }
             }
 
        },
            if( this.hasUnits() ) {
        cerebralMetabolicRateFactor: {
                var valueUnits = value.formatUnits();
            name: 'Cerebral metabolic rate factor',
 
            abbreviation: '%CMR',
                if( !valueUnits ) {
            data: {
                    throw new Error( 'Could not set value for "' + this.id + '": Value must define units' );
                 variables: {
                 } else if( this.units.indexOf( valueUnits ) === -1 ) {
                     optional: [ 'temperature' ]
                     throw new Error( 'Could not set value for "' + this.id + '": Units "' + valueUnits + '" are not valid for this variable' );
                 }
                 }
             }
             },
        } else if( this.hasOptions() ) {
            description: '7% change in CMR for every 1 &deg;C change in temperature',
             if( !this.options.hasOwnProperty( value ) ) {
             references: [
                 throw new Error( 'Could not set value "' + value + '" for "' + this.id + '": Value must define be one of: ' + Object.keys( this.options ).join( ', ' ) );
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
            }
            ],
        }
            calculate: function( data ) {
 
                 var temperature = data.temperature ? data.temperature.toNumber( 'degC' ) : null;
        return true;
    };


    mw.calculators.objectClasses.Variable.prototype.prepareValue = function( value ) {
                var cerebralMetabolicRateFactor = 1;
        if( !this.isValueValid( value ) ) {
            // isValueValid will throw a meaningful error to the console
            return null;
        }


        if( value !== null ) {
                 if( temperature ) {
            if( this.type === TYPE_NUMBER ) {
                     cerebralMetabolicRateFactor += 0.07 * ( temperature - 37 );
                 if( typeof value !== 'object' ) {
                     value = math.unit( value );
                 }
                 }
            }
        }
        return value;
    };
    mw.calculators.objectClasses.Variable.prototype.setValue = function( value ) {
        this.value = this.prepareValue( value );
        this.valueUpdated();
        return true;
    };
    mw.calculators.objectClasses.Variable.prototype.valueUpdated = function() {
        for( var iCalculation in this.calculations ) {
            var calculation = mw.calculators.getCalculation( this.calculations[ iCalculation ] );


            if( calculation ) {
                 return cerebralMetabolicRateFactor;
                 calculation.render();
             }
             }
         }
         },
    }
        cerebralMetabolicRateO2: {
 
            name: 'Cerebral metabolic rate (O<sub>2</sub>)',
 
            abbreviation: 'CMRO<sub>2</sub>',
 
            data: {
    /**
                calculations: {
    * Class AbstractCalculation
                    required: [ 'brainMass', 'cerebralMetabolicRateFactor' ]
    * @param {Object} propertyValues
                },
    * @returns {mw.calculators.objectClasses.AbstractCalculation}
                variables: {
    * @constructor
                    optional: [ 'temperature' ]
    */
                }
    mw.calculators.objectClasses.AbstractCalculation = function( propertyValues ) {
            },
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
            units: 'mL/min',
 
            description: '<ul><li>3 mL O<sub>2</sub>/min per 100g of brain mass</li><li>If temperature is provided, adjusts estimate using 7% change in CMR for every 1 &deg;C change in temperature</li></ul>',
        this.initialize();
             references: [
    };
                 'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
 
    mw.calculators.objectClasses.AbstractCalculation.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.addCalculation = function( calculationId ) {
        if( this.calculations.indexOf( calculationId ) !== -1 ) {
            return;
        }
 
        this.calculations.push( calculationId );
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.doRender = function() {};
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.getContainerClass = function() {
        return 'calculator-calculation-' + this.id;
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.getLabelString = function() {
        return this.id;
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.getProperties = function() {
        return {
             required: [
                 'id',
                'calculate'
             ],
             ],
             optional: [
             calculate: function( data ) {
                'data',
                 // Temperature is included as an optional variable to generate the input.
                 'description',
                 // It is used by cerebralMetabolicRateFactor, which is an internal calculation not typically shown.
                 'onRender',
                 var brainMass = data.brainMass.toNumber( 'gwt' );
                 'onRendered',
                 var cerebralMetabolicRateFactor = data.cerebralMetabolicRateFactor.toNumber();
                 'references',
                'type'
            ]
        };
    };


    mw.calculators.objectClasses.AbstractCalculation.prototype.getValue = function() {
                return cerebralMetabolicRateFactor * 3 * brainMass / 100;
        // For now, we always need to recalculate, since the calculation may not be rendered but still required by
            }
        // other calculations (i.e. drug dosages using lean body weight).
         },
        this.recalculate();
         cerebralMetabolicRateGlucose: {
 
             name: 'Cerebral metabolic rate (Glucose)',
        return this.value;
             abbreviation: 'CMR<sub>glu</sub>',
    };
             data: {
 
                 calculations: {
    mw.calculators.objectClasses.AbstractCalculation.prototype.hasInfo = function() {
                     required: [ 'brainMass', 'cerebralMetabolicRateFactor' ]
         return false;
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.hasValue = function() {
         if( this.value === null ||
            ( this.isValueMathObject() && !this.value.toNumber() ) ) {
             return false;
        }
 
        return true;
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.getCalculationData = function() {
        return this.data;
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.getCalculationDataValues = function() {
        var calculationData = this.getCalculationData();
 
        var data = {};
        var missingRequiredData = '';
        var calculationId, calculation, variableId, variable;
 
        for( var iRequiredCalculation in calculationData.calculations.required ) {
            calculationId = calculationData.calculations.required[ iRequiredCalculation ];
            calculation = mw.calculators.getCalculation( calculationId );
 
             if( !calculation ) {
                throw new Error( 'Invalid required calculation "' + calculationId + '" for calculation "' + this.id + '"' );
             } else if( !calculation.hasValue() ) {
                 if( missingRequiredData ) {
                     missingRequiredData = missingRequiredData + ', ';
                 }
                 }
            },
            units: 'mg/min',
            description: '<ul><li>5 mg glucose/min per 100g of brain mass</li><li>7% change in CMR for every 1 &deg;C change in temperature</li></ul>',
            references: [
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
            ],
            calculate: function( data ) {
                var brainMass = data.brainMass.toNumber( 'gwt' );
                var cerebralMetabolicRateFactor = data.cerebralMetabolicRateFactor.toNumber();


                 missingRequiredData = missingRequiredData + calculation.getLabelString();
                 return cerebralMetabolicRateFactor * 5 * brainMass / 100;
            } else {
                data[ calculationId ] = calculation.value;
             }
             }
         }
         },
 
         cerebralBloodFlow: {
         for( var iRequiredVariable in calculationData.variables.required ) {
             name: 'Cerebral blood flow',
             variableId = calculationData.variables.required[ iRequiredVariable ];
             abbreviation: 'CBF',
             variable = mw.calculators.getVariable( variableId );
             data: {
 
                 calculations: {
             if( !variable ) {
                    required: [ 'brainMass', 'cerebralMetabolicRateFactor' ]
                 throw new Error( 'Invalid required variable "' + variableId + '" for calculation "' + this.id + '"' );
                },
            } else if( !variable.hasValue() ) {
                 variables: {
                 if( missingRequiredData ) {
                     optional: [ 'paCO2' ]
                     missingRequiredData = missingRequiredData + ', ';
                 }
                 }
            },
            units: 'mL/min',
            description: '<ul><li>50 mL/min per 100g of brain mass.</li><li>Every mmHg in PaCO2 changes CBF by 1.5 mL/min per 100g of brain mass.</li><li>Cerebral blood flow and cerebral metabolic rate are coupled. Factors that alter CMR (e.g. temperature) will proportionally alter CBF.</li>',
            references: [
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001',
                'Brian JE Jr. Carbon dioxide and the cerebral circulation. Anesthesiology. 1998 May;88(5):1365-86. doi: 10.1097/00000542-199805000-00029. PMID: 9605698.'
            ],
            calculate: function( data ) {
                var brainMass = data.brainMass.toNumber( 'gwt' );
                var cerebralMetabolicRateFactor = data.cerebralMetabolicRateFactor.toNumber();
                var paCO2 = data.paCO2.toNumber( 'mmHg' );


                 missingRequiredData = missingRequiredData + variable.getLabelString();
                 var cerebralBloodFlow = cerebralMetabolicRateFactor * 50 * brainMass / 100;
            } else {
                data[ variableId ] = variable.getValue();
            }
        }
 
        if( missingRequiredData ) {
            this.message = missingRequiredData + ' required';
 
            return false;
        }
 
        for( var iOptionalCalculation in calculationData.calculations.optional ) {
            calculationId = calculationData.calculations.optional[ iOptionalCalculation ];
            calculation = mw.calculators.getCalculation( calculationId );
 
            if( !calculation ) {
                throw new Error( 'Invalid optional calculation "' + calculationId + '" for calculation "' + this.id + '"' );
            }
 
            data[ calculationId ] = calculation.hasValue() ? calculation.value : null;
        }
 
        for( var iOptionalVariable in calculationData.variables.optional ) {
            variableId = calculationData.variables.optional[ iOptionalVariable ];
            variable = mw.calculators.getVariable( variableId );
 
            if( !variable ) {
                throw new Error( 'Invalid optional variable "' + variableId + '" for calculation "' + this.id + '"' );
            }
 
            data[ variableId ] = variable.hasValue() ? variable.getValue() : null;
        }
 
        return data;
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.initialize = function() {
        if( typeof this.calculate !== 'function' ) {
            throw new Error( 'calculate() must be a function for Calculation "' + this.id + '"' );
        }
 
        // Initialize array to store calculation ids which depend on this calculation's value
        this.calculations = [];
 
        this.data = new mw.calculators.objectClasses.CalculationData( this.getCalculationData() );
 
        this.type = this.type ? this.type : TYPE_NUMBER;
 
        this.message = null;
        this.value = null;
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.isValueMathObject = function() {
        return mw.calculators.isValueMathObject( this.value );
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.recalculate = function() {
        this.message = '';
        this.value = null;
 
        var data = this.getCalculationDataValues();
 
        if( data === false ) {
            this.valueUpdated();


            return false;
                if( paCO2 ) {
        }
                    // CO2 reductions don't reduce CBF more than 50%
                    var minCerebralBloodFlow = cerebralBloodFlow / 2;


        try {
                    cerebralBloodFlow += 1.5 * brainMass / 100 * ( paCO2 - 40 );
            var value = this.calculate( data );


            if( this.type === TYPE_NUMBER && !isNaN( value ) ) {
                    cerebralBloodFlow = math.max( cerebralBloodFlow, minCerebralBloodFlow );
                if( this.units ) {
                    value = value + ' ' + this.units;
                 }
                 }


                 this.value = math.unit( value );
                 return cerebralBloodFlow;
            } else {
                this.value = value;
            }
        } catch( e ) {
            console.warn( e.message );
 
            this.message = e.message;
            this.value = null;
        } finally {
            this.valueUpdated();
        }
 
        return true;
    };
 
 
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.render = function() {
        this.recalculate();
 
        if( typeof this.onRender === 'function' ) {
            this.onRender();
        }
 
        this.doRender();
 
        if( typeof this.onRendered === 'function' ) {
            this.onRendered();
        }
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.setDependencies = function() {
        this.data = this.getCalculationData();
 
        var calculationIds = this.data.calculations.required.concat( this.data.calculations.optional );
 
        for( var iCalculationId in calculationIds ) {
            var calculationId = calculationIds[ iCalculationId ];
 
            if( !mw.calculators.calculations.hasOwnProperty( calculationId ) ) {
                throw new Error('Calculation "' + calculationId + '" does not exist for calculation "' + this.id + '"');
            }
 
            mw.calculators.calculations[ calculationId ].addCalculation( this.id );
        }
 
        var variableIds = this.data.variables.required.concat( this.data.variables.optional );
 
        for( var iVariableId in variableIds ) {
            var variableId = variableIds[ iVariableId ];
 
            if( !mw.calculators.variables.hasOwnProperty( variableId ) ) {
                throw new Error('Variable "' + variableId + '" does not exist for calculation "' + this.id + '"');
            }
 
            mw.calculators.variables[ variableId ].addCalculation( this.id );
        }
 
        this.recalculate();
    };
 
    mw.calculators.objectClasses.AbstractCalculation.prototype.valueUpdated = function() {
        for( var iCalculation in this.calculations ) {
            calculation = mw.calculators.getCalculation( this.calculations[ iCalculation ] );
 
            if( calculation ) {
                calculation.render();
             }
             }
         }
         }
     };
     } );
 
 
 
    /**
    * Class CalculationData
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.CalculationData}
    * @constructor
    */
    mw.calculators.objectClasses.CalculationData = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
 
        var dataTypes = this.getDataTypes();
        var dataRequirements = this.getDataRequirements();


        // Iterate through the supported data types (e.g. calculation, variable) to initialize the structure
        for( var iDataType in dataTypes ) {
            var dataType = dataTypes[ iDataType ];


            if( !this[ dataType ] ) {
                this[ dataType ] = {
                    optional: [],
                    required: []
                };
            } else {
                // Iterate through the requirement levels (i.e. optional, required) to initialize the structure
                for( var iDataRequirement in dataRequirements ) {
                    var dataRequirement = dataRequirements[ iDataRequirement ];


                    if( this[ dataType ].hasOwnProperty( dataRequirement ) ) {
    // Pulmonary
                        for( var iDataId in this[ dataType ][ dataRequirement ] ) {
    mw.calculators.addCalculations( {
                            var dataId = this[ dataType ][ dataRequirement ][ iDataId ];
        paO2Predicted: {
                        }
            name: 'PaO<sub>2</sub> (predicted)',
                    } else {
            abbreviation: 'PaO<sub>2</sub> pred.',
                        this[ dataType ][ dataRequirement ] = [];
            data: {
                    }
                variables: {
                    required: [ 'pAtm', 'fiO2', 'paCO2' ]
                 }
                 }
             }
             },
        }
             units: 'mmHg',
    };
             formula: '<math>P_{aO_2Predicted} = FiO_2*(P_{atm}-P_{H_{2}O})-\\frac{P_{aCO_2}}{R}</math>',
 
            references: [
    mw.calculators.objectClasses.CalculationData.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
                 'McFarlane MJ, Imperiale TF. Use of the alveolar-arterial oxygen gradient in the diagnosis of pulmonary embolism. Am J Med. 1994 Jan;96(1):57-62. doi: 10.1016/0002-9343(94)90116-3. PMID: 8304364.'
 
    mw.calculators.objectClasses.CalculationData.prototype.getDataRequirements = function() {
        return [
             'optional',
             'required'
        ];
    };
 
    mw.calculators.objectClasses.CalculationData.prototype.getDataTypes = function() {
        return [
            'calculations',
            'variables'
        ];
    };
 
    mw.calculators.objectClasses.CalculationData.prototype.getProperties = function() {
        return {
            required: [],
            optional: [
                'calculations',
                'variables'
            ]
        };
    };
 
 
 
    mw.calculators.objectClasses.CalculationData.prototype.merge = function() {
        var mergedData = new mw.calculators.objectClasses.CalculationData();
 
        var data = [ this ].concat( Array.prototype.slice.call( arguments ) );
 
        var dataTypes = this.getDataTypes();
 
        for( var iData in data ) {
            for( var iDataType in dataTypes ) {
                var dataType = dataTypes[ iDataType ];
 
                mergedData[ dataType ].required = mergedData[ dataType ].required
                    .concat( data[ iData ][ dataType ].required )
                    .filter( mw.calculators.uniqueValues );
 
                mergedData[ dataType ].optional = mergedData[ dataType ].optional
                    .concat( data[ iData ][ dataType ].optional )
                    .filter( mw.calculators.uniqueValues );
            }
        }
 
        return mergedData;
    };
 
 
 
 
 
    /**
    * Class SimpleCalculation
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.SimpleCalculation}
    * @constructor
    */
    mw.calculators.objectClasses.SimpleCalculation = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
 
        this.initialize();
    };
 
    mw.calculators.objectClasses.SimpleCalculation.prototype = Object.create( mw.calculators.objectClasses.AbstractCalculation.prototype );
 
 
    mw.calculators.objectClasses.SimpleCalculation.prototype.hasInfo = function() {
        return this.description || this.formula || this.references.length;
    };
 
    mw.calculators.objectClasses.SimpleCalculation.prototype.getLabelHtml = function() {
        var labelHtml = this.getLabelString();
 
        if( this.link ) {
            var href = this.link;
 
            // Detect internal links (this isn't great)
            var matches = href.match( /\[\[(.*?)\]\]/ );
 
            if( matches ) {
                 href = mw.util.getUrl( matches[ 1 ] );
            }
 
            labelHtml = $( '<a>', {
                href: href,
                text: labelHtml
            } )[ 0 ].outerHTML;
        }
 
        return labelHtml;
    };
 
    mw.calculators.objectClasses.SimpleCalculation.prototype.getLabelString = function() {
        return mw.calculators.isMobile() && this.abbreviation ? this.abbreviation : this.name;
    };
 
    mw.calculators.objectClasses.SimpleCalculation.prototype.getProperties = function() {
        var inheritedProperties = mw.calculators.objectClasses.AbstractCalculation.prototype.getProperties();
 
        return this.mergeProperties( inheritedProperties, {
            required: [
                'name'
             ],
             ],
             optional: [
             calculate: function( data ) {
                'abbreviation',
                var fiO2 = data.fiO2.toNumber( 'pcto2' ) / 100;
                'digits',
                var pAtm = data.pAtm.toNumber( 'mmHg' );
                'formula',
                var pH2O = 47; // mmHg
                'link',
                var paCO2 = data.paCO2.toNumber( 'mmHg' );
                'units'
                var r = 0.8;
            ]
        } );
    };
 
    mw.calculators.objectClasses.SimpleCalculation.prototype.getValueString = function() {
        if( this.message ) {
            return this.message;
        } else if( typeof this.value === 'object' && this.value.hasOwnProperty( 'value' ) ) {
            return mw.calculators.getValueString( this.value );
        } else {
            return String( this.value );
        }
    };
 
    mw.calculators.objectClasses.SimpleCalculation.prototype.doRender = function() {
        var $calculationContainer = $( '.' + this.getContainerClass() );
 
        if( !$calculationContainer.length ) {
            return;
        }
 
        var valueString = this.getValueString();
 
        var inputVariableIds = this.data.variables.required.concat( this.data.variables.optional );
        var missingVariableInputs = [];
 
        for( var iInputVariableId in inputVariableIds ) {
            var variableId = inputVariableIds[ iInputVariableId ];


            if( !$( '#calculator-input-' + variableId ).length ) {
                return fiO2 * ( pAtm - pH2O ) - paCO2 / r;
                missingVariableInputs.push( variableId );
             }
             }
         }
         },
 
         aaGradientO2: {
         var calculation = this;
             name: 'A-a O<sub>2</sub> gradient',
 
            abbreviation: 'A-a O<sub>2</sub>',
        $calculationContainer.each( function() {
            data: {
             $( this ).empty();
                calculations: {
 
                     required: [ 'paO2Predicted' ]
            var isTable = this.tagName.toLowerCase() === 'tr';
                 },
 
                variables: {
            var $infoButton = null;
                     required: [ 'paO2' ]
 
            if( calculation.hasInfo() ) {
                $infoButton = $( '<a>', {
                    'data-toggle': 'collapse',
                    href: '#' + calculation.getContainerClass() + '-info',
                    role: 'button',
                    'aria-expanded': 'false',
                     'aria-controls': calculation.getContainerClass() + '-info'
                 } )
                    .append( $( '<i>', {
                        class: 'far fa-question-circle'
                    } ) );
            }
 
            var labelHtml = calculation.getLabelHtml();
 
            if( isTable ) {
                if( calculation.hasInfo() ) {
                     labelHtml += $( '<span>', {
                        class: 'calculator-SimpleCalculator-info'
                    } ).append( $infoButton )[ 0 ].outerHTML;
                 }
                 }
            },
            units: 'mmHg',
            formula: '<math>\\text{A-a gradient}_{\\mathrm{O}_2} = P_{aO_2Predicted}-P_{aO_2} </math>',
            references: [
                'McFarlane MJ, Imperiale TF. Use of the alveolar-arterial oxygen gradient in the diagnosis of pulmonary embolism. Am J Med. 1994 Jan;96(1):57-62. doi: 10.1016/0002-9343(94)90116-3. PMID: 8304364.'
            ],
            calculate: function( data ) {
                var paO2Predicted = data.paO2Predicted.toNumber( 'mmHg' );
                var paO2 = data.paO2.toNumber( 'mmHg' );


                 $( this )
                 return math.round( paO2Predicted - paO2 );
                    .append( $( '<th>', {
                        class: 'calculator-SimpleCalculator-calculation-cell',
                        html: labelHtml
                    } ) )
                    .append( $( '<td>', {
                        class: 'calculator-SimpleCalculator-value-cell',
                        html: valueString
                    } ) );
            } else {
                $( this )
                    .append( labelHtml + $infoButton[ 0 ].outerHTML + ': ' + valueString );
             }
             }
 
        },
            if( calculation.hasInfo() ) {
        aaGradientO2Predicted: {
                var infoHtml = '';
            name: 'A-a O<sub>2</sub> gradient (predicted)',
 
            abbreviation: 'A-a O<sub>2</sub> pred.',
                if( calculation.description ) {
            data: {
                    infoHtml += $( '<p>', {
                variables: {
                        html: calculation.description
                     required: [ 'age' ]
                     } )[ 0 ].outerHTML;
                 }
                 }
            },
            units: 'mmHg',
            formula: '<math>\\text{Predicted A-a gradient}_{\\mathrm{O}_2} = \\frac{(\\mathrm{age_{yr} + 10)}}{4}</math>',
            references: [
                'Hantzidiamantis PJ, Amaro E. Physiology, Alveolar to Arterial Oxygen Gradient. 2021 Feb 22. In: StatPearls [Internet]. Treasure Island (FL): StatPearls Publishing; 2021 Jan–. PMID: 31424737.'
            ],
            calculate: function( data ) {
                var age = data.age.toNumber( 'yr' );


                 if( calculation.formula ) {
                 return ( age + 10 ) / 4;
                    infoHtml += $( '<span>', {
                        class: calculation.getContainerClass() + '-formula'
                    } )[ 0 ].outerHTML;
 
                    var api = new mw.Api();
 
                    api.parse( calculation.formula ).then( function( result ) {
                        $( '.' + calculation.getContainerClass() + '-formula' ).html( result );
                    } );
                }
 
                if( calculation.references.length ) {
                    var $references = $( '<ol>' );
 
                    for( var iReference in calculation.references ) {
                        $references.append( $( '<li>', {
                            text: calculation.references[ iReference ]
                        } ) );
                    }
 
                    infoHtml += $references[ 0 ].outerHTML;
                }
 
                var infoContainerId = calculation.getContainerClass() + '-info';
                var $infoContainer = $( '#' + infoContainerId );
 
                if( $infoContainer.length ) {
                    $infoContainer.empty();
                }
 
                if( isTable ) {
                    $infoContainer = $( '<tr>', {
                        id: infoContainerId,
                        class: 'collapse'
                    } )
                        .append( $( '<td>', {
                            colspan: 2
                        } ).append( infoHtml ) );
                } else {
                    $infoContainer = $( '<div>', {
                        id: infoContainerId,
                        class: 'collapse'
                    } ).append( infoHtml );
                }
 
                $( this ).after( $infoContainer );
             }
             }
 
        },
            if( missingVariableInputs.length ) {
        weightBasedTidalVolume: {
                var variablesContainerClass = 'calculator-SimpleCalculator-variables ' + calculation.getContainerClass() + '-variables';
            name: 'Weight-based tidal volume',
                var inputGroup = mw.calculators.createInputGroup( missingVariableInputs );
            abbreviation: 'Weight-based TV',
 
            data: {
                 if( isTable ) {
                 calculations: {
                     $variablesContainer =  $( '<tr>' )
                     required: [ 'ibw' ]
                        .append( $( '<td>', {
                },
                            class: variablesContainerClass,
                variables: {
                            colspan: 2
                     required: [ 'weightBasedTidalVolumePerKgMin', 'weightBasedTidalVolumePerKgMax' ]
                        } ).append( inputGroup ) );
                } else {
                     $variablesContainer = $( '<div>', {
                        class: variablesContainerClass
                    } ).append( inputGroup );
                 }
                 }
 
             },
                $( this ).after( $variablesContainer );
            type: 'string',
 
            description: '<ul><li>Calculated using ideal body weight</li><li>Low tidal volume uses 6-8 mL/kg<sup>1</sup><ul><li>Current evidence does not show benefit of intraoperative low tidal volumes for patients without pulmonary injury<sup>2</sup></li></ul></li>',
                missingVariableInputs = [];
            references: [
             }
                'Acute Respiratory Distress Syndrome Network, Brower RG, Matthay MA, Morris A, Schoenfeld D, Thompson BT, Wheeler A. Ventilation with lower tidal volumes as compared with traditional tidal volumes for acute lung injury and the acute respiratory distress syndrome. N Engl J Med. 2000 May 4;342(18):1301-8. doi: 10.1056/NEJM200005043421801. PMID: 10793162.',
        } );
                'Karalapillai D, Weinberg L, Peyton P, Ellard L, Hu R, Pearce B, Tan CO, Story D, O\'Donnell M, Hamilton P, Oughton C, Galtieri J, Wilson A, Serpa Neto A, Eastwood G, Bellomo R, Jones DA. Effect of Intraoperative Low Tidal Volume vs Conventional Tidal Volume on Postoperative Pulmonary Complications in Patients Undergoing Major Surgery: A Randomized Clinical Trial. JAMA. 2020 Sep 1;324(9):848-858. doi: 10.1001/jama.2020.12866. PMID: 32870298; PMCID: PMC7489812.'
    };
 
 
 
 
 
    /**
    * Class AbstractCalculator
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.AbstractCalculator}
    * @constructor
    */
    mw.calculators.objectClasses.AbstractCalculator = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
    };
 
    mw.calculators.objectClasses.AbstractCalculator.prototype = Object.create( mw.calculators.objectClasses.CalculatorObject.prototype );
 
    mw.calculators.objectClasses.AbstractCalculator.prototype.getCalculatorClass = function() {
        return '';
    };
 
    mw.calculators.objectClasses.AbstractCalculator.prototype.getContainerClass = function() {
        return 'calculator-' + this.module + '-' + this.id;
    };
 
    mw.calculators.objectClasses.AbstractCalculator.prototype.getProperties = function() {
        return {
            required: [
                'id',
                'module',
                'name',
                'calculations'
             ],
             ],
             optional: [
             calculate: function( data ) {
                'onRender',
                var ibw = data.ibw.toNumber( 'kgwt' );
                'onRendered'
                var weightBasedTidalVolumePerKgMin = data.weightBasedTidalVolumePerKgMin.toNumber( 'mL/kgwt' );
            ]
                var weightBasedTidalVolumePerKgMax = data.weightBasedTidalVolumePerKgMax.toNumber( 'mL/kgwt' );
        };
    };
 
    mw.calculators.objectClasses.AbstractCalculator.prototype.render = function() {
        if( typeof this.onRender === 'function' ) {
            this.onRender();
        }
 
        this.doRender();
 
        if( typeof this.onRendered === 'function' ) {
            this.onRendered();
        }
    };
 
 
    mw.calculators.objectClasses.AbstractCalculator.prototype.doRender = function() {};


 
                return math.round( weightBasedTidalVolumePerKgMin * ibw ) + '-' + math.round( weightBasedTidalVolumePerKgMax * ibw ) + ' mL';
 
 
 
    /**
    * Class SimpleCalculator
    * @param {Object} propertyValues
    * @returns {mw.calculators.objectClasses.SimpleCalculator}
    * @constructor
    */
    mw.calculators.objectClasses.SimpleCalculator = function( propertyValues ) {
        mw.calculators.objectClasses.CalculatorObject.call( this, this.getProperties(), propertyValues );
    };
 
    mw.calculators.objectClasses.SimpleCalculator.prototype = Object.create( mw.calculators.objectClasses.AbstractCalculator.prototype );
 
    mw.calculators.objectClasses.SimpleCalculator.prototype.doRender = function() {
        var $calculatorContainer = $( '.' + this.getContainerClass() );
 
        if( !$calculatorContainer.length ) {
            return;
        }
 
        $calculatorContainer.addClass( this.getCalculatorClass() );
 
        if( this.css ) {
            $calculatorContainer.css( this.css );
        }
 
        $calculatorContainer.empty();
 
        $calculatorContainer.append( $( '<h4>', {
            text: this.name
        } ) );
 
        var $calculationsContainer;
 
        if( this.table ) {
            $calculationsContainer = $( '<table>', {
                class: 'wikitable'
            } ).append( '<tbody>' );
 
            $calculationsContainer
                .append( $( '<tr>' )
                    .append(
                        $( '<th>', {
                            class: this.getCalculatorClass() + '-calculation-header'
                        } ).text( 'Calculation' ),
                        $( '<th>', {
                            class: this.getCalculatorClass() + '-value-header'
                        }  ).text( 'Value' )
                    )
                );
        } else {
            $calculationsContainer = $( '<div>' );
        }
 
        $calculatorContainer.append( $calculationsContainer );
 
        for( var iCalculationId in this.calculations ) {
            var calculation = mw.calculators.getCalculation( this.calculations[ iCalculationId ] );
            var calculationContainerClass = calculation.getContainerClass();
 
            var $calculationContainer = $( '.' + calculationContainerClass );
 
            // If a container doesn't exist yet, add it
            if( !$calculationContainer.length ) {
                if( this.table ) {
                    $calculationContainer = $( '<tr>', {
                        class: calculationContainerClass
                    } );
                } else {
                    $calculationContainer = $( '<div>', {
                        class: calculationContainerClass
                    } );
                }
 
                $calculationsContainer.append( $calculationContainer );
             }
             }
            calculation.render();
         }
         }
     };
     } );
 
    mw.calculators.objectClasses.SimpleCalculator.prototype.getCalculatorClass = function() {
        return 'calculator-SimpleCalculator';
    };
 
 
    mw.calculators.objectClasses.SimpleCalculator.prototype.getProperties = function() {
        var inheritedProperties = mw.calculators.objectClasses.AbstractCalculator.prototype.getProperties();
 
        return this.mergeProperties( inheritedProperties, {
            required: [],
            optional: [
                'css',
                'table'
            ]
        } );
    };
 
    mw.calculators.initialize();
 
}() );
}() );

Latest revision as of 06:27, 19 March 2022

( function() {
    mw.calculators.addUnitsBases( {
        bpm: {
            toString: function( units ) {
                units = units.replace( 'bpm', 'beats/min' );

                return units;
            }
        },
        hgb: {
            toString: function( units ) {
                units = units.replace( 'hgbperdL', '/dL' );
                units = units.replace( /\s?pcthct/, '%' );

                return units;
            }
        },
        o2: {
            toString: function( units ) {
                units = units.replace( /\s?pcto2/, '%' );

                return units;
            }
        },
        temperature: {
            toString: function( units ) {
                units = units.replace( 'deg', '&deg;' );

                return units;
            }
        }
    } );

    mw.calculators.addUnits( {
        bpm: {
            baseName: 'bpm'
        },
        pcthct: {
            baseName: 'hgb'
        },
        pcto2: {
            baseName: 'o2'
        },
        ghgbperdL: {
            baseName: 'hgb',
            prefixes: 'short',
            definition: '3 pcthct'
        }
    } );

    mw.calculators.addVariables( {
        fiO2: {
            name: 'FiO<sub>2</sub>',
            type: 'number',
            abbreviation: 'FiO<sub>2</sub>',
            minValue: '10 pcto2',
            maxValue: '100 pcto2',
            defaultValue: '21 pcto2',
            maxLength: 3,
            units: [
                'pcto2'
            ]
        },
        heartRate: {
            name: 'Heart rate',
            type: 'number',
            abbreviation: 'HR',
            defaultValue: '60 bpm',
            maxLength: 4,
            units: [
                'bpm'
            ]
        },
        hgb: {
            name: 'Hemoglobin/hematocrit',
            type: 'number',
            abbreviation: 'HgB',
            minValue: '3 ghgbperdL',
            maxValue: '25 ghgbperdL',
            defaultValue: '13 ghgbperdL',
            maxLength: 4,
            units: [
                'pcthct',
                'ghgbperdL'
            ]
        },
        paCO2: {
            name: 'PaCO<sub>2</sub>',
            type: 'number',
            abbreviation: 'PaCO<sub>2</sub>',
            minValue: '20 mmHg',
            defaultValue: '40 mmHg',
            maxLength: 3,
            units: [
                'mmHg'
            ]
        },
        paO2: {
            name: 'PaO<sub>2</sub>',
            type: 'number',
            abbreviation: 'PaO<sub>2</sub>',
            minValue: '25 mmHg',
            defaultValue: '100 mmHg',
            maxLength: 3,
            units: [
                'mmHg'
            ]
        },
        pAtm: {
            name: 'Atmospheric pressure',
            type: 'number',
            abbreviation: 'P<sub>atm</sub>',
            minValue: '0 mmHg',
            defaultValue: '760 mmHg',
            maxLength: 4,
            units: [
                'mmHg'
            ]
        },
        saO2: {
            name: 'SaO<sub>2</sub>',
            type: 'number',
            abbreviation: 'SaO<sub>2</sub>',
            minValue: '25 pcto2',
            maxValue: '100 pcto2',
            defaultValue: '100 pcto2',
            maxLength: 3,
            units: [
                'pcto2'
            ]
        },
        smvO2: {
            name: 'SmvO<sub>2</sub>',
            type: 'number',
            abbreviation: 'SmvO<sub>2</sub>',
            minValue: '25 pcto2',
            maxValue: '100 pcto2',
            defaultValue: '75 pcto2',
            maxLength: 3,
            units: [
                'pcto2'
            ]
        },
        raceSpirometry: {
            name: 'Race',
            type: 'string',
            abbreviation: 'Race',
            defaultValue: 'unknown',
            options: {
                unknown: 'Unknown',
                black: 'Black',
                caucasian: 'Caucasian',
                mexican: 'Mexican-American'
            }
        },
        temperature: {
            name: 'Temperature',
            type: 'number',
            abbreviation: 'Temp',
            minValue: '20 degC',
            maxValue: '44 degC',
            defaultValue: '37 degC',
            maxLength: 5,
            units: [
                'degC',
                'degF'
            ]
        },
        weightBasedTidalVolumePerKgMin: {
            name: 'Minimum tidal volume',
            type: 'number',
            abbreviation: 'Min TV',
            minValue: '3 mL/kgwt',
            maxValue: '12 mL/kgwt',
            defaultValue: '6 mL/kgwt',
            maxLength: 2,
            units: [
                'mL/kgwt'
            ]
        },
        weightBasedTidalVolumePerKgMax: {
            name: 'Maximum tidal volume',
            type: 'number',
            abbreviation: 'Max TV',
            minValue: '3 mL/kgwt',
            maxValue: '12 mL/kgwt',
            defaultValue: '8 mL/kgwt',
            maxLength: 2,
            units: [
                'mL/kgwt'
            ]
        }
    } );

    // Force re-render of ibw and lbw. This is necessary to remove the additional inputs for patient variables from the
    // calculation which are now provided by the patient input toolbar.
    mw.calculators.calculations.ibw.render();
    mw.calculators.calculations.lbw.render();

    mw.calculators.addCalculations( {
        bmi: {
            name: 'Body mass index',
            abbreviation: 'BMI',
            data: {
                variables: {
                    required: [ 'weight', 'height' ]
                }
            },
            digits: 0,
            units: 'kg/m^2',
            formula: '<math>\\mathrm{BMI} = \\frac{\\mathrm{mass_{kg}}}{{(\\mathrm{height_{m}}})^2}</math>',
            link: '[[Body mass index]]',
            references: [],
            calculate: function( data ) {
                return data.weight.toNumber( 'kgwt' ) / Math.pow( data.height.toNumber( 'm' ), 2 );
            }
        },
        bsa: {
            name: 'Body surface area',
            abbreviation: 'BSA',
            data: {
                variables: {
                    required: [ 'weight', 'height' ]
                }
            },
            digits: 2,
            units: 'm^2',
            formula: '<math>\\mathrm{BSA} = \\sqrt{\\frac{\\mathrm{weight_{kg}}*\\mathrm{height_{cm}}}{3600}}</math>',
            link: false,
            references: [
                'Mosteller RD. Simplified calculation of body-surface area. N Engl J Med. 1987 Oct 22;317(17):1098. doi: 10.1056/NEJM198710223171717. PMID: 3657876.'
            ],
            calculate: function( data ) {
                return Math.sqrt( data.height.toNumber( 'cm' ) * data.weight.toNumber( 'kgwt' ) / 3600 );
            }
        },
        systolicBloodPressure: {
            name: 'Systolic blood pressure',
            abbreviation: 'SBP',
            data: {
                variables: {
                    required: [ 'age' ]
                }
            },
            type: 'string',
            references: [
                'Baby Miller 6e, ch. 16, pg. 550'
            ],
            calculate: function( data ) {
                var age = data.age.toNumber( 'yo' );

                var systolicMin, systolicMax, diastolicMin, diastolicMax, meanMin, meanMax;

                if( age >= 16 ) {
                    systolicMin = 100;
                    systolicMax = 125;
                } else if( age >= 13 ) {
                    systolicMin = 95;
                    systolicMax = 120;
                } else if( age >= 9 ) {
                    systolicMin = 90;
                    systolicMax = 115;
                } else if( age >= 6 ) {
                    systolicMin = 85;
                    systolicMax = 105;
                } else if( age >= 3 ) {
                    systolicMin = 80;
                    systolicMax = 100;
                } else if( age >= 1 ) {
                    systolicMin = 75;
                    systolicMax = 95;
                } else if( age >= 6 / 12 ) {
                    systolicMin = 70;
                    systolicMax = 90;
                } else if( age >= 1 / 12 ) {
                    systolicMin = 65;
                    systolicMax = 85;
                } else {
                    systolicMin = 60;
                    systolicMax = 75;
                }
            }
        }
    } );

    // Cardiovascular
    mw.calculators.addCalculations( {
        vO2: {
            name: 'Rate of oxygen consumption (VO<sub>2</sub>)',
            abbreviation: 'VO<sub>2</sub>',
            data: {
                calculations: {
                    required: [ 'bsa' ]
                },
                variables: {
                    optional: [ 'age' ]
                }
            },
            units: 'mL/min',
            formula: '<math>\\mathrm{VO_2} = \\begin{cases} 125 * \\mathrm{BSA}  & \\text{if age} < 70 \\\\ 110 * \\mathrm{BSA} & \\text{if age} \\geq 70 \\end{cases}</math>',
            references: [],
            calculate: function( data ) {
                var bsa = data.bsa.toNumber();
                var age = data.age ? data.age.toNumber( 'yr' ) : null;

                if( age < 70 ) {
                    return 125 * bsa;
                } else {
                    return 110 * bsa;
                }
            }
        },
        cardiacOutputFick: {
            name: 'Cardiac output (Fick)',
            abbreviation: 'CO (Fick)',
            data: {
                variables: {
                    required: [ 'saO2', 'smvO2', 'hgb' ]
                },
                calculations: {
                    required: [ 'vO2' ]
                }
            },
            units: 'L/min',
            formula: '<math>\\mathrm{CO_{Fick}}=\\frac{\\mathrm{VO_2}}{(\\mathrm{S_aO_2} - \\mathrm{S_{mv}O_2}) * H_b * 13.4}</math>',
            link: false,
            references: [],
            calculate: function( data ) {
                var vO2 = data.vO2.toNumber( 'mL/min' );
                var saO2 = data.saO2.toNumber() / 100;
                var smvO2 = data.smvO2.toNumber() / 100;
                var hgb = data.hgb.toNumber( 'ghgbperdL' );

                return vO2 / ( ( saO2 - smvO2 ) * hgb * 13.4 );
            }
        },
        cardiacIndex: {
            name: 'Cardiac index',
            abbreviation: 'CI',
            data: {
                calculations: {
                    required: [ 'bsa', 'cardiacOutputFick' ]
                }
            },
            units: 'L/min/m^2',
            formula: '<math>\\mathrm{CI}=\\frac{\\mathrm{CO}}{\\mathrm{BSA}}</math>',
            link: false,
            references: [],
            calculate: function( data ) {
                var cardiacOutput = data.cardiacOutputFick.toNumber( 'L/min' );
                var bsa = data.bsa.toNumber( 'm^2' );

                return cardiacOutput / bsa;
            }
        },
        strokeVolume: {
            name: 'Stroke volume',
            abbreviation: 'SV',
            data: {
                variables: {
                    required: [ 'heartRate' ]
                },
                calculations: {
                    required: [ 'cardiacOutputFick' ]
                }
            },
            units: 'mL',
            formula: '<math>\\mathrm{SV}=\\frac{\\mathrm{CO}}{\\mathrm{HR}}</math>',
            link: false,
            references: [],
            calculate: function( data ) {
                var cardiacOutput = data.cardiacOutputFick.toNumber( 'mL/min' );
                var heartRate = data.heartRate.toNumber();

                return cardiacOutput / heartRate;
            }
        }
    } );

    // Neuro
    mw.calculators.addCalculations( {
        brainMass: {
            name: 'Brain mass',
            data: {
                variables: {
                    optional: [ 'age', 'gender' ]
                }
            },
            digits: 0,
            units: 'gwt',
            description: 'This calculation will give a more precise estimate of brain mass if age and/or gender are provided.',
            references: [
                'Dekaban AS. Changes in brain weights during the span of human life: relation of brain weights to body heights and body weights. Ann Neurol. 1978 Oct;4(4):345-56. doi: 10.1002/ana.410040410. PMID: 727739.'
            ],
            calculate: function( data ) {
                var age = data.age ? data.age.toNumber( 'yr' ) : null;
                var gender = data.gender ? data.gender : null;

                var brainMassFemale = 1290;
                var brainMassMale = 1450;

                if( age !== null ) {
                    if( age <= 10 / 365 ) {
                        // <=10 days
                        brainMassFemale = 360;
                        brainMassMale = 380;
                    } else if( age <= 4 * 30 / 365 ) {
                        // Less than 4 months. This is a gap in the reported data of the paper, so linearly interpolate?
                        var ageFactor = 1 - ( 4 * 30 / 365 - age ) / ( 4 * 30 / 365 - 10 / 365 );

                        brainMassFemale = 360 + ageFactor * ( 580 - 360 );
                        brainMassMale = 380 + ageFactor * ( 640 - 380 );
                    } else if( age <= 8 * 30 / 365 ) {
                        // <=8 months
                        brainMassFemale = 580;
                        brainMassMale = 640;
                    } else if( age <= 18 * 30 / 365 ) {
                        // <=18 months
                        brainMassFemale = 940;
                        brainMassMale = 970;
                    } else if( age <= 30 * 30 / 365 ) {
                        // <=30 months
                        brainMassFemale = 1040;
                        brainMassMale = 1120;
                    } else if( age <= 43 * 30 / 365 ) {
                        // <=43 months
                        brainMassFemale = 1090;
                        brainMassMale = 1270;
                    } else if( age <= 5 ) {
                        brainMassFemale = 1150;
                        brainMassMale = 1300;
                    } else if( age <= 7 ) {
                        brainMassFemale = 1210;
                        brainMassMale = 1330;
                    } else if( age <= 9 ) {
                        brainMassFemale = 1180;
                        brainMassMale = 1370;
                    } else if( age <= 12 ) {
                        brainMassFemale = 1260;
                        brainMassMale = 1440;
                    } else if( age <= 15 ) {
                        brainMassFemale = 1280;
                        brainMassMale = 1410;
                    } else if( age <= 18 ) {
                        brainMassFemale = 1340;
                        brainMassMale = 1440;
                    } else if( age <= 21 ) {
                        brainMassFemale = 1310;
                        brainMassMale = 1450;
                    } else if( age <= 30 ) {
                        brainMassFemale = 1300;
                        brainMassMale = 1440;
                    } else if( age <= 40 ) {
                        brainMassFemale = 1290;
                        brainMassMale = 1440;
                    } else if( age <= 50 ) {
                        brainMassFemale = 1290;
                        brainMassMale = 1430;
                    } else if( age <= 55 ) {
                        brainMassFemale = 1280;
                        brainMassMale = 1410;
                    } else if( age <= 60 ) {
                        brainMassFemale = 1250;
                        brainMassMale = 1370;
                    } else if( age <= 65 ) {
                        brainMassFemale = 1240;
                        brainMassMale = 1370;
                    } else if( age <= 70 ) {
                        brainMassFemale = 1240;
                        brainMassMale = 1360;
                    } else if( age <= 75 ) {
                        brainMassFemale = 1230;
                        brainMassMale = 1350;
                    } else if( age <= 80 ) {
                        brainMassFemale = 1190;
                        brainMassMale = 1330;
                    } else if( age <= 85 ) {
                        brainMassFemale = 1170;
                        brainMassMale = 1310;
                    } else {
                        brainMassFemale = 1140;
                        brainMassMale = 1290;
                    }
                }

                if( gender === 'F' ) {
                    return brainMassFemale;
                } else if( gender === 'M' ) {
                    return brainMassMale;
                } else {
                    return ( brainMassFemale + brainMassMale ) / 2;
                }
            }
        },
        cerebralBloodVolume: {
            name: 'Cerebral blood volume',
            abbreviation: 'CBV',
            data: {
                calculations: {
                    required: [ 'brainMass' ]
                }
            },
            units: 'mL',
            description: '4 mL per 100g of brain mass',
            references: [
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
            ],
            calculate: function( data ) {
                var brainMass = data.brainMass.toNumber( 'gwt' );

                return 4 * brainMass / 100;
            }
        },
        cerebralMetabolicRateFactor: {
            name: 'Cerebral metabolic rate factor',
            abbreviation: '%CMR',
            data: {
                variables: {
                    optional: [ 'temperature' ]
                }
            },
            description: '7% change in CMR for every 1 &deg;C change in temperature',
            references: [
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
            ],
            calculate: function( data ) {
                var temperature = data.temperature ? data.temperature.toNumber( 'degC' ) : null;

                var cerebralMetabolicRateFactor = 1;

                if( temperature ) {
                    cerebralMetabolicRateFactor += 0.07 * ( temperature - 37 );
                }

                return cerebralMetabolicRateFactor;
            }
        },
        cerebralMetabolicRateO2: {
            name: 'Cerebral metabolic rate (O<sub>2</sub>)',
            abbreviation: 'CMRO<sub>2</sub>',
            data: {
                calculations: {
                    required: [ 'brainMass', 'cerebralMetabolicRateFactor' ]
                },
                variables: {
                    optional: [ 'temperature' ]
                }
            },
            units: 'mL/min',
            description: '<ul><li>3 mL O<sub>2</sub>/min per 100g of brain mass</li><li>If temperature is provided, adjusts estimate using 7% change in CMR for every 1 &deg;C change in temperature</li></ul>',
            references: [
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
            ],
            calculate: function( data ) {
                // Temperature is included as an optional variable to generate the input.
                // It is used by cerebralMetabolicRateFactor, which is an internal calculation not typically shown.
                var brainMass = data.brainMass.toNumber( 'gwt' );
                var cerebralMetabolicRateFactor = data.cerebralMetabolicRateFactor.toNumber();

                return cerebralMetabolicRateFactor * 3 * brainMass / 100;
            }
        },
        cerebralMetabolicRateGlucose: {
            name: 'Cerebral metabolic rate (Glucose)',
            abbreviation: 'CMR<sub>glu</sub>',
            data: {
                calculations: {
                    required: [ 'brainMass', 'cerebralMetabolicRateFactor' ]
                }
            },
            units: 'mg/min',
            description: '<ul><li>5 mg glucose/min per 100g of brain mass</li><li>7% change in CMR for every 1 &deg;C change in temperature</li></ul>',
            references: [
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001'
            ],
            calculate: function( data ) {
                var brainMass = data.brainMass.toNumber( 'gwt' );
                var cerebralMetabolicRateFactor = data.cerebralMetabolicRateFactor.toNumber();

                return cerebralMetabolicRateFactor * 5 * brainMass / 100;
            }
        },
        cerebralBloodFlow: {
            name: 'Cerebral blood flow',
            abbreviation: 'CBF',
            data: {
                calculations: {
                    required: [ 'brainMass', 'cerebralMetabolicRateFactor' ]
                },
                variables: {
                    optional: [ 'paCO2' ]
                }
            },
            units: 'mL/min',
            description: '<ul><li>50 mL/min per 100g of brain mass.</li><li>Every mmHg in PaCO2 changes CBF by 1.5 mL/min per 100g of brain mass.</li><li>Cerebral blood flow and cerebral metabolic rate are coupled. Factors that alter CMR (e.g. temperature) will proportionally alter CBF.</li>',
            references: [
                'Tameem A, Krovvidi H, Cerebral physiology, Continuing Education in Anaesthesia Critical Care & Pain, Volume 13, Issue 4, August 2013, Pages 113–118, https://doi.org/10.1093/bjaceaccp/mkt001',
                'Brian JE Jr. Carbon dioxide and the cerebral circulation. Anesthesiology. 1998 May;88(5):1365-86. doi: 10.1097/00000542-199805000-00029. PMID: 9605698.'
            ],
            calculate: function( data ) {
                var brainMass = data.brainMass.toNumber( 'gwt' );
                var cerebralMetabolicRateFactor = data.cerebralMetabolicRateFactor.toNumber();
                var paCO2 = data.paCO2.toNumber( 'mmHg' );

                var cerebralBloodFlow = cerebralMetabolicRateFactor * 50 * brainMass / 100;

                if( paCO2 ) {
                    // CO2 reductions don't reduce CBF more than 50%
                    var minCerebralBloodFlow = cerebralBloodFlow / 2;

                    cerebralBloodFlow += 1.5 * brainMass / 100 * ( paCO2 - 40 );

                    cerebralBloodFlow = math.max( cerebralBloodFlow, minCerebralBloodFlow );
                }

                return cerebralBloodFlow;
            }
        }
    } );



    // Pulmonary
    mw.calculators.addCalculations( {
        paO2Predicted: {
            name: 'PaO<sub>2</sub> (predicted)',
            abbreviation: 'PaO<sub>2</sub> pred.',
            data: {
                variables: {
                    required: [ 'pAtm', 'fiO2', 'paCO2' ]
                }
            },
            units: 'mmHg',
            formula: '<math>P_{aO_2Predicted} = FiO_2*(P_{atm}-P_{H_{2}O})-\\frac{P_{aCO_2}}{R}</math>',
            references: [
                'McFarlane MJ, Imperiale TF. Use of the alveolar-arterial oxygen gradient in the diagnosis of pulmonary embolism. Am J Med. 1994 Jan;96(1):57-62. doi: 10.1016/0002-9343(94)90116-3. PMID: 8304364.'
            ],
            calculate: function( data ) {
                var fiO2 = data.fiO2.toNumber( 'pcto2' ) / 100;
                var pAtm = data.pAtm.toNumber( 'mmHg' );
                var pH2O = 47; // mmHg
                var paCO2 = data.paCO2.toNumber( 'mmHg' );
                var r = 0.8;

                return fiO2 * ( pAtm - pH2O ) - paCO2 / r;
            }
        },
        aaGradientO2: {
            name: 'A-a O<sub>2</sub> gradient',
            abbreviation: 'A-a O<sub>2</sub>',
            data: {
                calculations: {
                    required: [ 'paO2Predicted' ]
                },
                variables: {
                    required: [ 'paO2' ]
                }
            },
            units: 'mmHg',
            formula: '<math>\\text{A-a gradient}_{\\mathrm{O}_2} = P_{aO_2Predicted}-P_{aO_2} </math>',
            references: [
                'McFarlane MJ, Imperiale TF. Use of the alveolar-arterial oxygen gradient in the diagnosis of pulmonary embolism. Am J Med. 1994 Jan;96(1):57-62. doi: 10.1016/0002-9343(94)90116-3. PMID: 8304364.'
            ],
            calculate: function( data ) {
                var paO2Predicted = data.paO2Predicted.toNumber( 'mmHg' );
                var paO2 = data.paO2.toNumber( 'mmHg' );

                return math.round( paO2Predicted - paO2 );
            }
        },
        aaGradientO2Predicted: {
            name: 'A-a O<sub>2</sub> gradient (predicted)',
            abbreviation: 'A-a O<sub>2</sub> pred.',
            data: {
                variables: {
                    required: [ 'age' ]
                }
            },
            units: 'mmHg',
            formula: '<math>\\text{Predicted A-a gradient}_{\\mathrm{O}_2} = \\frac{(\\mathrm{age_{yr} + 10)}}{4}</math>',
            references: [
                'Hantzidiamantis PJ, Amaro E. Physiology, Alveolar to Arterial Oxygen Gradient. 2021 Feb 22. In: StatPearls [Internet]. Treasure Island (FL): StatPearls Publishing; 2021 Jan–. PMID: 31424737.'
            ],
            calculate: function( data ) {
                var age = data.age.toNumber( 'yr' );

                return ( age + 10 ) / 4;
            }
        },
        weightBasedTidalVolume: {
            name: 'Weight-based tidal volume',
            abbreviation: 'Weight-based TV',
            data: {
                calculations: {
                    required: [ 'ibw' ]
                },
                variables: {
                    required: [ 'weightBasedTidalVolumePerKgMin', 'weightBasedTidalVolumePerKgMax' ]
                }
            },
            type: 'string',
            description: '<ul><li>Calculated using ideal body weight</li><li>Low tidal volume uses 6-8 mL/kg<sup>1</sup><ul><li>Current evidence does not show benefit of intraoperative low tidal volumes for patients without pulmonary injury<sup>2</sup></li></ul></li>',
            references: [
                'Acute Respiratory Distress Syndrome Network, Brower RG, Matthay MA, Morris A, Schoenfeld D, Thompson BT, Wheeler A. Ventilation with lower tidal volumes as compared with traditional tidal volumes for acute lung injury and the acute respiratory distress syndrome. N Engl J Med. 2000 May 4;342(18):1301-8. doi: 10.1056/NEJM200005043421801. PMID: 10793162.',
                'Karalapillai D, Weinberg L, Peyton P, Ellard L, Hu R, Pearce B, Tan CO, Story D, O\'Donnell M, Hamilton P, Oughton C, Galtieri J, Wilson A, Serpa Neto A, Eastwood G, Bellomo R, Jones DA. Effect of Intraoperative Low Tidal Volume vs Conventional Tidal Volume on Postoperative Pulmonary Complications in Patients Undergoing Major Surgery: A Randomized Clinical Trial. JAMA. 2020 Sep 1;324(9):848-858. doi: 10.1001/jama.2020.12866. PMID: 32870298; PMCID: PMC7489812.'
            ],
            calculate: function( data ) {
                var ibw = data.ibw.toNumber( 'kgwt' );
                var weightBasedTidalVolumePerKgMin = data.weightBasedTidalVolumePerKgMin.toNumber( 'mL/kgwt' );
                var weightBasedTidalVolumePerKgMax = data.weightBasedTidalVolumePerKgMax.toNumber( 'mL/kgwt' );

                return math.round( weightBasedTidalVolumePerKgMin * ibw ) + '-' + math.round( weightBasedTidalVolumePerKgMax * ibw ) + ' mL';
            }
        }
    } );
}() );