Real-Time NEWS2 Patient Scoring and Monitoring with Tiered Alerts, Longitudinal Tracking, and Dual Visualisations
Tech Stack: Python · FastAPI · Uvicorn · Pydantic · Matplotlib · Pytest · GitHub Actions (CI/CD)
This project is a Python-based clinical monitoring system that implements real-time NEWS2 (National Early Warning Score 2) calculation, tiered alert generation, and longitudinal vital sign tracking. The system accepts structured vital sign inputs via either a command-line interface (CLI) or a deployed FastAPI JSON API and applies deterministic scoring logic to compute NEWS2 values in accordance with established clinical thresholds. Based on aggregated scores, the system generates stratified alert levels to highlight potential patient deterioration and support structured monitoring workflows.
Vital sign data is persistently stored in CSV format with patient-level GDPR-compliant mapping, enabling longitudinal tracking across multiple observations. The system supports both lightweight terminal-based ASCII visualisations and Matplotlib-generated graphical trend plots, allowing users to interpret physiological trajectories over time. This dual visualisation approach demonstrates both operational usability and analytical reporting capability.
The architecture is fully deterministic and rule-based, reflecting direct implementation of validated clinical scoring criteria rather than probabilistic or machine learning inference. Strong input validation ensures structural integrity of submitted data, and outputs are returned in structured JSON format to enable integration with external systems or electronic health record workflows.
Version 2 extends the original CLI tool into a deployable backend service with documented API endpoints, enabling remote access and programmatic consumption. Automated unit tests, API endpoint testing, and continuous integration workflows validate scoring logic, alert generation, and system stability. The modular codebase supports future extension, including potential integration of predictive modelling or machine learning components, while preserving transparent and reproducible clinical scoring logic.
Overall, the system demonstrates deterministic clinical algorithm implementation, backend API deployment, persistent data handling, automated testing, and longitudinal physiological trend analysis within a production-oriented Python project.
The deployed API (v2) is live on Render and can be accessed here for interactive testing and demonstration.
Figure 1: Overview of patient vitals input, NEWS2 scoring, alert logic, and trend visualisation workflow.
| Feature | Description |
|---|---|
| Clinically-informed input handling | Collects vitals and level of consciousness, mapping to structured format. |
| Tiered alert logic | NEWS2 score reflects severity levels clinicians use for escalation decisions. |
| Trend visualisation | ASCII charts for quick CLI checks; Matplotlib plots for detailed analysis and portfolio showcase. |
| Patient ID anonymisation | Ensures privacy and GDPR-compliance for multi-patient tracking. |
| Robust error handling | Handles CSV header mismatches, data type conversions, and overlapping alert thresholds; ensures reliable plotting and accurate historical analysis. |
| Modular architecture | Functions separated by responsibility (input, scoring, alerting, visualization) for maintainability and expansion. |
| Foundation for predictive analytics | Clinically-informed scoring and trend data enable future AI/ML models for deterioration detection and decision support. |
| Feature | CLI (v1) | FastAPI (v2 - Live on Render) |
|---|---|---|
| User Interaction | Command-line prompts, numeric & categorical inputs | JSON-based API requests (GET /Root, POST /add_vitals/, GET /patient/{patient_id}, GET /trends/{patient_id}/png/, GET /trends/{patient_id}/json/) |
| Data Entry | Manual typing via terminal | Scriptable input via HTTP requests or Swagger UI |
| Visualisation | ASCII bar charts; Matplotlib plots saved locally | API returns JSON data; Matplotlib plots generated on server as PNG |
| Alerts & Scoring | NEWS2 scoring, tiered alerts printed to console | NEWS2 scoring calculated server-side; alerts included in structured JSON response |
| Persistence | CSV storage with unique patient IDs | CSV storage leveraged for backend; data retrieval via endpoints |
| Testing & CI/CD | Manual verification of CLI workflows | Automated endpoint validation using GitHub Actions, ensuring production-readiness |
| Clinical Insight | Real-time feedback at bedside; supports immediate clinical decision-making | Enables integration into EHRs, dashboards, or telemedicine apps, supports remote monitoring and analytics |
-
User Input
- Collects patient vitals (BP, HR, RR, Temp, O₂ sats, Level of Consciousness) via CLI.
- Validates input ranges and types to ensure clinical reliability.
- Maps multiple patients to anonymized IDs for longitudinal tracking.
-
Scoring & Alerts
- Computes NEWS2 score based on entered vitals.
- Integrated NEWS2 scoring determines tiered alert level (Normal → Mild → Moderate → Severe) reflecting real-world clinical prioritisation.
- Provides user-friendly alert messages highlighting actionable concerns.
-
Trend & Output Display
- Prints recent patient readings with aligned formatting for readability.
- Generates visualisations:
- ASCII charts for quick terminal-based monitoring.
Provides lightweight, real-time monitoring in terminal environments for quick interpretation during ward rounds or telemedicine sessions. - Matplotlib plots for portfolio-quality, dual-axis trends (vitals + NEWS2).
Enables detailed longitudinal monitoring, supporting clinical review, handover, and identification of subtle deterioration patterns over time.
- ASCII charts for quick terminal-based monitoring.
- Supports retrieval of historical data for multiple patients, enabling longitudinal trend analysis.
- Robust input validation: Ensures safe ranges, correct data types, and consistent nested data structures.
- Comprehensive error handling:
- Fixed CSV header issues to avoid KeyErrors.
- Type conversion for plotting prevents string/float errors.
- Adjusted diastolic BP alert ranges to prevent overlapping thresholds.
- Data persistence: Stores historical readings in CSV (vitals.csv) with patient mapping (patient_mapping.csv).
- Nested dictionary flattening: for CSV compatibility and smooth plotting.
- ASCII normalisation: Values scaled proportionally to fixed-width bars for quick interpretation.
- Matplotlib dual-axis plotting: Overlay NEWS2 scores with vitals over time.
- Timestamp formatting: ISO timestamps converted and formatted for readability in plots.
- Failsafes and edge-case handling:
- Minimum/maximum values handled in ASCII plots.
- Alert thresholds with default 0 if no match found.
Key Components
- Data Persistence
- save_to_csv(): Writes flattened vitals with patient ID, timestamp, and NEWS2 score.
- load_from_csv(): Retrieves historical readings for a given patient.
- Patient Management
- get_or_create_patient_id(): Maps multiple patients to unique IDs. Patient ID anonymisation ensure GDPR-compliant data handling
- find_patient_id(): CLI interface to locate patient ID via full name + DOB.
- Input & Validation
- user_inputs(): Collects vitals in nested dictionary form.
- validate_input(): Ensures numeric values are within safe clinical ranges.
- Level of Consciousness handled as categorical input (Yes/No/Unsure).
- Alert & Scoring
- check_alert(): Determines tiered alert level per NEWS2.
- get_alert_message(): Converts alert level to user-friendly message.
- Scores summed for total NEWS2; systolic BP counts in total, diastolic printed only.
- Display
- print_patient_vitals(): Prints last 5 historical readings with alignment for readability.
- plot_ascii(): ASCII-based trend visualisation for lightweight terminal output.
- plot_matplotlib(): Professional Matplotlib plots with dual-axis for vitals + NEWS2.
- CLI Loop
- Options: Add Reading, View Past Readings, View Trends, Exit.
- Nested loops for multiple readings, plotting options, and patient queries.
Python | FastAPI | Pydantic | JSON Output | Render
- Deployed API on Render: https://vitals-tracker-cli.onrender.com/docs
- API endpoints:
GET /Root→ {"message":"Clinically-Informed Vitals Tracker API is running"}POST /add_vitals/→ Add new patient vitals; returns NEWS2 + alertsGET /patient/{patient_id}→ Retrieve patient historyGET /trends/{patient_id}/png→ Matplotlib plot (PNG)GET /trends/{patient_id}/json→ Historical vitals & NEWS2 as JSON
- CLI logic wrapped into deployable API
- JSON output includes:
patient_idtotal_news2_scorealerts(clinically structured)
- Modular separation of CLI vs API code for maintainability and integration.
- Enables future integration with your Early Warning Score (EWS) project for predictive clinical monitoring.
| Endpoint | Method | Description |
|---|---|---|
/Root |
GET | Sanity check confirming API is running |
/add_vitals/ |
POST | Input: patient_name, dob, vitals JSON; Output: patient_id, total NEWS2, alerts |
/patient/{patient_id} |
GET | Retrieve all saved vitals for a patient as JSON list |
/trends/{patient_id}/png |
GET | Matplotlib plot of vitals + NEWS2 trends |
/trends/{patient_id}/json |
GET | Vitals and NEWS2 history as JSON |
1. Add Vitals Response (POST /add_vitals/)
| Key | Description |
|---|---|
patient_id |
Unique identifier for the patient (generated by the system) |
total_news2_score |
Calculated NEWS2 score based on the submitted vitals |
alerts |
Clinically structured dictionary of alert messages per vital sign; includes: |
- value: numeric or string value of the vital |
|
- level: severity category (e.g., Normal, Mild Alert, Moderate Alert, Severe Alert) |
|
- score: NEWS2 points assigned for that vital |
|
- message: Clinical advice or escalation recommendation |
2. Fetch Past Readings (GET /patient/{patient_id})
Returns a list of historical readings:
| Key | Description |
|---|---|
patient_id |
Unique patient identifier |
timestamp |
ISO 8601 datetime of the reading |
news2_score |
NEWS2 score at that timestamp |
bp_systolic |
Systolic blood pressure |
bp_diastolic |
Diastolic blood pressure |
heart_rate |
Heart rate (bpm) |
respiratory_rate |
Respiratory rate (breaths per minute) |
temperature |
Body temperature (°C) |
oxygen_sats |
Oxygen saturation (%) |
loc |
Level of consciousness (Yes = fully awake, No = not fully awake) |
3. Trends Responses
PNG Plot (GET /trends/{patient_id}/png)
- Returns a binary image file of vital trends over time (Matplotlib plot).
JSON Trend Data (GET /trends/{patient_id}/json)
- Returns historical vitals in JSON format.
- Keys are identical to the Fetch Past Readings endpoint.

Figure 2: FastAPI Swagger UI home page showing available endpoints.

Figure 3: /Root endpoint returning a confirmation message.

Figure 4: /add_vitals endpoint example adding vitals and structured JSON response with NEWS2 score and alerts.

Figure 5: /patient endpoint example fetches past readings for a patient, showing historical data in JSON format.

Figure 6: /trends endpoint example returns a PNG plot of historical vital trends over time generated via Matplotlib.

Figure 7: /trends endpoint example returns historical vital trends over time as JSON.
@app.get("/")
def root():
return {"message": "Clinically-Informed Vitals Tracker API is running"}Automated endpoint tests are run using GitHub Actions to ensure the API deployment works correctly.
- Workflow file:
.github/workflows/fastapi-app.yml - Uses pytest and httpx to test endpoints locally and against the live deployment.
- Tests include:
GET /returns200and a JSON message confirming the API is running.POST /add_vitals/accepts a sample vitals payload, returnspatient_id,total_news2_score, andalerts.GET /patient/{patient_id}returns a list of historical readings.- Basic validation that responses have correct JSON structure and data types.
Example test snippet:
from fastapi.testclient import TestClient
from v2_api.app import app
client = TestClient(app)
def test_root_endpoint():
# Test that the root endpoint returns a 200 and contains a message
response = client.get("/")
assert response.status_code == 200
assert "message" in response.json()
def test_add_vitals_and_fetch():
# Sample vitals payload
vitals_payload = {
"Blood pressure": {"systolic": 120, "diastolic": 80},
"Heart rate": 75,
"Respiratory rate": 18,
"Temperature": 37.0,
"Oxygen saturations": 98,
"Level of consciousness (fully awake and responsive?)": "Yes"
}- Workflow triggers:
- Push to main
- Scheduled weekly checks
This demonstrates a production-ready backend with continuous validation.
cd v2_api
uvicorn app:app --reload- Visit http://127.0.0.1:8000/docs for interactive Swagger UI
- Use the UI to test endpoints such as:
- GET /Root → sanity check that the API is running
- POST /add_vitals/ → add new patient vitals and receive NEWS2 scores + alerts
- GET /patient/{patient_id} → fetch historical vitals for a patient
- GET /trends/{patient_id}/png → retrieve Matplotlib trend plot as PNG
- GET /trends/{patient_id}/json → fetch historical vitals and NEWS2 scores as JSON
- You can also test endpoints programmatically using httpx or your own scripts.
- Demonstrates production-ready backend for a clinician-technologist portfolio
- JSON outputs and CSV persistence make it easy to integrate into dashboards, hospital EHRs, or telemedicine apps
- Future improvements:
- Add automated notifications/alerts for moderate or severe NEWS2 scores.
- Integrate predictive AI/ML models for patient deterioration detection.
- Expand support for additional vital signs or custom scoring systems.
- Implement persistent logging and analytics for longitudinal patient data.
- No frontend required; API + CLI + deployment is enough for portfolio showcase
- AI/ML Integration: Predictive models for deterioration, anomaly detection.
- Web or GUI Front-End: Interactive dashboards for hospitals or telemedicine.
- EHR Integration: Directly pull/push patient vitals for automated trend analysis.
- Alerts & Notifications: Automated escalation for moderate/severe NEWS2 scores.
Clone and run:
git clone https://github.com/SimonYip22/NEWS2-Vitals-Tracker.git
cd NEWS2-Vitals-Tracker
python3 vitals_tracker.py- Follow CLI prompts to add readings, view past vitals, or plot trends.
- Select ASCII for lightweight monitoring or Matplotlib for portfolio-ready visuals.
Figure 8: Professional trend visualisation of a patient’s vitals over time, highlighting systolic BP, heart rate, respiratory rate, temperature, oxygen saturations, and NEWS2 scores. Enables clinicians to rapidly detect deterioration and make informed escalation decisions.
- The included
vitals-tracker-matplotlib.pngdemonstrates a sample patient's vitals trends over time. It includes:- Line plots for key vital signs: Systolic BP, Heart Rate, Respiratory Rate, Temperature, and Oxygen Saturations.
- Dual-axis plotting: Vitals on the primary y-axis and NEWS2 scores on the secondary y-axis (red dashed line).
- Markers at each data point for clarity.
- Formatted timestamps on the x-axis for readability.
- This PNG exemplifies the professional plotting capabilities of the CLI tool and can be used in portfolio showcases or presentations to illustrate the trend visualisation functionality.
Patient vitals Monitoring App
=============================
1. Add new reading
2. View past readings
3. View trends
4. Exit
Select an option (1/2/3/4): 1
=== Enter Patient Vitals ===
Systolic BP (mmHg): 120
Diastolic BP (mmHg): 80
Heart Rate (bpm): 75
Respiratory Rate (bpm): 16
Temperature (°C): 37
Oxygen Saturation (%): 98
Fully awake and responsive? (Yes/No/Unsure): Yes
Vitals recorded successfully!
Total NEWS2 score: 0
Patient vitals Monitoring App
=============================
1. Add new reading
2. View past readings
3. View trends
4. Exit
Select an option (1/2/3/4): 2
Enter full name: simon yip
Please enter date of birth (dd/mm/yy): 26/11/00
Patient found. ID: 1. Retrieving vitals...
Vitals for patient 1:
Patient has 4 recorded vital sign entries. Showing the last 4:
2025-08-28T00:11:30.266959 | BP: 120/ 80 mmHg | HR: 75 bpm | RR: 16 bpm | Temp: 37 °C | O2 sats: 98 % | Awake and fully responsive?: Yes |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2025-08-28T00:11:55.336599 | BP: 110/ 75 mmHg | HR: 95 bpm | RR: 22 bpm | Temp: 38.5 °C | O2 sats: 95 % | Awake and fully responsive?: Yes |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2025-08-28T00:12:25.660340 | BP: 100/ 70 mmHg | HR: 115 bpm | RR: 25 bpm | Temp: 39.2 °C | O2 sats: 93 % | Awake and fully responsive?: Yes |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2025-08-28T00:12:55.637678 | BP: 85/ 55 mmHg | HR: 135 bpm | RR: 28 bpm | Temp: 34.5 °C | O2 sats: 89 % | Awake and fully responsive?: No/Unsure |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
View another past reading? (y/n): n
Patient vitals Monitoring App
=============================
1. Add new reading
2. View past readings
3. View trends
4. Exit
Select an option (1/2/3/4): 3
Enter full name: simon yip
Please enter date of birth (dd/mm/yy): 26/11/00
Patient found. ID: 1. Retrieving vitals...
View trends as (1) ASCII or (2) matplotlib? 1
news2_score trends:
2025-08-28T00:11:30.266959 | 0.0 |
------------------------------------------------------------
2025-08-28T00:11:55.336599 | 4.0 | ###########
------------------------------------------------------------
2025-08-28T00:12:25.660340 | 11.0 | ##############################
------------------------------------------------------------
2025-08-28T00:12:55.637678 | 18.0 | ##################################################
------------------------------------------------------------
bp_systolic trends:
2025-08-28T00:11:30.266959 | 120.0 | ##################################################
------------------------------------------------------------
2025-08-28T00:11:55.336599 | 110.0 | ###################################
------------------------------------------------------------
2025-08-28T00:12:25.660340 | 100.0 | #####################
------------------------------------------------------------
2025-08-28T00:12:55.637678 | 85.0 |
------------------------------------------------------------
bp_diastolic trends:
2025-08-28T00:11:30.266959 | 80.0 | ##################################################
------------------------------------------------------------
2025-08-28T00:11:55.336599 | 75.0 | ########################################
------------------------------------------------------------
2025-08-28T00:12:25.660340 | 70.0 | ##############################
------------------------------------------------------------
2025-08-28T00:12:55.637678 | 55.0 |
------------------------------------------------------------
heart_rate trends:
2025-08-28T00:11:30.266959 | 75.0 |
------------------------------------------------------------
2025-08-28T00:11:55.336599 | 95.0 | ################
------------------------------------------------------------
2025-08-28T00:12:25.660340 | 115.0 | #################################
------------------------------------------------------------
2025-08-28T00:12:55.637678 | 135.0 | ##################################################
------------------------------------------------------------
respiratory_rate trends:
2025-08-28T00:11:30.266959 | 16.0 |
------------------------------------------------------------
2025-08-28T00:11:55.336599 | 22.0 | #########################
------------------------------------------------------------
2025-08-28T00:12:25.660340 | 25.0 | #####################################
------------------------------------------------------------
2025-08-28T00:12:55.637678 | 28.0 | ##################################################
------------------------------------------------------------
temperature trends:
2025-08-28T00:11:30.266959 | 37.0 | ##########################
------------------------------------------------------------
2025-08-28T00:11:55.336599 | 38.5 | ##########################################
------------------------------------------------------------
2025-08-28T00:12:25.660340 | 39.2 | ##################################################
------------------------------------------------------------
2025-08-28T00:12:55.637678 | 34.5 |
------------------------------------------------------------
oxygen_sats trends:
2025-08-28T00:11:30.266959 | 98.0 | ##################################################
------------------------------------------------------------
2025-08-28T00:11:55.336599 | 95.0 | #################################
------------------------------------------------------------
2025-08-28T00:12:25.660340 | 93.0 | ######################
------------------------------------------------------------
2025-08-28T00:12:55.637678 | 89.0 |
------------------------------------------------------------
- Full sample_run.md included demonstrating normal → severe alerts, past readings, ASCII trends, and Matplotlib plots.
Test the CLI workflows, NEWS2 scoring, CSV persistence, and plotting functions:
pytest -vTests include:
- Validation of vital sign input ranges (validate_input)
- Correct NEWS2 alert level assignment (check_alert, get_alert_message)
- Flattening of nested vitals for CSV storage (flatten_vitals)
- Patient management and CSV persistence (get_or_create_patient_id, save_to_csv, load_from_csv)
- Display functions and CLI output (print_patient_vitals)
- Trend visualisation functions (plot_ascii, plot_matplotlib)
NEWS2-Vitals-Tracker/
├── .github/
│ └── workflows/
│ ├── fastapi-app.yml
│ └── python-app.yml
├── test_vitals_tracker_CLI/
│ ├── test_patient_mapping.csv
│ ├── test_vitals_tracker.py
│ └── test_vitals.csv
├── v2_api/
│ ├── app.py
│ ├── swagger_get_patient.png
│ ├── swagger_get_root.png
│ ├── swagger_get_trends_json.png
│ ├── swagger_get_trends_png.png
│ ├── swagger_home.png
│ ├── swagger_post_add_vitals.png
│ ├── test_api_endpoint.py
│ └── vitals_tracker_v2.py
├── notes.md
├── patient_mapping.csv
├── README.md
├── reflection.md
├── requirements.txt
├── sample_run.md
├── vitals_tracker.py
├── vitals-tracker-flowchart.png
├── vitals-tracker-matplotlib.png
├── vitals.csv
Explanations:
- github/workflows/
- python-app.yml — Runs unit tests and CLI validation
- fastapi-app.yml — Tests FastAPI endpoints and API responses
- test_vitals_tracker_CLI/ — Unit tests and mock CSV files for testing input validation, scoring, and plotting
- v2_api/
- app.py — API routes wrapping CLI logic
- swagger_*.png — Screenshots of Swagger UI endpoints
- test_api_endpoint.py — Tests for API endpoints
- vitals_tracker_v2.py — Core functions for API usage
- notes.md — Daily development logs
- patient_mapping.csv — Maps patient names + DOB to IDs.
- README.md — Project documentation
- reflection.md - Final project reflection
- requirements.txt — Dependencies for the API and CLI
- sample_run.md — Demonstrates all scenarios.
- vitals_tracker.py — Main CLI program.
- vitals-tracker-flowchart.png - Flowchart explaining logic
- vitals-tracker-matplotlib.png — Example Matplotlib output.
- vitals.csv — Historical patient readings for persistence.
- Educational and portfolio purposes only.
- Not a substitute for professional medical advice.
- For emergency concerns, contact NHS 111 or 999 immediately.