Repository: sunpy/sunpy

Issue:
Adding GOES SatelliteNumber to XRSTimeSeries when reading cdf files with TimeSeries
There is a dedicated field for satellite number in the goes metadata at XRSTimeSeries.meta.metas[0]['platform'] but this, like many of these fields is blank for all but the most modern data. Currently you get the GOES satellite number by parsing a number of othjer fields or the filenames themselves. 

Since SatelliteNumber is a generally useful field, should this be something that is parsed by TimeSeries when loading the data and is accessible directly from the XRSTimeSeries rather than having users construct their own way of harvesting the information.


Discussion:
Adding a new `.satellite_number` property to `XRSTimeSeries` that automatically extracts this seems like a good way to solve this. Would you be up for opening a pull request with that addition?
Yeah will have a go.

Resolving patch:
diff --git a/sunpy/timeseries/sources/goes.py b/sunpy/timeseries/sources/goes.py
--- a/sunpy/timeseries/sources/goes.py
+++ b/sunpy/timeseries/sources/goes.py
@@ -18,6 +18,7 @@
 
 import sunpy.io
 from sunpy import log
+from sunpy.extern import parse
 from sunpy.io.file_tools import UnrecognizedFileTypeError
 from sunpy.time import is_time_in_given_format, parse_time
 from sunpy.timeseries.timeseriesbase import GenericTimeSeries
@@ -115,6 +116,40 @@ def plot(self, axes=None, **kwargs):
         axes.fmt_xdata = matplotlib.dates.DateFormatter("%H:%M")
         return axes
 
+    @property
+    def observatory(self):
+        """
+        Retrieves the goes satellite number by parsing the meta dictionary.
+        """
+        # various pattern matches for the meta fields.
+        pattern_old = ("{}go{SatelliteNumber:02d}{}{month:2d}{day:2d}.fits{}")
+        pattern_new = ("{}sci_gxrs-l2-irrad_g{SatelliteNumber:02d}_d{year:4d}{month:2d}{day:2d}_{}.nc{}")
+        pattern_r = ("{}sci_xrsf-l2-flx1s_g{SatelliteNumber:02d}_d{year:4d}{month:2d}{day:2d}_{}.nc{}")
+        pattern_telescop = ("GOES {SatelliteNumber:02d}")
+        pattern_inst = ("{}GOES 1-{SatelliteNumber:02d} {}")
+
+        try:
+            id = self.meta.metas[0]['id']
+            parsed = parse(pattern_r, str(id))
+            if parsed is None:
+                parsed = parse(pattern_new, str(id))
+                if parsed is None:
+                    parsed = parse(pattern_old, str(id))
+                    if parsed is None:
+                        id = self.meta.metas[0]['Instrument']
+                        parsed = parse(pattern_inst, str(id))
+        except KeyError:
+            try:
+                id = self.meta.metas[0]['TELESCOP']
+                parsed = parse(pattern_telescop, str(id))
+            except KeyError:
+                log.debug('Satellite Number not found in metadata')
+                return
+        if parsed is None:
+            log.debug('Satellite Number not found in metadata')
+            return
+        return f"GOES-{parsed['SatelliteNumber']}"
+
     @peek_show
     def peek(self, title="GOES Xray Flux", **kwargs):
         """
diff --git a/sunpy/timeseries/timeseriesbase.py b/sunpy/timeseries/timeseriesbase.py
--- a/sunpy/timeseries/timeseriesbase.py
+++ b/sunpy/timeseries/timeseriesbase.py
@@ -140,6 +140,13 @@ def source(self):
         """
         return self._source
 
+    @property
+    def observatory(self):
+        """
+        A string/object used to specify the observatory for the TimeSeries.
+        """
+        return
+
     @property
     def columns(self):
         """


Test patch:
diff --git a/sunpy/data/test/goes_truncated_test_goes16.nc b/sunpy/data/test/goes_truncated_test_goes17.nc
similarity index 100%
rename from sunpy/data/test/goes_truncated_test_goes16.nc
rename to sunpy/data/test/goes_truncated_test_goes17.nc
diff --git a/sunpy/timeseries/tests/test_timeseries_factory.py b/sunpy/timeseries/tests/test_timeseries_factory.py
--- a/sunpy/timeseries/tests/test_timeseries_factory.py
+++ b/sunpy/timeseries/tests/test_timeseries_factory.py
@@ -34,7 +34,7 @@
 goes_filepath_com = os.path.join(filepath, 'go1520120601.fits.gz')
 lyra_filepath = os.path.join(filepath, 'lyra_20150101-000000_lev3_std_truncated.fits.gz')
 new_goes15_filepath = os.path.join(filepath, 'goes_truncated_test_goes15.nc')
-new_goes16_filepath = os.path.join(filepath, 'goes_truncated_test_goes16.nc')
+new_goes17_filepath = os.path.join(filepath, 'goes_truncated_test_goes17.nc')
 noaa_ind_json_filepath = os.path.join(filepath, 'observed-solar-cycle-indices-truncated.json')
 noaa_ind_txt_filepath = os.path.join(filepath, 'RecentIndices_truncated.txt')
 noaa_pre_json_filepath = os.path.join(filepath, 'predicted-solar-cycle-truncated.json')
@@ -145,11 +145,32 @@ def test_implicit_new_goes15(self):
         ts_goes = sunpy.timeseries.TimeSeries(new_goes15_filepath)
         assert isinstance(ts_goes, sunpy.timeseries.sources.goes.XRSTimeSeries)
 
-    def test_implicit_new_goes16(self):
+    def test_implicit_new_goes17(self):
         # Test a GOES TimeSeries
-        ts_goes = sunpy.timeseries.TimeSeries(new_goes16_filepath)
+        ts_goes = sunpy.timeseries.TimeSeries(new_goes17_filepath)
         assert isinstance(ts_goes, sunpy.timeseries.sources.goes.XRSTimeSeries)
 
+    def test_implicit_goes_satno(self):
+        # Test a GOES TimeSeries for satellite number
+        ts_goes = sunpy.timeseries.TimeSeries(goes_filepath)
+        assert ts_goes.observatory == 'GOES-15'
+
+    def test_implicit_new_goes15_satno(self):
+        # Test a GOES TimeSeries for satellite number
+        ts_goes = sunpy.timeseries.TimeSeries(new_goes15_filepath)
+        assert ts_goes.observatory == 'GOES-15'
+
+    def test_implicit_new_goes17_satno(self):
+        # Test a GOES TimeSeries for satellite number
+        ts_goes = sunpy.timeseries.TimeSeries(new_goes17_filepath)
+        assert ts_goes.observatory == 'GOES-17'
+
+    def test_implicit_goes_satno_missing(self):
+        # Test a GOES TimeSeries for a missing satellite number
+        ts_goes = sunpy.timeseries.TimeSeries(new_goes17_filepath)
+        del ts_goes.meta.metas[0]['id']
+        assert ts_goes.observatory is None
+
     def test_implicit_lyra(self):
         # Test a LYRA TimeSeries
         ts_lyra = sunpy.timeseries.TimeSeries(lyra_filepath)
@@ -207,7 +228,7 @@ def test_new_goes15(self):
 
     def test_new_goes16(self):
         # Test a GOES TimeSeries
-        ts_goes = sunpy.timeseries.TimeSeries(new_goes16_filepath, source='XRS')
+        ts_goes = sunpy.timeseries.TimeSeries(new_goes17_filepath, source='XRS')
         assert isinstance(ts_goes, sunpy.timeseries.sources.goes.XRSTimeSeries)
 
     def test_lyra(self):