The PLUM Prostate Cancer Risk Calculator

by lewpaka

HTML

<!DOCTYPE html>
<html>

  <head>
    <meta charset=utf-8 />
    <meta name="author" content="Sumeet S. Shah">
    <title>The PLUM Prostate Cancer Risk Calculator</title>
  </head>

  <body>
    <div class="header-container">
      <h2>
        The PLUM Prostate Cancer Risk Calculator
      </h2>
      <em>
        A calculator to estimate prostate cancer detection risk for patients receiving a multiparametric MRI (mpMRI) of the prostate. This calculator is only for patients who have never had a diagnosis of prostate cancer.
      </em>
    </div>
    <form>
      <div class="table">
        <div class="tr">
          <label class="td" for="age">Age (years): </label>
          <input class="td" id="age" type="number" onchange="validateNumberField('age')">
          <p class="td field-description">(40 - 90 years allowed)</p>
        </div>

        <div class="tr">
          <label class="td" for="psa">PSA (ng/dL): </label>
          <input class="td" id="psa" type="number" onchange="validateNumberField('psa')">
          <p class="td field-description">(0.3 - 84 ng/dL allowed)</p>
        </div>

        <div class="tr">
          <label class="td" for="prostate-volume">Prostate Volume (cc): </label>
          <input class="td" id="prostate-volume" type="number" onchange="validateNumberField('prostate-volume')">
          <p class="td field-description">(15 - 350 cc allowed)</p>
        </div>

        <div class="tr">
          <label class="td" for="pi-rads-score">PI-RADS Score: </label>
          <input class="td" id="pi-rads-score" type="number" onchange="validateNumberField('pi-rads-score', true)">
          <p class="td field-description">(Highest PI-RADS scored lesion on MRI: 0, 1, 2, 3, 4, 5)</p>
        </div>
      </div>

      <div class="form-question">
        <label for="african-ancestry">Do you have African ancestry?</label>
        <div>
          <label>
            <input type="radio" name="african-ancestry" value="1">
            Yes
         ...

CSS

table,
th {
  border-bottom: 1px solid black;
}

tr td {
  min-width: 100px;
}

body {
  font-family: 'Open Sans', arial, sans-serif;
  margin: 30px;
}

.table {
  display: table;
}

.form-question {
  margin-top: 10px;
}

.form-question div {
  margin-top: 5px;
}

.header-container {
  margin-bottom: 10px;
}

.tr {
  display: table-row;
}

.td {
  display: table-cell;
  min-width: 160px;
  margin-top: 10px;
}

.field-description {
  color: grey;
  padding-left: 10px;
}

.result {
  text-align: right;
}

.pbcg-table {
  font-size: 1em;
  color: gray;
}

.notes-text {
  font-size: 0.6em;
  margin-bottom: 10px;
}

#notes-header {
  margin-bottom: -5px;
}

#references {
  margin-bottom: -5px;
}

#calculate-risk {
  font-size: 1em;
}

JavaScript

const h1Coefficients = {
  piRads: {
    0: 0,
    1: 0,
    2: 0,
    3: 0.353384,
    4: 0.949164,
    5: 1.874741
  },
  age: 0.0350792,
  africanAncestry: 0.5251729,
  familyHistory: 0.3260991,
  priorNegativeBiopsy: -1.17898,
  psad: 0.1668917,
  prostateVolume: -0.0186987,
  constant: -2.397652
}
const h2Coefficients = {
  piRads: {
    0: 0,
    1: 0,
    2: 0,
    3: 0.4550155,
    4: 2.210478,
    5: 3.634756
  },
  age: 0.0772189,
  africanAncestry: 0.490936,
  familyHistory: 0.4781321,
  priorNegativeBiopsy: -1.15653,
  psad: 1.204331,
  prostateVolume: -0.0201334,
  constant: -3.492468
}
const s1Coefficients = {
  age: 0.01780617,
  africanAncestry: 0.78721039,
  familyHistory: 0.33233636,
  priorNegativeBiopsy: -0.83613721,
  psad: 0.13617244,
  constant: -2.44052108,
  dre: 0.04612721
}
const s2Coefficients = {
  age: 0.05566536,
  africanAncestry: 0.61596975,
  familyHistory: 0.61003848,
  priorNegativeBiopsy: -1.27437249,
  psad: 0.7999651,
  constant: -6.36851856,
  dre: 0.85780143
}
const minMaxValues = {
  age: {
    min: 40,
    max: 90
  },
  psa: {
    min: 0.3,
    max: 84
  },
  'prostate-volume': {
    min: 15,
    max: 350
  },
  'pi-rads-score': {
    min: 0,
    max: 5
  }
}

const calculateRisk = () => {
  // Get form values
  const {
    age,
    africanAncestry,
    familyHistory,
    priorNegativeBiopsy,
    psa,
    prostateVolume,
    piRadsScore,
    dre
  } = getFormFieldValues();

  // Calculate risk values
  calculatePlumRisk(age, africanAncestry, familyHistory, priorNegativeBiopsy, psa, prostateVolume, piRadsScore);
  calcualtePbcgRisk(age, africanAncestry, familyHistory, priorNegativeBiopsy, psa, dre);

}

const calculatePlumRisk = (age, africanAncestry, familyHistory, priorNegativeBiopsy, psa, prostateVolume, piRadsScore) => {
  const h1 = h1Coefficients.constant +
    (age * h1Coefficients.age) +
    (africanAncestry * h1Coefficients.africanAncestry) +
    (familyHistory * h1Coefficients.familyHistory) +
   ...