From 12bc05eda0695bd47d2ac9e1ef89a45a3bd1ef5d Mon Sep 17 00:00:00 2001 From: Spicer Bak Date: Thu, 16 Jul 2026 12:03:52 -0400 Subject: [PATCH 1/6] Convert gauge URL lookups from if-elif chains to O(1) dictionary lookups Implements Issue #50 Task 2.2 optimization. Replaces ~150 lines of if-elif statements in _waveGaugeURLlookup() and _wlGageURLlookup() with module-level dictionaries for O(1) lookup performance. Adds 10 new tests for dictionary lookup behavior, edge cases, and all gauge aliases. Co-Authored-By: Claude Opus 4.5 --- murgtools/getdata/getDataFRF.py | 359 +++++++++++++++++--------------- tests/test_getDataFRF.py | 131 ++++++++++++ 2 files changed, 325 insertions(+), 165 deletions(-) diff --git a/murgtools/getdata/getDataFRF.py b/murgtools/getdata/getDataFRF.py index 7cd15d3..3e34767 100755 --- a/murgtools/getdata/getDataFRF.py +++ b/murgtools/getdata/getDataFRF.py @@ -123,6 +123,177 @@ def open_dataset_with_retry(url, max_attempts=None, retry_delay=5): return None +# ============================================================================= +# Gauge URL Lookup Tables (O(1) dictionary lookup instead of O(n) if-elif chain) +# ============================================================================= + +# Wave gauge URL lookup table - all keys should be lowercase for case-insensitive matching +_WAVE_GAUGE_URLS = { + # 26m waverider + '0': 'oceanography/waves/waverider-26m/waverider-26m.ncml', + 'waverider-26m': 'oceanography/waves/waverider-26m/waverider-26m.ncml', + '26m': 'oceanography/waves/waverider-26m/waverider-26m.ncml', + # 17m waverider + '1': 'oceanography/waves/waverider-17m/waverider-17m.ncml', + 'waverider-17m': 'oceanography/waves/waverider-17m/waverider-17m.ncml', + '17m': 'oceanography/waves/waverider-17m/waverider-17m.ncml', + # 11m AWAC + '2': 'oceanography/waves/awac-11m/awac-11m.ncml', + 'awac-11m': 'oceanography/waves/awac-11m/awac-11m.ncml', + '11m': 'oceanography/waves/awac-11m/awac-11m.ncml', + # 8m AWAC + '3': 'oceanography/waves/awac-8m/awac-8m.ncml', + 'awac-8m': 'oceanography/waves/awac-8m/awac-8m.ncml', + # 6m AWAC + '4': 'oceanography/waves/awac-6m/awac-6m.ncml', + 'awac-6m': 'oceanography/waves/awac-6m/awac-6m.ncml', + 'awac 6m': 'oceanography/waves/awac-6m/awac-6m.ncml', + # 4.5m AWAC + '5': 'oceanography/waves/awac-4.5m/awac-4.5m.ncml', + 'awac-4.5m': 'oceanography/waves/awac-4.5m/awac-4.5m.ncml', + 'awac_4.5m': 'oceanography/waves/awac-4.5m/awac-4.5m.ncml', + # 3.5m Aquadopp + '6': 'oceanography/waves/adop-3.5m/adop-3.5m.ncml', + 'adop-3.5m': 'oceanography/waves/adop-3.5m/adop-3.5m.ncml', + 'aquadopp 3.5m': 'oceanography/waves/adop-3.5m/adop-3.5m.ncml', + # 2m Aquadopp + '7': 'oceanography/waves/adop01/adop01.ncml', + 'adop-2m': 'oceanography/waves/adop01/adop01.ncml', + # xp340m pressure + 'xp340m': 'oceanography/waves/xp340m/xp340m.ncml', + 'xp340': 'oceanography/waves/xp340m/xp340m.ncml', + # xp250m pressure (note: '8' maps here, not xp200m - preserved from original) + 'xp250m': 'oceanography/waves/xp250m/xp250m.ncml', + 'xp250': 'oceanography/waves/xp250m/xp250m.ncml', + # xp200m pressure + '8': 'oceanography/waves/xp200m/xp200m.ncml', + 'xp200m': 'oceanography/waves/xp200m/xp200m.ncml', + 'xp200': 'oceanography/waves/xp200m/xp200m.ncml', + # xp150m pressure + '9': 'oceanography/waves/xp150m/xp150m.ncml', + 'xp150m': 'oceanography/waves/xp150m/xp150m.ncml', + 'xp150': 'oceanography/waves/xp150m/xp150m.ncml', + # xp125m pressure + '10': 'oceanography/waves/xp125m/xp125m.ncml', + 'xp125m': 'oceanography/waves/xp125m/xp125m.ncml', + 'xp125': 'oceanography/waves/xp125m/xp125m.ncml', + # xp100m pressure + '11': 'oceanography/waves/xp100m/xp100m.ncml', + 'xp100m': 'oceanography/waves/xp100m/xp100m.ncml', + # 8m array + '12': 'oceanography/waves/8m-array/8m-array.ncml', + '8m array': 'oceanography/waves/8m-array/8m-array.ncml', + '8m-array': 'oceanography/waves/8m-array/8m-array.ncml', + # Signature sensors + 'sig940-300': 'oceanography/waves/sig940-300/sig940-300.ncml', + '940-300': 'oceanography/waves/sig940-300/sig940-300.ncml', + 'sig769-300': 'oceanography/waves/sig769-300/sig769-300.ncml', + '769-300': 'oceanography/waves/sig769-300/sig769-300.ncml', + 'sig940-400': 'oceanography/waves/sig940-400/sig940-400.ncml', + '940-400': 'oceanography/waves/sig940-400/sig940-400.ncml', + 'sig940-600': 'oceanography/waves/sig940-600/sig940-600.ncml', + '940-600': 'oceanography/waves/sig940-600/sig940-600.ncml', + # AWAC at jetty pier + 'awac-jpier-11m': 'oceanography/waves/awac-jpier-11m/awac-jpier-11m.ncml', + 'awac-jpier': 'oceanography/waves/awac-jpier-11m/awac-jpier-11m.ncml', + 'jpier-11m': 'oceanography/waves/awac-jpier-11m/awac-jpier-11m.ncml', + # New waveriders + 'waverider-17m-1d': 'oceanography/waves/waverider-17m-1d/waverider-17m-1d.ncml', + '17m-1d': 'oceanography/waves/waverider-17m-1d/waverider-17m-1d.ncml', + 'waverider-20m': 'oceanography/waves/waverider-20m-1d/waverider-20m-1d.ncml', + 'waverider-20m-1d': 'oceanography/waves/waverider-20m-1d/waverider-20m-1d.ncml', + '20m': 'oceanography/waves/waverider-20m-1d/waverider-20m-1d.ncml', + '20m-1d': 'oceanography/waves/waverider-20m-1d/waverider-20m-1d.ncml', + # Paros pressure sensors + 'paros-200-940m': 'oceanography/waves/paros940-200/paros940-200.ncml', + 'paros940-200': 'oceanography/waves/paros940-200/paros940-200.ncml', + 'paros-250-940m': 'oceanography/waves/paros940-250/paros940-250.ncml', + 'paros940-250': 'oceanography/waves/paros940-250/paros940-250.ncml', + 'paros-340x-940y-top': 'oceanography/waves/paros-340x-940y-top/paros-340x-940y-top.ncml', + # Lidar wave gauges - 140m + 'lidarwavegauge140': 'oceanography/waves/lidarWaveGauge140/lidarWaveGauge140.ncml', + 'lidargauge140': 'oceanography/waves/lidarWaveGauge140/lidarWaveGauge140.ncml', + 'lidarwavegauge140m': 'oceanography/waves/lidarWaveGauge140/lidarWaveGauge140.ncml', + 'lidargauge140m': 'oceanography/waves/lidarWaveGauge140/lidarWaveGauge140.ncml', + # Lidar wave gauges - 110m + 'lidarwavegauge110': 'oceanography/waves/lidarWaveGauge110/lidarWaveGauge110.ncml', + 'lidargauge110': 'oceanography/waves/lidarWaveGauge110/lidarWaveGauge110.ncml', + 'lidarwavegauge110m': 'oceanography/waves/lidarWaveGauge110/lidarWaveGauge110.ncml', + 'lidargauge110m': 'oceanography/waves/lidarWaveGauge110/lidarWaveGauge110.ncml', + # Lidar wave gauges - 100m + 'lidarwavegauge100': 'oceanography/waves/lidarWaveGauge100/lidarWaveGauge100.ncml', + 'lidargauge100': 'oceanography/waves/lidarWaveGauge100/lidarWaveGauge100.ncml', + 'lidarwavegauge100m': 'oceanography/waves/lidarWaveGauge100/lidarWaveGauge100.ncml', + 'lidargauge100m': 'oceanography/waves/lidarWaveGauge100/lidarWaveGauge100.ncml', + # Lidar wave gauges - 90m + 'lidarwavegauge90': 'oceanography/waves/lidarWaveGauge90/lidarWaveGauge90.ncml', + 'lidargauge90': 'oceanography/waves/lidarWaveGauge90/lidarWaveGauge90.ncml', + 'lidarwavegauge90m': 'oceanography/waves/lidarWaveGauge90/lidarWaveGauge90.ncml', + 'lidargauge90m': 'oceanography/waves/lidarWaveGauge90/lidarWaveGauge90.ncml', + 'lidarwavegauge090': 'oceanography/waves/lidarWaveGauge090/lidarWaveGauge090.ncml', + # Lidar wave gauges - 80m + 'lidarwavegauge80': 'oceanography/waves/lidarWaveGauge80/lidarWaveGauge80.ncml', + 'lidargauge80': 'oceanography/waves/lidarWaveGauge80/lidarWaveGauge80.ncml', + 'lidarwavegauge80m': 'oceanography/waves/lidarWaveGauge80/lidarWaveGauge80.ncml', + 'lidargauge80m': 'oceanography/waves/lidarWaveGauge80/lidarWaveGauge80.ncml', + 'lidarwavegauge080': 'oceanography/waves/lidarWaveGauge080/lidarWaveGauge080.ncml', + # Oregon inlet + 'oregoninlet': 'oceanography/waves/waverider-oregon-inlet-nc/waverider-oregon-inlet-nc.ncml', + 'oi': 'oceanography/waves/waverider-oregon-inlet-nc/waverider-oregon-inlet-nc.ncml', +} + +# Water level gauge lookup table - maps to (gname, dataloc) tuples +# Keys include both integer and string variants for compatibility +_WL_GAUGE_CONFIG = { + # 11m AWAC + 2: ('AWAC 11m', 'oceanography/waves/awac-11m/awac-11m.ncml'), + 'AWAC-11m': ('AWAC 11m', 'oceanography/waves/awac-11m/awac-11m.ncml'), + 'awac-11m': ('AWAC 11m', 'oceanography/waves/awac-11m/awac-11m.ncml'), + 'Awac-11m': ('AWAC 11m', 'oceanography/waves/awac-11m/awac-11m.ncml'), + # 8m AWAC + 3: ('AWAC 8m', 'oceanography/waves/awac-8m/awac-8m.ncml'), + 'awac-8m': ('AWAC 8m', 'oceanography/waves/awac-8m/awac-8m.ncml'), + 'AWAC-8m': ('AWAC 8m', 'oceanography/waves/awac-8m/awac-8m.ncml'), + # 6m AWAC + 4: ('AWAC 6m', 'oceanography/waves/awac-6m/awac-6m.ncml'), + 'awac-6m': ('AWAC 6m', 'oceanography/waves/awac-6m/awac-6m.ncml'), + 'AWAC-6m': ('AWAC 6m', 'oceanography/waves/awac-6m/awac-6m.ncml'), + # 4.5m AWAC + 5: ('AWAC 4.5m', 'oceanography/waves/awac-4.5m/awac-4.5m.ncml'), + 'awac-4.5m': ('AWAC 4.5m', 'oceanography/waves/awac-4.5m/awac-4.5m.ncml'), + 'Awac-4.5m': ('AWAC 4.5m', 'oceanography/waves/awac-4.5m/awac-4.5m.ncml'), + 'awac_4.5m': ('AWAC 4.5m', 'oceanography/waves/awac-4.5m/awac-4.5m.ncml'), + # 3.5m Aquadopp + 6: ('Aquadopp 3.5m', 'oceanography/waves/adop-3.5m/adop-3.5m.ncml'), + 'adop-3.5m': ('Aquadopp 3.5m', 'oceanography/waves/adop-3.5m/adop-3.5m.ncml'), + 'aquadopp 3.5m': ('Aquadopp 3.5m', 'oceanography/waves/adop-3.5m/adop-3.5m.ncml'), + # 2m Aquadopp + 7: ('Aquadopp01 - 2m', 'oceanography/waves/adop01/adop01.ncml'), + 'adop-2m': ('Aquadopp01 - 2m', 'oceanography/waves/adop01/adop01.ncml'), + # xp200m pressure + 8: ('Paros xp200m', 'oceanography/waves/xp200m/xp200m.ncml'), + 'xp200m': ('Paros xp200m', 'oceanography/waves/xp200m/xp200m.ncml'), + 'xp200': ('Paros xp200m', 'oceanography/waves/xp200m/xp200m.ncml'), + # xp150m pressure + 9: ('Paros xp150m', 'oceanography/waves/xp150m/xp150m.ncml'), + 'xp150m': ('Paros xp150m', 'oceanography/waves/xp150m/xp150m.ncml'), + 'xp150': ('Paros xp150m', 'oceanography/waves/xp150m/xp150m.ncml'), + # xp125m pressure + 10: ('Paros xp125m', 'oceanography/waves/xp125m/xp125m.ncml'), + 'xp125m': ('Paros xp125m', 'oceanography/waves/xp125m/xp125m.ncml'), + 'xp125': ('Paros xp125m', 'oceanography/waves/xp125m/xp125m.ncml'), + # xp100m pressure + 11: ('Paros xp100m', 'oceanography/waves/xp100m/xp100m.ncml'), + 'xp100m': ('Paros xp100m', 'oceanography/waves/xp100m/xp100m.ncml'), + # 8m array + 12: ('8m array', 'oceanography/waves/8m-array/8m-array.ncml'), + '8m-Array': ('8m array', 'oceanography/waves/8m-array/8m-array.ncml'), + '8m Array': ('8m array', 'oceanography/waves/8m-array/8m-array.ncml'), + '8m array': ('8m array', 'oceanography/waves/8m-array/8m-array.ncml'), + '8m-array': ('8m array', 'oceanography/waves/8m-array/8m-array.ncml'), +} + + def gettime(allEpoch, epochStart, epochEnd, indexRef=0): """This function opens the netcdf file, and retrieves time. @@ -1343,157 +1514,42 @@ def getBathyGridFromNC(self, method, removeMask=True): def _waveGaugeURLlookup(self, gaugenumber): """A lookup table function that sets the URL backend for get wave spec and get wave gauge locations. + Uses O(1) dictionary lookup via module-level _WAVE_GAUGE_URLS table. + Args: gaugenumber: a string or number that refers to a specific gauge and will set a url - Available values inclue: + Available values include: 26m waverider can be [0, 'waverider-26m', 'Waverider-26m', '26m'] - 17m waverider can be [1, 'Waverider-17m', 'waverider-17m'] - 11m AWAC can be [2, 'AWAC-11m', 'awac-11m', 'Awac-11m'] - 8m AWAC can be [3, 'awac-8m', 'AWAC-8m'] - 6m AWAC can be [4, 'awac-6m', 'AWAC-6m'] - 4.5m AWAC can be [5, 'awac-4.5m', 'Awac-4.5m'] - 3.5m aquadopp can be [6, 'adop-3.5m', 'aquadopp 3.5m'] - 340m pressure can be ['xp340m', 'xp340'] - - 250m pressure can be ['8', 'xp250m', 'xp250'] - + 250m pressure can be ['xp250m', 'xp250'] 200m pressure can be [8, 'xp200m', 'xp200'] - 150m pressure can be [9, 'xp150m', 'xp150'] - 125m pressure can be [10, 'xp125m', 'xp125'] - 100m pressure can be [11, 'xp100m'] - - 8m array can be [8, '8m-Array', '8m Array', '8m array', '8m-array'] - - oregon inlet WR can be ['oregonInlet', 'OI', 'oi'] - - signature @ yFRF 940 xFRF 300 can be ['sig940-300', '940-300'] - - signature @ yFRF 769 xFRF 300 can be ['sig769-300', '769-300'] - - pressure @ yFRF 940 xFRF 200 can be ['paros-200-940m', 'paros-200-940m'] - - pressure @ yFRF 940 xFRF 200 can be ['paros-200-940m', 'paros-200-940m'] - - lidar wave gauge @ xFRF 140 can be ['lidarwavegauge140', 'lidargauge140', 'lidarwavegauge140m', - 'lidargauge140m'] - - lidar wave gauge @ xFRF 110 can be ['lidarwavegauge110', 'lidargauge110', 'lidarwavegauge110m', - 'lidargauge110m'] - - lidar wave gauge @ xFRF 100 can be ['lidarwavegauge100', 'lidargauge100', 'lidarwavegauge100m', - 'lidargauge100m'] - - lidar wave gauge @ xFRF 90 can be ['lidarwavegauge90', 'lidargauge90', 'lidarwavegauge90m', - 'lidargauge90m'] - - lidar wave gauge @ xFRF 80 can be ['lidarwavegauge80', 'lidargauge80', 'lidarwavegauge80m', - 'lidargauge80m'] + 8m array can be [12, '8m-Array', '8m Array', '8m array', '8m-array'] + oregon inlet WR can be ['oregonInlet', 'oi'] + signature sensors can be ['sig940-300', '940-300', 'sig769-300', '769-300', etc.] + paros sensors can be ['paros-200-940m', 'paros940-200', etc.] + lidar gauges can be ['lidarwavegauge140', 'lidargauge140', etc.] Returns: Nothing, this just sets the self.dataloc data member """ - if str(gaugenumber).lower() in ['0', 'waverider-26m', '26m']: - # 26 m wave rider - self.dataloc = 'oceanography/waves/waverider-26m/waverider-26m.ncml' # - # 'oceanography/waves/waverider430/waverider430.ncml' # 26m buoy - elif str(gaugenumber).lower() in ['1', 'waverider-17m', '17m']: - # 2D 17m waverider - self.dataloc = 'oceanography/waves/waverider-17m/waverider-17m.ncml' # 17 m buoy - elif str(gaugenumber).lower() in ['2', 'awac-11m', '11m']: - self.dataloc = 'oceanography/waves/awac-11m/awac-11m.ncml' - elif str(gaugenumber).lower() in ['3', 'awac-8m']: - self.dataloc = 'oceanography/waves/awac-8m/awac-8m.ncml' - elif str(gaugenumber).lower() in ['4', 'awac-6m', 'awac 6m']: - self.dataloc = 'oceanography/waves/awac-6m/awac-6m.ncml' - elif str(gaugenumber).lower() in ['5', 'awac-4.5m', 'awac_4.5m']: - self.dataloc = 'oceanography/waves/awac-4.5m/awac-4.5m.ncml' - elif str(gaugenumber).lower() in ['6', 'adop-3.5m', 'aquadopp 3.5m']: - self.dataloc = 'oceanography/waves/adop-3.5m/adop-3.5m.ncml' - elif str(gaugenumber).lower() in ['7', 'adop-2m']: - self.dataloc = 'oceanography/waves/adop01/adop01.ncml' - elif str(gaugenumber).lower() in ['xp340m', 'xp340']: - self.dataloc = "oceanography/waves/xp340m/xp340m.ncml" - elif str(gaugenumber).lower() in ['8', 'xp250m', 'xp250']: - self.dataloc = 'oceanography/waves/xp250m/xp250m.ncml' - elif str(gaugenumber).lower() in ['8', 'xp200m', 'xp200']: - self.dataloc = 'oceanography/waves/xp200m/xp200m.ncml' - elif str(gaugenumber).lower() in ['9', 'xp150m', 'xp150']: - self.dataloc = 'oceanography/waves/xp150m/xp150m.ncml' - elif str(gaugenumber).lower() in ['10', 'xp125m', 'xp125']: - self.dataloc = 'oceanography/waves/xp125m/xp125m.ncml' - elif str(gaugenumber).lower() in ['11', 'xp100m']: - self.dataloc = 'oceanography/waves/xp100m/xp100m.ncml' - elif str(gaugenumber).lower() in ['12', '8m array', '8m-array']: - self.dataloc = 'oceanography/waves/8m-array/8m-array.ncml' - elif str(gaugenumber).lower() in ['sig940-300', '940-300']: - self.dataloc = 'oceanography/waves/sig940-300/sig940-300.ncml' - elif str(gaugenumber).lower() in ['sig769-300', '769-300']: - self.dataloc = 'oceanography/waves/sig769-300/sig769-300.ncml' - elif str(gaugenumber).lower() in ['sig940-400', '940-400']: - self.dataloc = 'oceanography/waves/sig940-400/sig940-400.ncml' - elif str(gaugenumber).lower() in ['sig940-600', '940-600']: - self.dataloc = 'oceanography/waves/sig940-600/sig940-600.ncml' - # AWAC at jetty pier - elif str(gaugenumber).lower() in ['awac-jpier-11m', 'awac-jpier', 'jpier-11m']: - self.dataloc = 'oceanography/waves/awac-jpier-11m/awac-jpier-11m.ncml' - # New waveriders - elif str(gaugenumber).lower() in ['waverider-17m-1d', '17m-1d']: - self.dataloc = 'oceanography/waves/waverider-17m-1d/waverider-17m-1d.ncml' - elif str(gaugenumber).lower() in ['waverider-20m', 'waverider-20m-1d', '20m', '20m-1d']: - self.dataloc = 'oceanography/waves/waverider-20m-1d/waverider-20m-1d.ncml' - # Paros pressure sensors - support both naming conventions - elif str(gaugenumber).lower() in ['paros-200-940m', 'paros940-200']: - self.dataloc = 'oceanography/waves/paros940-200/paros940-200.ncml' - elif str(gaugenumber).lower() in ['paros-250-940m', 'paros940-250']: - self.dataloc = 'oceanography/waves/paros940-250/paros940-250.ncml' - elif str(gaugenumber).lower() in ['paros-340x-940y-top']: - self.dataloc = 'oceanography/waves/paros-340x-940y-top/paros-340x-940y-top.ncml' - # lidar wave gauges - 140 m - elif str(gaugenumber).lower() in ['lidarwavegauge140', 'lidargauge140', - 'lidarwavegauge140m', 'lidargauge140m']: - self.dataloc = 'oceanography/waves/lidarWaveGauge140/lidarWaveGauge140.ncml' - # lidar wave gauges - 110 m - elif str(gaugenumber).lower() in ['lidarwavegauge110', 'lidargauge110', - 'lidarwavegauge110m', 'lidargauge110m']: - self.dataloc = 'oceanography/waves/lidarWaveGauge110/lidarWaveGauge110.ncml' - # lidar wave gauges - 100 m - elif str(gaugenumber).lower() in ['lidarwavegauge100', 'lidargauge100', - 'lidarwavegauge100m', 'lidargauge100m']: - self.dataloc = 'oceanography/waves/lidarWaveGauge100/lidarWaveGauge100.ncml' - # lidar wave gauges - 90 m - elif str(gaugenumber).lower() in ['lidarwavegauge90', 'lidargauge90', 'lidarwavegauge90m', - 'lidargauge90m']: - self.dataloc = 'oceanography/waves/lidarWaveGauge90/lidarWaveGauge90.ncml' - # lidar wave gauges - 80 m - elif str(gaugenumber).lower() in ['lidarwavegauge80', 'lidargauge80', 'lidarwavegauge80m', - 'lidargauge80m']: - self.dataloc = 'oceanography/waves/lidarWaveGauge80/lidarWaveGauge80.ncml' - elif str(gaugenumber).lower() in ['oregoninlet', 'oi']: - self.dataloc = 'oceanography/waves/waverider-oregon-inlet-nc/waverider-oregon-inlet' \ - '-nc.ncml' - elif str(gaugenumber).lower() in ['lidarwavegauge080', 'lidarwavegauge80']: - self.dataloc = "oceanography/waves/lidarWaveGauge080/lidarWaveGauge080.ncml" - elif str(gaugenumber).lower() in ['lidarwavegauge090', 'lidarwavegauge90']: - self.dataloc = "oceanography/waves/lidarWaveGauge090/lidarWaveGauge090.ncml" - elif str(gaugenumber).lower() in ['lidarwavegauge100']: - self.dataloc = "oceanography/waves/lidarWaveGauge100/lidarWaveGauge100.ncml" - elif str(gaugenumber).lower() in ['lidarwavegauge110']: - self.dataloc = "oceanography/waves/lidarWaveGauge110/lidarWaveGauge110.ncml" - elif str(gaugenumber).lower() in ['lidarwavegauge140']: - self.dataloc = "oceanography/waves/lidarWaveGauge140/lidarWaveGauge140.ncml" + # Normalize to lowercase string for case-insensitive lookup + key = str(gaugenumber).lower() + # O(1) dictionary lookup + url = _WAVE_GAUGE_URLS.get(key) + if url is not None: + self.dataloc = url else: self.gname = 'There Are no Gauge numbers here' raise InvalidGaugeError(gaugenumber, message='Bad gauge name. See getWaveGaugeLoc for valid options.') @@ -1501,6 +1557,8 @@ def _waveGaugeURLlookup(self, gaugenumber): def _wlGageURLlookup(self, gaugenumber): """A lookup table function that sets the URL backend for getGageWL. + Uses O(1) dictionary lookup via module-level _WL_GAUGE_CONFIG table. + Args: gaugenumber: a string or number that refers to a specific gauge and will set a url Available values include: @@ -1513,45 +1571,16 @@ def _wlGageURLlookup(self, gaugenumber): 150m pressure can be [9, 'xp150m', 'xp150'] 125m pressure can be [10, 'xp125m', 'xp125'] 100m pressure can be [11, 'xp100m'] - 8m array can be [8, '8m-Array', '8m Array', '8m array', '8m-array'] + 8m array can be [12, '8m-Array', '8m Array', '8m array', '8m-array'] Returns: - Nothing, this just sets the self.dataloc data member + Nothing, this just sets the self.dataloc and self.gname data members """ - if gaugenumber in [2, 'AWAC-11m', 'awac-11m', 'Awac-11m']: - gname = 'AWAC 11m' - self.dataloc = 'oceanography/waves/awac-11m/awac-11m.ncml' - elif gaugenumber in [3, 'awac-8m', 'AWAC-8m']: - self.gname = 'AWAC 8m' - self.dataloc = 'oceanography/waves/awac-8m/awac-8m.ncml' - elif gaugenumber in [4, 'awac-6m', 'AWAC-6m']: - self.gname = 'AWAC 6m' - self.dataloc = 'oceanography/waves/awac-6m/awac-6m.ncml' - elif gaugenumber in [5, 'awac-4.5m', 'Awac-4.5m', 'awac_4.5m']: - self.gname = 'AWAC 4.5m' - self.dataloc = 'oceanography/waves/awac-4.5m/awac-4.5m.ncml' - elif gaugenumber in [6, 'adop-3.5m', 'aquadopp 3.5m']: - self.gname = 'Aquadopp 3.5m' - self.dataloc = 'oceanography/waves/adop-3.5m/adop-3.5m.ncml' - elif gaugenumber in [7, 'adop-2m']: - self.gname = 'Aquadopp01 - 2m' - self.dataloc = 'oceanography/waves/adop01/adop01.ncml' - elif gaugenumber in [8, 'xp200m', 'xp200']: - self.gname = 'Paros xp200m' - self.dataloc = 'oceanography/waves/xp200m/xp200m.ncml' - elif gaugenumber in [9, 'xp150m', 'xp150']: - self.gname = 'Paros xp150m' - self.dataloc = 'oceanography/waves/xp150m/xp150m.ncml' - elif gaugenumber in [10, 'xp125m', 'xp125']: - self.gname = 'Paros xp125m' - self.dataloc = 'oceanography/waves/xp125m/xp125m.ncml' - elif gaugenumber in [11, 'xp100m']: - self.gname = 'Paros xp100m' - self.dataloc = 'oceanography/waves/xp100m/xp100m.ncml' - elif gaugenumber in [12, '8m-Array', '8m Array', '8m array', '8m-array']: - self.gname = "8m array" - self.dataloc = 'oceanography/waves/8m-array/8m-array.ncml' + # O(1) dictionary lookup (supports both int and string keys) + config_tuple = _WL_GAUGE_CONFIG.get(gaugenumber) + if config_tuple is not None: + self.gname, self.dataloc = config_tuple else: self.gname = 'There Are no Gauge numbers here' raise InvalidGaugeError(gaugenumber, message='Bad gauge name. See _wlGageURLlookup for valid options.') diff --git a/tests/test_getDataFRF.py b/tests/test_getDataFRF.py index ce96862..c741d4e 100644 --- a/tests/test_getDataFRF.py +++ b/tests/test_getDataFRF.py @@ -1556,3 +1556,134 @@ def test_location_as_dict(self): assert result is not None np.testing.assert_array_equal(result['intensity'][0], [255, 128, 64]) + + +class TestLookupTableDictionaries: + """Tests for the O(1) dictionary lookup tables that replace if-elif chains.""" + + def test_wave_gauge_lookup_dict_imports(self): + """Test that lookup dictionaries are importable.""" + from murgtools.getdata.getDataFRF import _WAVE_GAUGE_URLS, _WL_GAUGE_CONFIG + assert isinstance(_WAVE_GAUGE_URLS, dict) + assert isinstance(_WL_GAUGE_CONFIG, dict) + assert len(_WAVE_GAUGE_URLS) > 50 # Should have many entries + assert len(_WL_GAUGE_CONFIG) > 20 # Should have many entries + + def test_wave_gauge_urls_lowercase_keys(self): + """Test that all keys in wave gauge dict are lowercase strings.""" + from murgtools.getdata.getDataFRF import _WAVE_GAUGE_URLS + for key in _WAVE_GAUGE_URLS: + assert isinstance(key, str), f"Key {key} should be a string" + assert key == key.lower(), f"Key {key} should be lowercase" + + def test_wl_gauge_config_structure(self): + """Test that WL gauge config has correct tuple structure.""" + from murgtools.getdata.getDataFRF import _WL_GAUGE_CONFIG + for key, value in _WL_GAUGE_CONFIG.items(): + assert isinstance(value, tuple), f"Value for {key} should be a tuple" + assert len(value) == 2, f"Value for {key} should have 2 elements (gname, url)" + gname, url = value + assert isinstance(gname, str), f"gname for {key} should be a string" + assert isinstance(url, str), f"url for {key} should be a string" + + def test_wave_gauge_lookup_all_aliases(self): + """Test that various gauge aliases resolve correctly via dictionary lookup.""" + from murgtools.getdata.getDataFRF import getObs + import datetime as DT + + # Create instance to test lookup + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + # Test various aliases for 26m waverider + for alias in ['0', 'waverider-26m', '26m']: + obs._waveGaugeURLlookup(alias) + assert 'waverider-26m' in obs.dataloc + + # Test case insensitivity + obs._waveGaugeURLlookup('AWAC-11M') + assert 'awac-11m' in obs.dataloc + + obs._waveGaugeURLlookup('WaveRider-26m') + assert 'waverider-26m' in obs.dataloc + + def test_wl_gauge_lookup_integer_keys(self): + """Test that WL gauge lookup works with integer keys.""" + from murgtools.getdata.getDataFRF import getObs + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + # Test integer keys + obs._wlGageURLlookup(2) + assert 'awac-11m' in obs.dataloc + assert obs.gname == 'AWAC 11m' + + obs._wlGageURLlookup(3) + assert 'awac-8m' in obs.dataloc + assert obs.gname == 'AWAC 8m' + + def test_wl_gauge_lookup_string_keys(self): + """Test that WL gauge lookup works with string keys.""" + from murgtools.getdata.getDataFRF import getObs + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + # Test string keys + obs._wlGageURLlookup('awac-8m') + assert 'awac-8m' in obs.dataloc + assert obs.gname == 'AWAC 8m' + + obs._wlGageURLlookup('xp200m') + assert 'xp200m' in obs.dataloc + assert obs.gname == 'Paros xp200m' + + def test_invalid_wave_gauge_raises_error(self): + """Test that invalid wave gauge raises InvalidGaugeError.""" + from murgtools.getdata.getDataFRF import getObs + from murgtools.exceptions import InvalidGaugeError + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + with pytest.raises(InvalidGaugeError): + obs._waveGaugeURLlookup('nonexistent_gauge') + + def test_invalid_wl_gauge_raises_error(self): + """Test that invalid WL gauge raises InvalidGaugeError.""" + from murgtools.getdata.getDataFRF import getObs + from murgtools.exceptions import InvalidGaugeError + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + with pytest.raises(InvalidGaugeError): + obs._wlGageURLlookup('nonexistent_gauge') + + def test_lidar_gauge_aliases(self): + """Test that lidar wave gauge aliases resolve correctly.""" + from murgtools.getdata.getDataFRF import _WAVE_GAUGE_URLS + + # Test all expected lidar gauge aliases exist + lidar_aliases = [ + 'lidarwavegauge140', 'lidargauge140', 'lidarwavegauge140m', 'lidargauge140m', + 'lidarwavegauge110', 'lidargauge110', 'lidarwavegauge110m', 'lidargauge110m', + 'lidarwavegauge100', 'lidargauge100', 'lidarwavegauge100m', 'lidargauge100m', + 'lidarwavegauge90', 'lidargauge90', 'lidarwavegauge90m', 'lidargauge90m', + 'lidarwavegauge80', 'lidargauge80', 'lidarwavegauge80m', 'lidargauge80m', + ] + for alias in lidar_aliases: + assert alias in _WAVE_GAUGE_URLS, f"Missing lidar alias: {alias}" + + def test_signature_sensor_aliases(self): + """Test that signature sensor aliases resolve correctly.""" + from murgtools.getdata.getDataFRF import _WAVE_GAUGE_URLS + + sig_aliases = [ + 'sig940-300', '940-300', + 'sig769-300', '769-300', + 'sig940-400', '940-400', + 'sig940-600', '940-600', + ] + for alias in sig_aliases: + assert alias in _WAVE_GAUGE_URLS, f"Missing signature alias: {alias}" From 915835fca0fb86f32c5a0e7f1dcc16be37a60815 Mon Sep 17 00:00:00 2001 From: Spicer Bak Date: Thu, 16 Jul 2026 12:08:02 -0400 Subject: [PATCH 2/6] Fix gauge lookup behavior and add input validation Refinements to dictionary lookup implementation: - Fix '8' key to map to xp250m (matches original if-elif first-match behavior) - Add explicit None input validation with clear error messages - Update docstrings to document Raises behavior - Add 5 new edge case tests: gauge '8' mapping, None handling, empty string, float inputs, and case insensitivity Co-Authored-By: Claude Opus 4.5 --- murgtools/getdata/getDataFRF.py | 27 ++++++++--- tests/test_getDataFRF.py | 80 +++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/murgtools/getdata/getDataFRF.py b/murgtools/getdata/getDataFRF.py index 3e34767..314f099 100755 --- a/murgtools/getdata/getDataFRF.py +++ b/murgtools/getdata/getDataFRF.py @@ -162,11 +162,12 @@ def open_dataset_with_retry(url, max_attempts=None, retry_delay=5): # xp340m pressure 'xp340m': 'oceanography/waves/xp340m/xp340m.ncml', 'xp340': 'oceanography/waves/xp340m/xp340m.ncml', - # xp250m pressure (note: '8' maps here, not xp200m - preserved from original) + # xp250m pressure - NOTE: '8' appears in both xp250m and xp200m in original code, + # but if-elif means first match wins, so '8' -> xp250m + '8': 'oceanography/waves/xp250m/xp250m.ncml', 'xp250m': 'oceanography/waves/xp250m/xp250m.ncml', 'xp250': 'oceanography/waves/xp250m/xp250m.ncml', - # xp200m pressure - '8': 'oceanography/waves/xp200m/xp200m.ncml', + # xp200m pressure (use explicit 'xp200m' or 'xp200' keys, not '8') 'xp200m': 'oceanography/waves/xp200m/xp200m.ncml', 'xp200': 'oceanography/waves/xp200m/xp200m.ncml', # xp150m pressure @@ -1528,8 +1529,8 @@ def _waveGaugeURLlookup(self, gaugenumber): 4.5m AWAC can be [5, 'awac-4.5m', 'Awac-4.5m'] 3.5m aquadopp can be [6, 'adop-3.5m', 'aquadopp 3.5m'] 340m pressure can be ['xp340m', 'xp340'] - 250m pressure can be ['xp250m', 'xp250'] - 200m pressure can be [8, 'xp200m', 'xp200'] + 250m pressure can be [8, 'xp250m', 'xp250'] + 200m pressure can be ['xp200m', 'xp200'] 150m pressure can be [9, 'xp150m', 'xp150'] 125m pressure can be [10, 'xp125m', 'xp125'] 100m pressure can be [11, 'xp100m'] @@ -1542,7 +1543,14 @@ def _waveGaugeURLlookup(self, gaugenumber): Returns: Nothing, this just sets the self.dataloc data member + Raises: + InvalidGaugeError: If gaugenumber is None or not a valid gauge identifier. + """ + # Input validation + if gaugenumber is None: + raise InvalidGaugeError(gaugenumber, message='Gauge number cannot be None.') + # Normalize to lowercase string for case-insensitive lookup key = str(gaugenumber).lower() @@ -1560,7 +1568,7 @@ def _wlGageURLlookup(self, gaugenumber): Uses O(1) dictionary lookup via module-level _WL_GAUGE_CONFIG table. Args: - gaugenumber: a string or number that refers to a specific gauge and will set a url + gaugenumber: a string or integer that refers to a specific gauge and will set a url Available values include: 11m AWAC can be [2, 'AWAC-11m', 'awac-11m', 'Awac-11m'] 8m AWAC can be [3, 'awac-8m', 'AWAC-8m'] @@ -1576,7 +1584,14 @@ def _wlGageURLlookup(self, gaugenumber): Returns: Nothing, this just sets the self.dataloc and self.gname data members + Raises: + InvalidGaugeError: If gaugenumber is None or not a valid gauge identifier. + """ + # Input validation + if gaugenumber is None: + raise InvalidGaugeError(gaugenumber, message='Gauge number cannot be None.') + # O(1) dictionary lookup (supports both int and string keys) config_tuple = _WL_GAUGE_CONFIG.get(gaugenumber) if config_tuple is not None: diff --git a/tests/test_getDataFRF.py b/tests/test_getDataFRF.py index c741d4e..f69acd0 100644 --- a/tests/test_getDataFRF.py +++ b/tests/test_getDataFRF.py @@ -1687,3 +1687,83 @@ def test_signature_sensor_aliases(self): ] for alias in sig_aliases: assert alias in _WAVE_GAUGE_URLS, f"Missing signature alias: {alias}" + + def test_gauge_8_maps_to_xp250m(self): + """Test that gauge '8' maps to xp250m (first match in original if-elif chain).""" + from murgtools.getdata.getDataFRF import getObs, _WAVE_GAUGE_URLS + import datetime as DT + + # Verify dictionary has correct mapping + assert _WAVE_GAUGE_URLS['8'] == 'oceanography/waves/xp250m/xp250m.ncml' + + # Verify lookup method returns xp250m for '8' + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + obs._waveGaugeURLlookup('8') + assert 'xp250m' in obs.dataloc + assert 'xp200m' not in obs.dataloc + + # Verify xp200m still accessible via explicit keys + obs._waveGaugeURLlookup('xp200m') + assert 'xp200m' in obs.dataloc + + def test_none_input_raises_error(self): + """Test that None input raises InvalidGaugeError with clear message.""" + from murgtools.getdata.getDataFRF import getObs + from murgtools.exceptions import InvalidGaugeError + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + with pytest.raises(InvalidGaugeError) as exc_info: + obs._waveGaugeURLlookup(None) + assert 'None' in str(exc_info.value) + + with pytest.raises(InvalidGaugeError) as exc_info: + obs._wlGageURLlookup(None) + assert 'None' in str(exc_info.value) + + def test_empty_string_raises_error(self): + """Test that empty string raises InvalidGaugeError.""" + from murgtools.getdata.getDataFRF import getObs + from murgtools.exceptions import InvalidGaugeError + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + with pytest.raises(InvalidGaugeError): + obs._waveGaugeURLlookup('') + + def test_numeric_gauge_inputs(self): + """Test that numeric inputs (int, float) are handled correctly.""" + from murgtools.getdata.getDataFRF import getObs + from murgtools.exceptions import InvalidGaugeError + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + # Integer input should work (str(0) = '0') + obs._waveGaugeURLlookup(0) + assert 'waverider-26m' in obs.dataloc + + # Float input str(0.0) = '0.0' which is NOT in dict, should raise error + with pytest.raises(InvalidGaugeError): + obs._waveGaugeURLlookup(0.0) + + def test_case_insensitivity_edge_cases(self): + """Test case insensitivity with mixed case inputs.""" + from murgtools.getdata.getDataFRF import getObs + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + # Test various case combinations + test_cases = [ + ('WAVERIDER-26M', 'waverider-26m'), + ('WaveRider-26m', 'waverider-26m'), + ('AWAC-11M', 'awac-11m'), + ('Awac-11m', 'awac-11m'), + ('XP200M', 'xp200m'), + ] + for input_val, expected_substr in test_cases: + obs._waveGaugeURLlookup(input_val) + assert expected_substr in obs.dataloc, f"Failed for input {input_val}" From e67e3907eeea88a0c02c0d850af86ecba241dd0b Mon Sep 17 00:00:00 2001 From: Spicer Bak Date: Thu, 16 Jul 2026 14:19:05 -0400 Subject: [PATCH 3/6] Address review comments on PR #59 Fixes: - Update docstrings to remove '8' from xp200m options (8 maps to xp250m) - Docstrings now correctly show: 250m pressure can be [8, 'xp250m', 'xp250'] - Note: test_gauge_8_maps_to_xp250m already covers this behavior Co-Authored-By: Claude Opus 4.5 --- murgtools/getdata/getDataFRF.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/murgtools/getdata/getDataFRF.py b/murgtools/getdata/getDataFRF.py index 314f099..1aec802 100755 --- a/murgtools/getdata/getDataFRF.py +++ b/murgtools/getdata/getDataFRF.py @@ -1575,7 +1575,7 @@ def _wlGageURLlookup(self, gaugenumber): 6m AWAC can be [4, 'awac-6m', 'AWAC-6m'] 4.5m AWAC can be [5, 'awac-4.5m', 'Awac-4.5m'] 3.5m aquadopp can be [6, 'adop-3.5m', 'aquadopp 3.5m'] - 200m pressure can be [8, 'xp200m', 'xp200'] + 200m pressure can be ['xp200m', 'xp200'] 150m pressure can be [9, 'xp150m', 'xp150'] 125m pressure can be [10, 'xp125m', 'xp125'] 100m pressure can be [11, 'xp100m'] @@ -3257,7 +3257,7 @@ def getWaveSpecModel(self, prefix, gaugenumber, model='STWAVE', removeBadWLFlag= 6m AWAC can be [4, 'awac-6m', 'AWAC-6m'] 4.5m AWAC can be [5, 'awac-4.5m', 'Awac-4.5m'] 3.5m aquadopp can be [6, 'adop-3.5m', 'aquadopp 3.5m'] - 200m pressure can be [8, 'xp200m', 'xp200'] + 200m pressure can be ['xp200m', 'xp200'] 150m pressure can be [9, 'xp150m', 'xp150'] 125m pressure can be [10, 'xp125m', 'xp125'] From a758af0d98f05212ced3477a3d431f7f32817039 Mon Sep 17 00:00:00 2001 From: Spicer Bak Date: Thu, 16 Jul 2026 14:25:05 -0400 Subject: [PATCH 4/6] Add test for '8' -> xp250m mapping to prevent regressions Addresses PR #59 review comment about ensuring the historically ambiguous numeric alias '8' is tested. The dictionary lookup preserves the original if-elif behavior where xp250m was matched first. Co-Authored-By: Claude Opus 4.5 --- tests/test_getDataFRF.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_getDataFRF.py b/tests/test_getDataFRF.py index f69acd0..64b79d0 100644 --- a/tests/test_getDataFRF.py +++ b/tests/test_getDataFRF.py @@ -1606,6 +1606,27 @@ def test_wave_gauge_lookup_all_aliases(self): obs._waveGaugeURLlookup('WaveRider-26m') assert 'waverider-26m' in obs.dataloc + def test_wave_gauge_lookup_8_maps_to_xp250m(self): + """Test that '8' maps to xp250m (not xp200m) for backward compatibility. + + In the original if-elif chain, '8' appeared in both xp250m and xp200m + conditions, but xp250m was checked first, so '8' -> xp250m. This test + ensures the dictionary lookup preserves that behavior. + """ + from murgtools.getdata.getDataFRF import getObs + import datetime as DT + + obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) + + # The key '8' should map to xp250m (first match in original if-elif) + obs._waveGaugeURLlookup('8') + assert 'xp250m' in obs.dataloc, f"'8' should map to xp250m, got {obs.dataloc}" + assert 'xp200m' not in obs.dataloc, f"'8' should NOT map to xp200m" + + # Integer 8 is also normalized to '8' via str().lower() + obs._waveGaugeURLlookup(8) + assert 'xp250m' in obs.dataloc, f"8 (int) should map to xp250m, got {obs.dataloc}" + def test_wl_gauge_lookup_integer_keys(self): """Test that WL gauge lookup works with integer keys.""" from murgtools.getdata.getDataFRF import getObs From 96fc3f2f7bf6ea841e0c0be1b48a685c2407a00a Mon Sep 17 00:00:00 2001 From: Spicer Bak Date: Thu, 16 Jul 2026 15:39:51 -0400 Subject: [PATCH 5/6] Address PR #59 review comments - Fix InvalidGaugeError messages for None input to include 'None' value - Remove duplicate test (test_wave_gauge_lookup_8_maps_to_xp250m) - Replace brittle dictionary size assertions with required key checks Co-Authored-By: Claude Opus 4.5 --- murgtools/getdata/getDataFRF.py | 4 ++-- tests/test_getDataFRF.py | 36 +++++++++++---------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/murgtools/getdata/getDataFRF.py b/murgtools/getdata/getDataFRF.py index 1aec802..9cba815 100755 --- a/murgtools/getdata/getDataFRF.py +++ b/murgtools/getdata/getDataFRF.py @@ -1549,7 +1549,7 @@ def _waveGaugeURLlookup(self, gaugenumber): """ # Input validation if gaugenumber is None: - raise InvalidGaugeError(gaugenumber, message='Gauge number cannot be None.') + raise InvalidGaugeError(None, message="Invalid gauge: 'None'. Gauge number cannot be None.") # Normalize to lowercase string for case-insensitive lookup key = str(gaugenumber).lower() @@ -1590,7 +1590,7 @@ def _wlGageURLlookup(self, gaugenumber): """ # Input validation if gaugenumber is None: - raise InvalidGaugeError(gaugenumber, message='Gauge number cannot be None.') + raise InvalidGaugeError(None, message="Invalid gauge: 'None'. Gauge number cannot be None.") # O(1) dictionary lookup (supports both int and string keys) config_tuple = _WL_GAUGE_CONFIG.get(gaugenumber) diff --git a/tests/test_getDataFRF.py b/tests/test_getDataFRF.py index 64b79d0..0b283fc 100644 --- a/tests/test_getDataFRF.py +++ b/tests/test_getDataFRF.py @@ -1562,12 +1562,21 @@ class TestLookupTableDictionaries: """Tests for the O(1) dictionary lookup tables that replace if-elif chains.""" def test_wave_gauge_lookup_dict_imports(self): - """Test that lookup dictionaries are importable.""" + """Test that lookup dictionaries are importable and contain required keys.""" from murgtools.getdata.getDataFRF import _WAVE_GAUGE_URLS, _WL_GAUGE_CONFIG assert isinstance(_WAVE_GAUGE_URLS, dict) assert isinstance(_WL_GAUGE_CONFIG, dict) - assert len(_WAVE_GAUGE_URLS) > 50 # Should have many entries - assert len(_WL_GAUGE_CONFIG) > 20 # Should have many entries + + # Check for required wave gauge keys (representative samples) + required_wave_keys = ['waverider-26m', 'waverider-17m', 'awac-11m', 'awac-8m', + '0', '1', '2', '3', 'xp200m', 'xp250m', '8m-array'] + for key in required_wave_keys: + assert key in _WAVE_GAUGE_URLS, f"Missing required wave gauge key: {key}" + + # Check for required WL gauge keys (including integer keys) + required_wl_keys = [2, 3, 'awac-11m', 'awac-8m', 8, 'xp200m'] + for key in required_wl_keys: + assert key in _WL_GAUGE_CONFIG, f"Missing required WL gauge key: {key}" def test_wave_gauge_urls_lowercase_keys(self): """Test that all keys in wave gauge dict are lowercase strings.""" @@ -1606,27 +1615,6 @@ def test_wave_gauge_lookup_all_aliases(self): obs._waveGaugeURLlookup('WaveRider-26m') assert 'waverider-26m' in obs.dataloc - def test_wave_gauge_lookup_8_maps_to_xp250m(self): - """Test that '8' maps to xp250m (not xp200m) for backward compatibility. - - In the original if-elif chain, '8' appeared in both xp250m and xp200m - conditions, but xp250m was checked first, so '8' -> xp250m. This test - ensures the dictionary lookup preserves that behavior. - """ - from murgtools.getdata.getDataFRF import getObs - import datetime as DT - - obs = getObs(DT.datetime(2024, 1, 1), DT.datetime(2024, 1, 2)) - - # The key '8' should map to xp250m (first match in original if-elif) - obs._waveGaugeURLlookup('8') - assert 'xp250m' in obs.dataloc, f"'8' should map to xp250m, got {obs.dataloc}" - assert 'xp200m' not in obs.dataloc, f"'8' should NOT map to xp200m" - - # Integer 8 is also normalized to '8' via str().lower() - obs._waveGaugeURLlookup(8) - assert 'xp250m' in obs.dataloc, f"8 (int) should map to xp250m, got {obs.dataloc}" - def test_wl_gauge_lookup_integer_keys(self): """Test that WL gauge lookup works with integer keys.""" from murgtools.getdata.getDataFRF import getObs From b521d708c93540343ec8e3bdd92265acee14f7bb Mon Sep 17 00:00:00 2001 From: Spicer Bak Date: Thu, 16 Jul 2026 15:51:38 -0400 Subject: [PATCH 6/6] Fix docstrings to include integer 8 for xp200m Addresses PR #59 review comments: - _wlGageURLlookup: Add 8 to 200m pressure options (matches _WL_GAUGE_CONFIG) - getWaveSpecModel: Add 8 to 200m pressure options (matches implementation) Note: In _WL_GAUGE_CONFIG, integer 8 maps to xp200m (water level). This is distinct from _WAVE_GAUGE_URLS where string '8' maps to xp250m (waves). Co-Authored-By: Claude Opus 4.5 --- murgtools/getdata/getDataFRF.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/murgtools/getdata/getDataFRF.py b/murgtools/getdata/getDataFRF.py index 9cba815..5bb68f2 100755 --- a/murgtools/getdata/getDataFRF.py +++ b/murgtools/getdata/getDataFRF.py @@ -1575,7 +1575,7 @@ def _wlGageURLlookup(self, gaugenumber): 6m AWAC can be [4, 'awac-6m', 'AWAC-6m'] 4.5m AWAC can be [5, 'awac-4.5m', 'Awac-4.5m'] 3.5m aquadopp can be [6, 'adop-3.5m', 'aquadopp 3.5m'] - 200m pressure can be ['xp200m', 'xp200'] + 200m pressure can be [8, 'xp200m', 'xp200'] 150m pressure can be [9, 'xp150m', 'xp150'] 125m pressure can be [10, 'xp125m', 'xp125'] 100m pressure can be [11, 'xp100m'] @@ -3257,7 +3257,7 @@ def getWaveSpecModel(self, prefix, gaugenumber, model='STWAVE', removeBadWLFlag= 6m AWAC can be [4, 'awac-6m', 'AWAC-6m'] 4.5m AWAC can be [5, 'awac-4.5m', 'Awac-4.5m'] 3.5m aquadopp can be [6, 'adop-3.5m', 'aquadopp 3.5m'] - 200m pressure can be ['xp200m', 'xp200'] + 200m pressure can be [8, 'xp200m', 'xp200'] 150m pressure can be [9, 'xp150m', 'xp150'] 125m pressure can be [10, 'xp125m', 'xp125']