diff --git a/study_lyte/calibrations.py b/study_lyte/calibrations.py index 5aae964..94175a0 100644 --- a/study_lyte/calibrations.py +++ b/study_lyte/calibrations.py @@ -5,16 +5,27 @@ import logging from dataclasses import dataclass from typing import List +from datetime import datetime +import pandas as pd + setup_log() LOG = logging.getLogger('study_lyte.calibrations') +class MissingMeasurementDateException(Exception): + """ + Exception to raise when a probe has multiple calibrations but the date has + not been specified. + """ + pass + @dataclass() class Calibration: """Small class to make accessing calibration data a bit more convenient""" serial: str calibration: dict[str, List[float]] + date: datetime = None class Calibrations: @@ -26,16 +37,41 @@ def __init__(self, filename:Path): with open(filename, mode='r') as fp: self._info = json.load(fp) - def from_serial(self, serial:str) -> Calibration: + def from_serial(self, serial:str, date: datetime=None) -> Calibration: """ Build data object from the calibration result """ - cal = self._info.get(serial) - if cal is None: - LOG.warning(f"No Calibration found for serial {serial}, using default") + calibrations = self._info.get(serial) + cal = None + + if calibrations is None: cal = self._info['default'] serial = 'UNKNOWN' else: + # Single calibration, returned as a dict + if isinstance(calibrations, dict): + cal = calibrations + + # Account for multiple calibrations + elif isinstance(calibrations, list): + # Check the date is provided + if date is None and len(calibrations) > 1: + raise MissingMeasurementDateException("Multiple calibrations found, but no date provided") + else: + # Find the calibration that matches the date + for c in calibrations: + if date >= pd.to_datetime(c['date']): + cal = c + + # No matches were found, date is too early + if cal is None: + LOG.warning(f"All available calibrations for {serial} are not available before {date}, using default") + cal = self._info['default'] + serial = 'UNKNOWN' + + if cal is not None and serial != 'UNKNOWN': LOG.info(f"Calibration found ({serial})!") + else: + LOG.warning(f"No calibration found for {serial}, using default") result = Calibration(serial=serial, calibration=cal) return result diff --git a/study_lyte/profile.py b/study_lyte/profile.py index 46faaed..2ecf495 100644 --- a/study_lyte/profile.py +++ b/study_lyte/profile.py @@ -96,7 +96,7 @@ def set_calibration(self, ext_calibrations:Calibrations): Args: ext_calibrations: External collection of calibrations """ - cal = ext_calibrations.from_serial(self.serial_number) + cal = ext_calibrations.from_serial(self.serial_number, date=self.datetime) self._calibration = cal.calibration @property diff --git a/tests/data/angled_measurement.csv b/tests/data/angled_measurement.csv index b430fb4..849cd2d 100644 --- a/tests/data/angled_measurement.csv +++ b/tests/data/angled_measurement.csv @@ -6,7 +6,7 @@ MODEL NUMBER = 3 SAMPLE RATE = 16000 ZPFO = 50 ACC. Range = 16 -Serial Num. = 252813070A020004 +Serial Num. = 252813070A020005 time,Sensor1,Sensor2,Sensor3,Sensor4,depth,X-Axis,Y-Axis,Z-Axis 0.0,3451,6,1216,3665,-0.5615234375,-0.49348,-0.7920499999999999,-0.12045 6.250247868332343e-05,3438,8,1212,3666,-0.5806001142951156,-0.49361417545537245,-0.7920525373676652,-0.1206102717929017 diff --git a/tests/data/calibrations.json b/tests/data/calibrations.json index ee29cfa..1b0609c 100644 --- a/tests/data/calibrations.json +++ b/tests/data/calibrations.json @@ -1,6 +1,17 @@ { - "252813070A020004":{"Sensor1":[0, 0, -10, 409], - "comment": "Test"}, + "252813070A020004":{ + "Sensor1":[0, 0, -10, 409], + "comment": "Test"}, + + "252813070A020005":[ + {"date": "2024-01-01", + "Sensor1":[0, 0, -10, 200], + "comment": "Date Test"}, + + {"date": "2025-05-01", + "Sensor1":[0, 0, -10, 600], + "comment": "Date Test2"} + ], "default":{"Sensor1":[0, 0, -1, 4096], "comment": "default"} diff --git a/tests/test_calibration.py b/tests/test_calibration.py index baa6389..fd07d5e 100644 --- a/tests/test_calibration.py +++ b/tests/test_calibration.py @@ -1,7 +1,8 @@ import pytest from os.path import join from pathlib import Path -from study_lyte.calibrations import Calibrations +from study_lyte.calibrations import Calibrations, MissingMeasurementDateException +import pandas as pd class TestCalibrations: @@ -14,7 +15,10 @@ def calibrations(self, calibration_json): return Calibrations(calibration_json) @pytest.mark.parametrize("serial, expected", [ + # Test actual calibration ("252813070A020004", -10), + + # Test no calibration found ("NONSENSE", -1), ]) def test_attributes(self, calibrations, serial, expected): @@ -22,4 +26,21 @@ def test_attributes(self, calibrations, serial, expected): result = calibrations.from_serial(serial) assert result.calibration['Sensor1'][2] == expected + @pytest.mark.parametrize("serial, date, expected", [ + # Test valid multi calibration between date 1 and date 2 + ("252813070A020005", "2024-02-01", 200), + # Test valid multi calibration with exact match on date 2 + ("252813070A020005", "2025-05-01", 600), + # Test single calibration with a date provided. + ("252813070A020004", "2025-01-01", 409), + ]) + def test_date_based(self, calibrations, serial, date, expected): + """""" + dt = pd.to_datetime(date) + result = calibrations.from_serial(serial, date=dt) + assert result.calibration['Sensor1'][3] == expected + def test_missing_measurement_date_exception(self, calibrations): + """ Confirm this raises an exception when no date is provided """ + with pytest.raises(MissingMeasurementDateException): + calibrations.from_serial("252813070A020005", date=None) diff --git a/tests/test_profile.py b/tests/test_profile.py index 172c361..ee91afa 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -233,6 +233,9 @@ def test_serial_number(self, profile,filename, depth_method, expected): ("open_air.csv", 'fused', -10), # Test serial number not found ("mores_20230119.csv", 'fused', -1), + # Tests the date based calibration serial in the profile + ("angled_measurement.csv", 'fused', -10), + ]) def test_set_calibration(self, data_dir, profile,filename, depth_method, expected): p = Path(join(data_dir,'calibrations.json'))