periop-calculators
Version:
Evidence-based perioperative risk assessment calculators for healthcare professionals
502 lines (496 loc) • 18.9 kB
JavaScript
function validateAge(age) {
const errors = [];
if (age === undefined || age === null) {
errors.push({
code: 'AGE_REQUIRED',
message: 'Age is required',
field: 'age',
});
}
else if (age < 18) {
errors.push({
code: 'AGE_TOO_LOW',
message: 'STOP-BANG is validated for adults 18 years and older',
field: 'age',
});
}
else if (age > 120) {
errors.push({
code: 'AGE_INVALID',
message: 'Please enter a valid age',
field: 'age',
});
}
return {
isValid: errors.length === 0,
errors,
};
}
function validateBMI(bmi, weight, height) {
const errors = [];
if (bmi === undefined && (weight === undefined || height === undefined)) {
errors.push({
code: 'BMI_CALCULATION_ERROR',
message: 'Either BMI or both weight and height must be provided',
field: 'bmi',
});
}
else if (bmi !== undefined && (bmi < 10 || bmi > 70)) {
errors.push({
code: 'BMI_INVALID',
message: 'BMI must be between 10 and 70',
field: 'bmi',
});
}
return {
isValid: errors.length === 0,
errors,
};
}
function calculateBMI(weightKg, heightCm) {
const heightM = heightCm / 100;
return Number((weightKg / (heightM * heightM)).toFixed(1));
}
/**
* Custom error class for calculator-related errors
*/
class CalculatorError extends Error {
constructor(message, code, field) {
super(message);
this.name = 'CalculatorError';
this.code = code;
this.field = field;
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CalculatorError);
}
}
}
/**
* Calculate STOP-BANG score for obstructive sleep apnea risk assessment
*
* STOP-BANG is a validated screening tool for obstructive sleep apnea (OSA)
* in surgical patients. It consists of 8 yes/no questions.
*
* Score interpretation:
* - 0-2: Low risk of OSA
* - 3-4: Intermediate risk of OSA
* - 5-8: High risk of OSA
*
* @param input - STOP-BANG questionnaire responses
* @param demographics - Optional patient demographics for auto-calculation
* @returns StopBangResult with score, risk level, and recommendations
* @throws Error if required inputs are missing or invalid
*
* @example
* ```typescript
* const result = calculateStopBang({
* snoring: true,
* tiredness: true,
* observed: false,
* pressure: true,
* bmi: 36,
* age: 55,
* neckCircumference: 43,
* gender: 'male'
* });
* console.log(result.score); // 6
* console.log(result.risk); // 'high'
* ```
*/
function calculateStopBang(input, demographics) {
const errors = [];
// Determine age
const age = input.age ?? demographics?.age;
const ageValidation = validateAge(age);
if (!ageValidation.isValid) {
errors.push(...ageValidation.errors);
}
// Determine BMI
let bmi = input.bmi;
if (!bmi && demographics?.weight && demographics?.height) {
bmi = calculateBMI(demographics.weight, demographics.height);
}
const bmiValidation = validateBMI(bmi, demographics?.weight, demographics?.height);
if (!bmiValidation.isValid) {
errors.push(...bmiValidation.errors);
}
// Determine gender
const gender = input.gender ?? demographics?.sex;
if (!gender) {
errors.push({
code: 'GENDER_REQUIRED',
message: 'Gender is required for STOP-BANG calculation',
field: 'gender',
});
}
// Determine neck circumference
const neckCircumference = input.neckCircumference ?? demographics?.neckCircumference;
if (errors.length > 0) {
throw new CalculatorError(`Validation errors: ${errors.map((e) => e.message).join(', ')}`);
}
// Calculate individual components
const components = {
S: input.snoring,
T: input.tiredness,
O: input.observed,
P: input.pressure,
B: (bmi ?? 0) > 35,
A: (age ?? 0) > 50,
N: neckCircumference ? neckCircumference > 40 : false,
G: gender === 'male',
};
// Calculate total score
const score = Object.values(components).filter(Boolean).length;
// Determine risk level
let risk;
if (score <= 2) {
risk = 'low';
}
else if (score <= 4) {
risk = 'intermediate';
}
else {
risk = 'high';
}
// Generate interpretation
const interpretation = generateInterpretation$1(score, risk);
// Generate recommendations
const recommendations = generateRecommendations$1(score, risk, components);
return {
score,
risk,
interpretation,
components,
recommendations,
};
}
function generateInterpretation$1(score, risk) {
const baseInterpretation = `STOP-BANG score of ${score} indicates ${risk} risk of obstructive sleep apnea.`;
switch (risk) {
case 'low':
return `${baseInterpretation} The patient has a low probability of moderate to severe OSA.`;
case 'intermediate':
return `${baseInterpretation} Further evaluation may be warranted based on clinical judgment and planned procedure.`;
case 'high':
return `${baseInterpretation} The patient has a high probability of moderate to severe OSA. Consider polysomnography and perioperative precautions.`;
}
}
function generateRecommendations$1(score, risk, components) {
const recommendations = [];
// Universal recommendations
recommendations.push('Document STOP-BANG score in preoperative assessment');
switch (risk) {
case 'low':
recommendations.push('Proceed with standard anesthetic care');
recommendations.push('No specific OSA precautions required');
break;
case 'intermediate':
recommendations.push('Consider extended monitoring in PACU');
recommendations.push('Use caution with sedatives and opioids');
recommendations.push('Consider referral for sleep study if multiple risk factors present');
if (components.B || components.N) {
recommendations.push('Optimize positioning to maintain airway patency');
}
break;
case 'high':
recommendations.push('Strong consideration for polysomnography before elective surgery');
recommendations.push('Consider using short-acting agents');
recommendations.push('Plan for possible postoperative continuous monitoring');
recommendations.push('Have difficult airway equipment readily available');
recommendations.push('Consider regional anesthesia when appropriate');
recommendations.push('Minimize opioid use - consider multimodal analgesia');
if (score >= 6) {
recommendations.push('Consider postoperative ICU admission for major surgery');
}
break;
}
// Specific recommendations based on components
if (components.P) {
recommendations.push('Ensure blood pressure is optimized preoperatively');
}
if (components.B) {
recommendations.push('Consider weight loss counseling for elective procedures');
}
return recommendations;
}
/**
* Simplified version of STOP-BANG calculation using just the basic inputs
* @param input - Basic STOP-BANG inputs
* @returns Just the numeric score (0-8)
*/
function calculateStopBangScore(input) {
try {
const result = calculateStopBang(input);
return result.score;
}
catch (error) {
throw new CalculatorError(`Cannot calculate STOP-BANG score: ${error}`);
}
}
/**
* RCRI (Revised Cardiac Risk Index) Calculator
*
* Reference: Lee TH, et al. Derivation and prospective validation of a simple index
* for prediction of cardiac risk of major noncardiac surgery. Circulation. 1999;100(10):1043-9.
*/
/**
* Calculate the Revised Cardiac Risk Index (RCRI) score
* @param input RCRI risk factors
* @returns RCRI calculation result with score, risk class, and recommendations
*/
function calculateRCRI(input) {
// Calculate total score
let score = 0;
if (input.highRiskSurgery)
score++;
if (input.ischemicHeartDisease)
score++;
if (input.congestiveHeartFailure)
score++;
if (input.cerebrovascularDisease)
score++;
if (input.insulinDependentDiabetes)
score++;
if (input.renalInsufficiency)
score++;
// Determine risk class and estimated risk
let riskClass;
let estimatedRisk;
let riskPercentage;
if (score === 0) {
riskClass = 'I';
estimatedRisk = '0.4%';
riskPercentage = 0.4;
}
else if (score === 1) {
riskClass = 'II';
estimatedRisk = '0.9%';
riskPercentage = 0.9;
}
else if (score === 2) {
riskClass = 'III';
estimatedRisk = '6.6%';
riskPercentage = 6.6;
}
else {
riskClass = 'IV';
estimatedRisk = '≥11%';
riskPercentage = 11;
}
// Generate interpretation
const interpretation = generateInterpretation(score, riskClass, estimatedRisk);
// Generate recommendations
const recommendations = generateRecommendations(score, riskClass, input);
return {
score,
riskClass,
estimatedRisk,
riskPercentage,
interpretation,
riskFactors: {
highRiskSurgery: input.highRiskSurgery,
ischemicHeartDisease: input.ischemicHeartDisease,
congestiveHeartFailure: input.congestiveHeartFailure,
cerebrovascularDisease: input.cerebrovascularDisease,
insulinDependentDiabetes: input.insulinDependentDiabetes,
renalInsufficiency: input.renalInsufficiency
},
recommendations
};
}
function generateInterpretation(score, riskClass, estimatedRisk) {
if (score === 0) {
return `RCRI Class ${riskClass} with no risk factors identified. Estimated risk of major cardiac complications is ${estimatedRisk}. This represents very low cardiac risk.`;
}
else if (score === 1) {
return `RCRI Class ${riskClass} with 1 risk factor. Estimated risk of major cardiac complications is ${estimatedRisk}. This represents low cardiac risk.`;
}
else if (score === 2) {
return `RCRI Class ${riskClass} with 2 risk factors. Estimated risk of major cardiac complications is ${estimatedRisk}. This represents intermediate cardiac risk.`;
}
else {
return `RCRI Class ${riskClass} with ${score} risk factors. Estimated risk of major cardiac complications is ${estimatedRisk}. This represents high cardiac risk.`;
}
}
function generateRecommendations(score, riskClass, input) {
const recommendations = [];
// General recommendations based on risk class
if (score === 0) {
recommendations.push('Proceed with surgery with standard perioperative care');
recommendations.push('No additional cardiac testing indicated based on RCRI alone');
}
else if (score === 1) {
recommendations.push('Consider perioperative beta-blockade if not already prescribed');
recommendations.push('Ensure optimal medical management of cardiac risk factors');
}
else if (score === 2) {
recommendations.push('Consider cardiology consultation for perioperative risk assessment');
recommendations.push('Optimize medical management before elective surgery');
recommendations.push('Consider non-invasive cardiac testing if it will change management');
}
else {
recommendations.push('Strongly consider cardiology consultation before surgery');
recommendations.push('Consider additional cardiac testing (stress test, echo) if results would change management');
recommendations.push('Optimize all modifiable risk factors before elective surgery');
recommendations.push('Consider perioperative beta-blockade and statin therapy');
}
// Specific recommendations based on risk factors
if (input.congestiveHeartFailure) {
recommendations.push('Optimize heart failure management preoperatively');
recommendations.push('Consider echocardiography to assess current cardiac function');
}
if (input.ischemicHeartDisease) {
recommendations.push('Ensure patient is on appropriate antiplatelet therapy considering surgical bleeding risk');
recommendations.push('Continue statin therapy perioperatively');
}
if (input.insulinDependentDiabetes) {
recommendations.push('Optimize glycemic control perioperatively');
recommendations.push('Monitor for diabetic complications');
}
if (input.renalInsufficiency) {
recommendations.push('Avoid nephrotoxic medications');
recommendations.push('Consider nephrology consultation for perioperative management');
recommendations.push('Monitor volume status carefully');
}
if (input.cerebrovascularDisease) {
recommendations.push('Consider carotid evaluation if symptomatic');
recommendations.push('Maintain adequate blood pressure perioperatively');
}
// Add monitoring recommendation for all patients
recommendations.push('Monitor for signs of cardiac complications postoperatively');
return recommendations;
}
/**
* Helper function to determine if a surgery type is high risk according to RCRI
* @param surgeryType Description of the surgery
* @returns Whether the surgery is considered high risk
*/
function isHighRiskSurgery(surgeryType) {
const highRiskKeywords = [
'intraperitoneal',
'intrathoracic',
'suprainguinal vascular',
'aortic',
'major vascular',
'peripheral vascular',
'thoracic',
'abdominal',
'esophagectomy',
'hepatectomy',
'pancreatectomy',
'pneumonectomy'
];
const lowerSurgeryType = surgeryType.toLowerCase();
return highRiskKeywords.some(keyword => lowerSurgeryType.includes(keyword));
}
/**
* Calculates the Apfel Score for predicting postoperative nausea and vomiting (PONV)
* @param input - Patient risk factors
* @returns Apfel score result with risk assessment and recommendations
* @throws {CalculatorError} If input validation fails
*/
function calculateApfelScore(input) {
// Validate input
if (!input || typeof input !== 'object') {
throw new CalculatorError('Invalid input: ApfelScoreInput object required');
}
// Validate boolean fields
const booleanFields = ['female', 'nonSmoker', 'historyOfPONV', 'postoperativeOpioids'];
for (const field of booleanFields) {
if (typeof input[field] !== 'boolean') {
throw new CalculatorError(`Invalid input: ${field} must be a boolean value`);
}
}
// Calculate score (0-4 points)
let score = 0;
if (input.female)
score++;
if (input.nonSmoker)
score++;
if (input.historyOfPONV)
score++;
if (input.postoperativeOpioids)
score++;
// Risk percentages based on Apfel et al. 1999
const riskPercentages = {
0: 10,
1: 21,
2: 39,
3: 61,
4: 79
};
const riskPercentage = riskPercentages[score];
// Determine risk category
let risk;
if (score === 0) {
risk = 'low';
}
else if (score === 1) {
risk = 'moderate';
}
else if (score === 2) {
risk = 'high';
}
else {
risk = 'very-high';
}
// Generate interpretation
const riskFactorCount = score === 0 ? 'no' : score.toString();
const riskFactorPlural = score === 1 ? 'risk factor' : 'risk factors';
const interpretation = `Apfel Score of ${score} with ${riskFactorCount} ${riskFactorPlural} indicates ${risk.replace('-', ' ')} risk for PONV. Estimated incidence: ${riskPercentage}% chance of experiencing postoperative nausea and vomiting.`;
// Generate evidence-based recommendations
const recommendations = [];
if (score === 0) {
recommendations.push('Low risk patient - routine PONV prophylaxis may not be necessary');
recommendations.push('Consider prophylaxis if other risk factors present (e.g., type of surgery)');
}
else if (score === 1) {
recommendations.push('Consider single-agent PONV prophylaxis');
recommendations.push('Options include dexamethasone, ondansetron, or droperidol');
}
else if (score === 2) {
recommendations.push('Recommend dual-agent PONV prophylaxis');
recommendations.push('Consider combination therapy (e.g., dexamethasone + ondansetron)');
}
else if (score >= 3) {
recommendations.push('High-risk patient - recommend multimodal PONV prophylaxis');
recommendations.push('Consider 3-4 antiemetic agents from different classes');
recommendations.push('Consider total intravenous anesthesia (TIVA) with propofol');
if (input.postoperativeOpioids) {
recommendations.push('Consider regional anesthesia or non-opioid analgesics to reduce opioid requirements');
}
}
// Always include general recommendations
recommendations.push('Monitor for PONV in PACU and postoperative period');
recommendations.push('Have rescue antiemetics readily available');
return {
score,
riskPercentage,
risk,
interpretation,
riskFactors: {
female: input.female,
nonSmoker: input.nonSmoker,
historyOfPONV: input.historyOfPONV,
postoperativeOpioids: input.postoperativeOpioids
},
recommendations
};
}
/**
* Returns detailed information about a specific Apfel risk factor
* @param factor - The risk factor to get information about
* @returns Detailed description of the risk factor
*/
function getApfelRiskFactorInfo(factor) {
const factorInfo = {
female: 'Female gender is associated with 2-3 times higher risk of PONV compared to males',
nonSmoker: 'Non-smoking status increases PONV risk, possibly due to chronic nicotine exposure providing antiemetic effects in smokers',
historyOfPONV: 'Previous PONV or motion sickness is a strong predictor of future PONV episodes',
postoperativeOpioids: 'Postoperative opioid use is a dose-dependent risk factor for PONV'
};
return factorInfo[factor] || 'Unknown risk factor';
}
export { calculateApfelScore, calculateBMI, calculateRCRI, calculateStopBang, calculateStopBangScore, getApfelRiskFactorInfo, isHighRiskSurgery };
//# sourceMappingURL=index.esm.js.map