WP Manifestindependent plugin directory
manifest / unclassified / wpp-ms-calculator

MS Calculator

Custom Milanscore calculator plugin

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/finoz/wpp-ms-calculator/archive/refs/heads/master.zip
Updates itself. It ships its own updater, so new versions appear under Dashboard → Updates.

Lightweight WordPress plugin that renders a configurable scoring calculator: a handful of input parameters (age, gender, ...), a score out.

Zero external dependencies. Scoring happens entirely client-side (vanilla JS, no REST calls): the field → points mapping and the result bands travel down embedded in the page as JSON, next to the form. PHP 8.0+, WP 6.0+.

Structure and update mechanism (GitHub auto-update, JSON config editor in admin) mirror the sibling plugin fnz-forms.


Quick start

  1. Upload ms-calculator/ to wp-content/plugins/ and activate.
  2. Go to Settings → MS Calculator and paste your JSON configuration.
  3. Add the shortcode to any page: [ms_calculator id="milan_score"]

To iterate on the interaction without WordPress: open preview/milan-score.html directly in a browser — it statically replicates the markup msc_render_calculator() generates from the bundled config-default.json.


Shortcode

[ms_calculator id="your_calculator"]

id must match a key in the calculators object of your config.


Configuration (JSON)

The full config lives in Settings → MS Calculator → Calculator configuration (stored in the database). Alternatively you can place a file at wp-content/ms-calculator-config.json — the admin UI always takes priority.

Minimal example

{
  "calculators": {
    "milan_score": {
      "title": "Milan Score 2.0",
      "fields": [
        {
          "id": "age",
          "type": "radio",
          "label": "Age",
          "required": true,
          "options": [
            { "value": "age_40", "label": "≤ 40 years", "points": 0 },
            { "value": "age_70", "label": "> 70 years",  "points": 2 }
          ]
        },
        {
          "id": "gender",
          "type": "radio",
          "label": "Gender",
          "required": true,
          "options": [
            { "value": "gender_f", "label": "Female", "points": 0 },
            { "value": "gender_m", "label": "Male",   "points": 3 }
          ]
        }
      ],
      "percentages": { "0": 8, "1": 21, "2": 100 },
      "bands": [
        { "min": 0, "max": 1, "label": "Unlikely" },
        { "min": 2, "max": 2, "label": "Very likely" }
      ]
    }
  }
}

The score updates live as fields are filled in — there is no "Calculate" button: the intended interaction is the clinician clicking through the patient's values in sequence and reading the result once every required field has been answered (see assets/ms-calculator.js). Once complete, the result panel shows three independent readings of the same total: Total score, % of risk, and Risk class.

Calculator-level keys

Key Required Default Description
title no — Title shown above the form
intro no — Intro copy
fields yes — Array of field objects
percentages no — Point-exact score → percentage lookup, feeds the "% of risk" row (see below)
bands no [] Min/max ranges with a verdict label each, feed the "Risk class" row (see below)

Field object

Key Required Default Description
id yes — Unique within the calculator; becomes the input name
type no select See field types below
label no same as id Visible label text
required no false HTML required attribute (not on checkbox-group)
options no — Required for select, radio, checkbox-group
points no 0 boolean only: points awarded when checked

Field types

Type HTML element Scoring
select <select> Points of the chosen option
radio group of <input type="radio"> Points of the chosen option
checkbox-group several <input type="checkbox"> Sum of points of every checked option
boolean single <input type="checkbox"> The field's points, if checked

select exists but is discouraged when there are only a few options (≤5–6): with radio the person filling in the form sees every choice at a glance and picks it in one click, instead of the two clicks (open the menu, then choose) a native <select> requires — that's why the Milan Score 2.0 in config-default.json uses radio for all 7 fields, gender included.

options format (for select, radio, checkbox-group):

"options": [
  { "value": "age_40", "label": "≤ 40 years", "points": 0 },
  { "value": "age_70", "label": "> 70 years",  "points": 2 }
]

value convention: {field_id}_{number}, where {number} is the clinical threshold with no separators (e.g. age_4070 for the 40–70 range). It only needs to stay unique within the field. For a field with just two options sharing the same cutoff (e.g. BMI < 25 / ≥ 25), a leading zero marks "below": bmi_025 vs bmi_25. For a field with no natural clinical number (e.g. gender), fall back to the option's initial: gender_f / gender_m.

The total score is the sum of the points of every field. percentages and bands are two independent readings of that same total — one numeric, one qualitative — shown side by side, not a fallback of one another.

Score → percentage lookup (percentages)

The total score is the sum of the points of every field. When each individual score has its own empirical value — as with the Milan Score 2.0, where the score/risk relationship is a lookup table, not a formula — use percentages: a plain number keyed by score (as a string). Feeds the % of risk row.

"percentages": { "0": 8, "1": 9, "20": 100 }

Displayed as {value}%, e.g. 9%.

Risk class (bands)

bands gives the total score a qualitative verdict: the first band whose [min, max] range contains the total is shown in the Risk class row, as {label} ({min}–{max} points) (both bounds inclusive and optional — omitting min means -∞, omitting max means +∞; the range in parentheses is computed from min/max, not written out by hand, so it can't drift out of sync with the label).

"bands": [
  { "min": 0,  "max": 3,  "label": "Unlikely" },
  { "min": 4,  "max": 10, "label": "Likely" },
  { "min": 11, "max": 20, "label": "Very likely" }
]

→ e.g. score 7 shows Risk class: Likely (4–10 points).


Generated markup

No "real" CSS is shipped by the plugin — assets/ms-calculator.css is a text placeholder pending the final design (see next section). Field markup reuses the same classes as fnz-forms (.form-group, label after/inside the input) so both plugins automatically pick up the same theme-level styling. Full example (2 fields out of 7) in preview/milan-score.html:

<div class="msc-calculator-wrap">
  <h3 class="msc-calculator__title">Milan Score 2.0</h3>

  <form class="form msc-calculator" id="milan_score_calculator" data-msc-id="milan_score" novalidate>
    <div class="form-group form-group--radio" role="group" aria-label="Gender">
      <span class="form-group__legend">Gender</span>
      <div class="form-group__options">
        <label for="milan_score_gender_0">
          <input type="radio" id="milan_score_gender_0" name="milan_score_gender" value="gender_f" required>
          <span>Female</span>
        </label>
        <label for="milan_score_gender_1">
          <input type="radio" id="milan_score_gender_1" name="milan_score_gender" value="gender_m" required>
          <span>Male</span>
        </label>
      </div>
    </div>
    <!-- … the other 6 questions, same pattern … -->
  </form>

  <!-- Result: always visible, updated live on every click -->
  <div class="msc-result" id="milan_score_result" data-msc-result aria-live="polite">
    <p class="msc-result__row"><span class="msc-result__caption">Total score</span><span class="msc-result__value" data-msc-result-score>0</span></p>
    <p class="msc-result__row"><span class="msc-result__caption">% of risk</span><span class="msc-result__value" data-msc-result-percent>—</span></p>
    <p class="msc-result__row"><span class="msc-result__caption">Risk class</span><span class="msc-result__value" data-msc-result-class>—</span></p>
    <p class="msc-result__hint" data-msc-result-hint>Fill in all fields to see the risk.</p>
  </div>

  <script type="application/json" id="milan_score_msc-config">{ "fields": […], "percentages": {…}, "bands": […] }</script>
</div>

How scoring works

No server call, no "Calculate" button: assets/ms-calculator.js listens for change events on the form, recalculates the score on every click by reading the `