diff --git a/HRM_Processor.py b/HRM_Processor.py
index f80e6a6..a4db441 100644
--- a/HRM_Processor.py
+++ b/HRM_Processor.py
@@ -1,4 +1,5 @@
 import numpy as np
+import logging
 
 
 class HRM_Processor:
@@ -15,6 +16,10 @@ class HRM_Processor:
     output_dict: dict
                 A dictionary containing the heart rate metrics that will be
                 written to the JSON file by the DataWriter.
+    isValid:    boolean
+                True if the number of beats is realistic for the ECG
+                strip duration. Used by the DataWriter to determine if
+                the data should be written to the JSON file.
 
     """
     def __init__(self, DataReader):
@@ -27,10 +32,26 @@ class HRM_Processor:
                     file and created and output_dict with the relevant data
                     for the HRM_Processor
         """
+
+        logging.basicConfig(filename="HRM_Processor_logs.txt",
+                            format='%(asctime)s %(levelname)s:%(message)s',
+                            datefmt='%m/%d/%Y %I:%M:%S %p')
+
         self.csv_file = DataReader.csv_file_path
         self.input_data = DataReader.output_dict
         self.output_dict = {}
         self.write_outputs_to_dict()
+        try:
+            self.validate_outputs(self.output_dict)
+        except ValueError:
+            print("The heart rate monitor has calculated a heart rate that "
+                  "is not physiologically realistic. This is likely due to "
+                  "a high level of signal noise. These misleading / "
+                  "inaccurate outputs will not be written to a JSON file.")
+            logging.error("Invalid BPM calculated from ECG strip. isValid "
+                          "flag set to false preventing this misleading data "
+                          "from being written to a JSON file.")
+            self.isValid = False
 
     def write_outputs_to_dict(self):
         """Writes all of the HRM_Processor's outputs to it's output
@@ -62,6 +83,31 @@ class HRM_Processor:
             self.output_dict["mean_hr_bpm"] = mean_hr_bpm
         except ValueError:
             print("Invalid duration (no beats in that duration)")
+            logging.error("ValueError: Invalid number of beats in specified "
+                          "duration for BPM calculations.")
+
+    def validate_outputs(self, output_dict):
+        """Validates the outputs in the output_dict to determine if the
+        program is generating reasonable metrics for this signal.
+
+        Parameters
+        ----------
+        output_dict:    dict
+                        Output dictionary containing the metrics for the
+                        heart rate monitor.
+
+        Returns
+        -------
+        None
+
+        """
+        self.check_voltage_extremes(output_dict["voltage_extremes"])
+        is_realistic = self.is_unrealistic_num_beats(output_dict["num_beats"],
+                                                     output_dict["duration"])
+
+        if not is_realistic:
+            raise ValueError
+        self.isValid = True
 
     def determine_voltage_extremes(self, voltage):
         """Determines the min and max values of the voltage data
@@ -85,6 +131,27 @@ class HRM_Processor:
 
         return voltage_extremes
 
+    def check_voltage_extremes(self, voltage_extremes):
+        """Prints a warning message if the voltage_extremes exceed 300 mV,
+        which is outside the range of a normal ECG signal.
+
+        Parameters
+        ----------
+        voltage_extremes:   tuple(float, float)
+                            A tuple containing the maximum and minimum
+                            voltage values in the format (min, max)
+
+        Returns
+        -------
+        None
+
+        """
+        if abs(voltage_extremes[0]) > 300 or abs(voltage_extremes[1] > 300):
+            print("Warning: voltage values exceed expected levels for a "
+                  "typical ECG signal. This data may not be valid.")
+            logging.warning("The voltage values exceed expected values for a"
+                            "typical ECG signal. This data may not be valid.")
+
     def determine_ecg_strip_duration(self, time):
         """Determines the length of the ECG strip
 
@@ -288,6 +355,37 @@ class HRM_Processor:
         num_beats = np.size(beat_start_times)
         return num_beats
 
+    def is_unrealistic_num_beats(self, num_beats, duration):
+        """Checks that the calculated number of beats is physiologically
+        realistic for the duration of the ECG strip.
+
+        Physiologically realistic for the sake of this function is defined
+        as a heart rate that is greater than 150 BPM or one that is less tha 40
+        BPM. While it may be possible in some extreme cases for heart rates
+        to exceed these extremes, it is considered more likely that the beat
+        detection algorithm is falsely detecting beats or missing beats.
+
+        Parameters
+        ----------
+        num_beats:  int
+                    The number of beats in the ECG strip
+        duration:   float
+                    The duration of the ECG strip
+
+        Returns
+        -------
+        realistic_num_beats:    boolean
+                                Specifies whether the number of beats is
+                                physiologically realistic.
+        """
+        realistic_max_beats = 2.5*duration  # corresponds to 150 BPM
+        realistic_min_beats = 0.6*duration  # corresponds to 36 BPM
+
+        if num_beats > realistic_max_beats or num_beats < realistic_min_beats:
+            return False
+        else:
+            return True
+
     def determine_bpm(self, beat_start_times, duration):
         """Determines the mean beats per minute (BPM) within a specified
         duration of time.

The HRM_Processor should display a warning when the voltage signal is beyond typical ECG values
diff --git a/test_HRM_Processor.py b/test_HRM_Processor.py
index 1eae4ee..8f67a65 100644
--- a/test_HRM_Processor.py
+++ b/test_HRM_Processor.py
@@ -275,3 +275,120 @@ def test_determine_bpm2(hrm):
     calculated_bpm = hrm.determine_bpm(start_times, duration)
 
     assert expected_bpm == calculated_bpm
+
+
+@pytest.mark.parametrize("voltage_extremes", [
+    (0, 500),
+    (-500, 0),
+    (-301, 5),
+    (1000, 10000),
+])
+def test_check_voltage_extremes_warnings(hrm, capsys, voltage_extremes):
+    """Tests that the check_voltage_extremes function prints a warning when
+    the absolute value of a voltage extreme is greater than 300.
+
+    Parameters
+    ----------
+    hrm:    HRM_Processor
+            A generic HRM_Processor made from a DataReader with test_file.csv
+    capsys: Pytest fixture
+            A pytest fixture for getting outputs printed to the console
+    voltage_extremes:   tuple(float, float)
+                        Voltage extremes in the format (min, max)
+
+    Returns
+    -------
+    None
+    """
+    hrm.check_voltage_extremes(voltage_extremes)
+    captured_output, err = capsys.readouterr()
+
+    assert captured_output == "Warning: voltage values exceed expected " \
+                              "levels for a typical ECG signal. This " \
+                              "data may not be valid.\n"
+
+
+@pytest.mark.parametrize("voltage_extremes", [
+    (0, 300),
+    (-300, 0),
+    (-150, 5),
+    (10, 100),
+])
+def test_check_voltage_extremes_no_warnings(hrm, capsys, voltage_extremes):
+    """Tests that the check_voltage_extremes function does prints a warning
+    when the absolute value of both voltage extremes is less than or equal to
+    300.
+
+    Parameters
+    ----------
+    hrm:    HRM_Processor
+            A generic HRM_Processor made from a DataReader with test_file.csv
+    capsys: Pytest fixture
+            A pytest fixture for getting outputs printed to the console
+    voltage_extremes:   tuple(float, float)
+                        Voltage extremes in the format (min, max)
+
+    Returns
+    -------
+    None
+    """
+    hrm.check_voltage_extremes(voltage_extremes)
+    captured_output, err = capsys.readouterr()
+
+    assert captured_output == ''
+
+
+@pytest.mark.parametrize("num_beats, duration, is_realistic", [
+    (50, 60.0, True),
+    (60, 60.0, True),
+    (151, 60.0, False),
+    (35, 60.0, False),
+])
+def test_is_realistic_num_beats(hrm, num_beats, duration, is_realistic):
+    """Tests that the is_realistic_num_beats correctly determines is the
+    number of beats is physiologically realistic for a given strip duration.
+
+    THe duration of the strip is measured in seconds. An unrealistic number
+    of beats is defined as less than 36 BPM or greater than 150 BPM.
+
+    Parameters
+    ----------
+    hrm:        HRM_Processor
+                A generic HRM_Processor made from a DataReader with
+                test_file.csv
+    num_beats:  int
+                The number of beats in a ECG sequence
+    duration:   float
+                The duration of the ECG sequence in seconds
+    is_realistic:   boolean
+                    True if the number of beats is physiologically realistic.
+
+    Returns
+    -------
+    None
+    """
+    measured_is_realistic = hrm.is_unrealistic_num_beats(num_beats, duration)
+
+    assert measured_is_realistic == is_realistic
+
+
+def test_validate_outputs_invalid_case(hrm):
+    """Checks that the validate_outputs function raises a ValueError when
+    the number of beats is not physiologically realistic for a ECG strip
+    duration.
+
+    Parameters
+    ----------
+    hrm:    HRM_Processor
+            A generic HRM_Processor object made from a DataReader with
+            test_file.csv
+
+    Returns
+    -------
+    None
+    """
+    output_dict = {"num_beats": 10000, "duration": 60, "voltage_extremes": (
+        0, 200)}
+
+    with pytest.raises(ValueError):
+        hrm.validate_outputs(output_dict)

pip install -r requirements.txt
parse_log_pytest
requirements.txt
pytest
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
coverage==7.8.0
exceptiongroup==1.2.2
execnet==2.1.1
iniconfig==2.1.0
numpy==2.0.2
packaging==24.2
pep8==1.7.1
pluggy==1.5.0
pytest==8.3.5
pytest-cache==1.0
pytest-cov==6.0.0
pytest-pep8==1.0.6
scipy==1.13.1
tomli==2.2.1

name: bme590hrm
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - iniconfig==2.1.0
      - numpy==2.0.2
      - packaging==24.2
      - pep8==1.7.1
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-cache==1.0
      - pytest-cov==6.0.0
      - pytest-pep8==1.0.6
      - scipy==1.13.1
      - tomli==2.2.1
prefix: /opt/conda/envs/bme590hrm

test_HRM_Processor.py::test_check_voltage_extremes_warnings[voltage_extremes0] test_HRM_Processor.py::test_check_voltage_extremes_warnings[voltage_extremes1] test_HRM_Processor.py::test_check_voltage_extremes_warnings[voltage_extremes2] test_HRM_Processor.py::test_check_voltage_extremes_warnings[voltage_extremes3] test_HRM_Processor.py::test_check_voltage_extremes_no_warnings[voltage_extremes0] test_HRM_Processor.py::test_check_voltage_extremes_no_warnings[voltage_extremes1] test_HRM_Processor.py::test_check_voltage_extremes_no_warnings[voltage_extremes2] test_HRM_Processor.py::test_check_voltage_extremes_no_warnings[voltage_extremes3] test_HRM_Processor.py::test_is_realistic_num_beats[50-60.0-True] test_HRM_Processor.py::test_is_realistic_num_beats[60-60.0-True] test_HRM_Processor.py::test_is_realistic_num_beats[151-60.0-False] test_HRM_Processor.py::test_is_realistic_num_beats[35-60.0-False] test_HRM_Processor.py::test_validate_outputs_invalid_case
test_HRM_Processor.py::test_find_qrs_peak_indices
test_HRM_Processor.py::test_HRM_Processor_init test_HRM_Processor.py::test_voltage_extremes test_HRM_Processor.py::test_write_outputs_to_dict_voltage_extremes test_HRM_Processor.py::test_determine_ecg_strip_duration test_HRM_Processor.py::test_write_strip_duration test_HRM_Processor.py::test_determine_threshold test_HRM_Processor.py::test_find_indices_above_threshold test_HRM_Processor.py::test_find_beat_separation_points test_HRM_Processor.py::test_index_beat_start_times test_HRM_Processor.py::test_determine_num_beats test_HRM_Processor.py::test_determine_bpm test_HRM_Processor.py::test_determine_bpm2
HRM_Processor.py
HRM_Processor.py