Add nursing assessment packs, ward board, and nurse performance KPIs.
Deploy Ladill Care / deploy (push) Successful in 55s

Seeds NEWS2/Braden/Morse/pain packs with visit vitals and a unit dashboard for MAR, assessments, notes, and handover activity.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-20 10:32:52 +00:00
co-authored by Cursor
parent cb6e59e5ed
commit 9eb6c21828
18 changed files with 1511 additions and 16 deletions
+136
View File
@@ -0,0 +1,136 @@
{
"template": {
"code": "braden",
"name": "Braden Scale (pressure injury risk)",
"category": "screening",
"version": 1,
"is_current": true,
"is_active": true,
"scoring_strategy": "sum_items",
"description": "Braden Scale for predicting pressure sore risk. Lower scores = higher risk (623).",
"meta": {
"capture_roles": ["nurse", "ed_nurse", "theatre_nurse", "labour_ward_nurse", "fertility_nurse", "dialysis_nurse", "midwife", "doctor", "general_physician"],
"specialty": "nursing",
"pack_family": "nursing",
"estimated_minutes": 5,
"max_total": 23,
"severity_bands": [
{ "max": 9, "label": "Very high risk" },
{ "max": 12, "label": "High risk" },
{ "max": 14, "label": "Moderate risk" },
{ "max": 18, "label": "Mild risk" },
{ "max": 23, "label": "No apparent risk" }
],
"attribution": "Braden Scale — verify licensing for commercial redistribution of branded materials."
}
},
"questions": [
{
"code": "sensory",
"section": "Risk factors",
"label": "Sensory perception",
"answer_type": "score_item",
"is_required": true,
"sort_order": 10,
"score_key": "braden",
"options": {
"max": 4,
"choices": [
{ "code": "1", "label": "Completely limited", "score": 1 },
{ "code": "2", "label": "Very limited", "score": 2 },
{ "code": "3", "label": "Slightly limited", "score": 3 },
{ "code": "4", "label": "No impairment", "score": 4 }
]
}
},
{
"code": "moisture",
"section": "Risk factors",
"label": "Moisture",
"answer_type": "score_item",
"is_required": true,
"sort_order": 20,
"score_key": "braden",
"options": {
"max": 4,
"choices": [
{ "code": "1", "label": "Constantly moist", "score": 1 },
{ "code": "2", "label": "Very moist", "score": 2 },
{ "code": "3", "label": "Occasionally moist", "score": 3 },
{ "code": "4", "label": "Rarely moist", "score": 4 }
]
}
},
{
"code": "activity",
"section": "Risk factors",
"label": "Activity",
"answer_type": "score_item",
"is_required": true,
"sort_order": 30,
"score_key": "braden",
"options": {
"max": 4,
"choices": [
{ "code": "1", "label": "Bedfast", "score": 1 },
{ "code": "2", "label": "Chairfast", "score": 2 },
{ "code": "3", "label": "Walks occasionally", "score": 3 },
{ "code": "4", "label": "Walks frequently", "score": 4 }
]
}
},
{
"code": "mobility",
"section": "Risk factors",
"label": "Mobility",
"answer_type": "score_item",
"is_required": true,
"sort_order": 40,
"score_key": "braden",
"options": {
"max": 4,
"choices": [
{ "code": "1", "label": "Completely immobile", "score": 1 },
{ "code": "2", "label": "Very limited", "score": 2 },
{ "code": "3", "label": "Slightly limited", "score": 3 },
{ "code": "4", "label": "No limitation", "score": 4 }
]
}
},
{
"code": "nutrition",
"section": "Risk factors",
"label": "Nutrition",
"answer_type": "score_item",
"is_required": true,
"sort_order": 50,
"score_key": "braden",
"options": {
"max": 4,
"choices": [
{ "code": "1", "label": "Very poor", "score": 1 },
{ "code": "2", "label": "Probably inadequate", "score": 2 },
{ "code": "3", "label": "Adequate", "score": 3 },
{ "code": "4", "label": "Excellent", "score": 4 }
]
}
},
{
"code": "friction",
"section": "Risk factors",
"label": "Friction and shear",
"answer_type": "score_item",
"is_required": true,
"sort_order": 60,
"score_key": "braden",
"options": {
"max": 3,
"choices": [
{ "code": "1", "label": "Problem", "score": 1 },
{ "code": "2", "label": "Potential problem", "score": 2 },
{ "code": "3", "label": "No apparent problem", "score": 3 }
]
}
}
]
}
+125
View File
@@ -0,0 +1,125 @@
{
"template": {
"code": "morse",
"name": "Morse Fall Scale",
"category": "screening",
"version": 1,
"is_current": true,
"is_active": true,
"scoring_strategy": "sum_items",
"description": "Morse Fall Scale for inpatient fall risk. Higher scores = higher risk (0125).",
"meta": {
"capture_roles": ["nurse", "ed_nurse", "theatre_nurse", "labour_ward_nurse", "fertility_nurse", "dialysis_nurse", "midwife", "doctor", "general_physician"],
"specialty": "nursing",
"pack_family": "nursing",
"estimated_minutes": 3,
"max_total": 125,
"severity_bands": [
{ "max": 24, "label": "Low risk" },
{ "max": 44, "label": "Medium risk" },
{ "max": 125, "label": "High risk" }
],
"attribution": "Morse Fall Scale — verify licensing for commercial redistribution of branded materials."
}
},
"questions": [
{
"code": "history_falling",
"section": "Fall risk",
"label": "History of falling",
"answer_type": "score_item",
"is_required": true,
"sort_order": 10,
"score_key": "morse",
"options": {
"max": 25,
"choices": [
{ "code": "0", "label": "No", "score": 0 },
{ "code": "25", "label": "Yes", "score": 25 }
]
}
},
{
"code": "secondary_diagnosis",
"section": "Fall risk",
"label": "Secondary diagnosis",
"answer_type": "score_item",
"is_required": true,
"sort_order": 20,
"score_key": "morse",
"options": {
"max": 15,
"choices": [
{ "code": "0", "label": "No", "score": 0 },
{ "code": "15", "label": "Yes", "score": 15 }
]
}
},
{
"code": "ambulatory_aid",
"section": "Fall risk",
"label": "Ambulatory aid",
"answer_type": "score_item",
"is_required": true,
"sort_order": 30,
"score_key": "morse",
"options": {
"max": 30,
"choices": [
{ "code": "0", "label": "None / bed rest / nurse assist", "score": 0 },
{ "code": "15", "label": "Crutches / cane / walker", "score": 15 },
{ "code": "30", "label": "Furniture", "score": 30 }
]
}
},
{
"code": "iv_therapy",
"section": "Fall risk",
"label": "IV / heparin lock",
"answer_type": "score_item",
"is_required": true,
"sort_order": 40,
"score_key": "morse",
"options": {
"max": 20,
"choices": [
{ "code": "0", "label": "No", "score": 0 },
{ "code": "20", "label": "Yes", "score": 20 }
]
}
},
{
"code": "gait",
"section": "Fall risk",
"label": "Gait / transferring",
"answer_type": "score_item",
"is_required": true,
"sort_order": 50,
"score_key": "morse",
"options": {
"max": 20,
"choices": [
{ "code": "0", "label": "Normal / bedrest / immobile", "score": 0 },
{ "code": "10", "label": "Weak", "score": 10 },
{ "code": "20", "label": "Impaired", "score": 20 }
]
}
},
{
"code": "mental_status",
"section": "Fall risk",
"label": "Mental status",
"answer_type": "score_item",
"is_required": true,
"sort_order": 60,
"score_key": "morse",
"options": {
"max": 15,
"choices": [
{ "code": "0", "label": "Oriented to own ability", "score": 0 },
{ "code": "15", "label": "Forgets limitations", "score": 15 }
]
}
}
]
}
+152
View File
@@ -0,0 +1,152 @@
{
"template": {
"code": "news2",
"name": "NEWS2 (Nursing early warning)",
"category": "screening",
"version": 1,
"is_current": true,
"is_active": true,
"scoring_strategy": "sum_items",
"description": "National Early Warning Score 2 style physiological track-and-trigger for ward nursing. Scores 020.",
"meta": {
"capture_roles": ["nurse", "ed_nurse", "theatre_nurse", "labour_ward_nurse", "fertility_nurse", "dialysis_nurse", "midwife", "doctor", "general_physician", "emergency_physician", "obstetrician", "gynecologist", "obgyn"],
"specialty": "nursing",
"pack_family": "nursing",
"estimated_minutes": 3,
"max_total": 20,
"severity_bands": [
{ "max": 4, "label": "Low" },
{ "max": 6, "label": "Medium" },
{ "max": 20, "label": "High" }
],
"attribution": "NEWS2-inspired clinical scoring for operational use. Official NEWS2 materials remain Royal College of Physicians IP — verify licensing for branded redistribution."
}
},
"questions": [
{
"code": "respiration",
"section": "Physiology",
"label": "Respiration rate (breaths/min)",
"answer_type": "score_item",
"is_required": true,
"sort_order": 10,
"score_key": "news2",
"options": {
"max": 3,
"choices": [
{ "code": "0", "label": "1220", "score": 0 },
{ "code": "1", "label": "911", "score": 1 },
{ "code": "2", "label": "2124", "score": 2 },
{ "code": "3a", "label": "≤8", "score": 3 },
{ "code": "3b", "label": "≥25", "score": 3 }
]
}
},
{
"code": "spo2",
"section": "Physiology",
"label": "SpO₂ scale 1 (%)",
"answer_type": "score_item",
"is_required": true,
"sort_order": 20,
"score_key": "news2",
"options": {
"max": 3,
"choices": [
{ "code": "0", "label": "≥96", "score": 0 },
{ "code": "1", "label": "9495", "score": 1 },
{ "code": "2", "label": "9293", "score": 2 },
{ "code": "3", "label": "≤91", "score": 3 }
]
}
},
{
"code": "air_oxygen",
"section": "Physiology",
"label": "Air or oxygen",
"answer_type": "score_item",
"is_required": true,
"sort_order": 30,
"score_key": "news2",
"options": {
"max": 2,
"choices": [
{ "code": "0", "label": "Air", "score": 0 },
{ "code": "2", "label": "Oxygen", "score": 2 }
]
}
},
{
"code": "systolic_bp",
"section": "Physiology",
"label": "Systolic BP (mmHg)",
"answer_type": "score_item",
"is_required": true,
"sort_order": 40,
"score_key": "news2",
"options": {
"max": 3,
"choices": [
{ "code": "0", "label": "111219", "score": 0 },
{ "code": "1", "label": "101110", "score": 1 },
{ "code": "2", "label": "91100", "score": 2 },
{ "code": "3a", "label": "≤90", "score": 3 },
{ "code": "3b", "label": "≥220", "score": 3 }
]
}
},
{
"code": "pulse",
"section": "Physiology",
"label": "Pulse (bpm)",
"answer_type": "score_item",
"is_required": true,
"sort_order": 50,
"score_key": "news2",
"options": {
"max": 3,
"choices": [
{ "code": "0", "label": "5190", "score": 0 },
{ "code": "1", "label": "4150 or 91110", "score": 1 },
{ "code": "2", "label": "111130", "score": 2 },
{ "code": "3a", "label": "≤40", "score": 3 },
{ "code": "3b", "label": "≥131", "score": 3 }
]
}
},
{
"code": "consciousness",
"section": "Physiology",
"label": "Consciousness",
"answer_type": "score_item",
"is_required": true,
"sort_order": 60,
"score_key": "news2",
"options": {
"max": 3,
"choices": [
{ "code": "0", "label": "Alert", "score": 0 },
{ "code": "3", "label": "CVPU", "score": 3 }
]
}
},
{
"code": "temperature",
"section": "Physiology",
"label": "Temperature (°C)",
"answer_type": "score_item",
"is_required": true,
"sort_order": 70,
"score_key": "news2",
"options": {
"max": 3,
"choices": [
{ "code": "0", "label": "36.138.0", "score": 0 },
{ "code": "1a", "label": "35.136.0 or 38.139.0", "score": 1 },
{ "code": "2", "label": "≥39.1", "score": 2 },
{ "code": "3", "label": "≤35.0", "score": 3 }
]
}
}
]
}
@@ -0,0 +1,49 @@
{
"template": {
"code": "pain_nrs",
"name": "Pain NRS (010)",
"category": "screening",
"version": 1,
"is_current": true,
"is_active": true,
"scoring_strategy": "single_value",
"description": "Numeric rating scale for pain intensity at rest / with activity.",
"meta": {
"capture_roles": ["nurse", "ed_nurse", "theatre_nurse", "labour_ward_nurse", "fertility_nurse", "dialysis_nurse", "midwife", "doctor", "general_physician", "emergency_physician"],
"specialty": "nursing",
"pack_family": "nursing",
"estimated_minutes": 1,
"max_total": 10,
"severity_bands": [
{ "max": 3, "label": "Mild" },
{ "max": 6, "label": "Moderate" },
{ "max": 10, "label": "Severe" }
]
}
},
"questions": [
{
"code": "pain_score",
"section": "Pain",
"label": "Pain intensity (0 = none, 10 = worst imaginable)",
"answer_type": "scale",
"is_required": true,
"sort_order": 10,
"score_key": "pain",
"options": {
"min": 0,
"max": 10,
"step": 1
}
},
{
"code": "pain_location",
"section": "Pain",
"label": "Location / notes",
"answer_type": "text",
"is_required": false,
"sort_order": 20,
"options": {}
}
]
}
@@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Visit-scoped vitals for ward nursing (no consultation required).
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('care_visit_vitals', function (Blueprint $table) {
$table->id();
$table->string('owner_ref')->index();
$table->foreignId('organization_id')->constrained('care_organizations')->cascadeOnDelete();
$table->foreignId('visit_id')->constrained('care_visits')->cascadeOnDelete();
$table->foreignId('patient_id')->constrained('care_patients')->cascadeOnDelete();
$table->foreignId('care_unit_id')->nullable()->constrained('care_care_units')->nullOnDelete();
$table->unsignedSmallInteger('bp_systolic')->nullable();
$table->unsignedSmallInteger('bp_diastolic')->nullable();
$table->unsignedSmallInteger('pulse')->nullable();
$table->decimal('temperature', 4, 1)->nullable();
$table->unsignedSmallInteger('spo2')->nullable();
$table->unsignedSmallInteger('respiratory_rate')->nullable();
$table->string('recorded_by')->nullable();
$table->timestamp('recorded_at');
$table->text('notes')->nullable();
$table->timestamps();
$table->index(['visit_id', 'recorded_at']);
$table->index(['care_unit_id', 'recorded_at']);
});
}
public function down(): void
{
Schema::dropIfExists('care_visit_vitals');
}
};