Repository: sunpy/sunpy

Issue:
Expose GOES quality flags
### Describe the feature

Currently, it is not possible to differentiate between corrupt and normal values when using the GOES `XRSTimeSeries`. As there are quite a few corrupt values which can be seen in the following image (see number of X-class values), it would be great to expose the flags indicating whether a given value is corrupt or not as well.
![173183771-b030c0e6-6897-4752-a341-e17b3e054658](https://user-images.githubusercontent.com/14012777/174065516-852ec962-9c30-40b0-b718-86d264c2226f.png)
(also refer to https://satdat.ngdc.noaa.gov/sem/goes/data/science/xrs/GOES_13-15_XRS_Science-Quality_Data_Readme.pdf)

### Proposed solution

Exposes GOES quality flags (xrsa_quality, xrsb_quality) as part of the `XRSTimeSeries` in order to filter for invalid values. An example could look as follows:

```
from sunpy import timeseries as ts
from sunpy.net import Fido
from sunpy.net import attrs as a

t_start = "2014-01-01 00:00"
t_end = "2014-01-31 00:00"
search_result = Fido.search(
                        a.Time(t_start, t_end), a.Instrument.xrs)
download_result = Fido.fetch(search_result)
goes_ts = ts.TimeSeries(download_result, concatenate=True)
df = goes_ts.to_dataframe()
df_clean = df[(df["xrsb_quality"] == 0)]
```

A PR will follow


Discussion:

Resolving patch:
diff --git a/examples/acquiring_data/goes_xrs_example.py b/examples/acquiring_data/goes_xrs_example.py
--- a/examples/acquiring_data/goes_xrs_example.py
+++ b/examples/acquiring_data/goes_xrs_example.py
@@ -67,6 +67,14 @@
 goes_15 = ts.TimeSeries(file_goes15)
 goes_15.peek()
 
+#############################################################
+# The resulting `~sunpy.timeseries.TimeSeries` can be filtered by GOES quality flags. For more information
+# refer to the `GOES Data Guide <https://satdat.ngdc.noaa.gov/sem/goes/data/science/xrs/GOES_13-15_XRS_Science-Quality_Data_Readme.pdf>`__.
+
+df = goes_15.to_dataframe()
+df = df[(df["xrsa_quality"] == 0) & (df["xrsb_quality"] == 0)]
+goes_15 = ts.TimeSeries(df, goes_15.meta, goes_15.units)
+
 ###############################################################
 # We can also pull out the individual GOES chanels and plot. The 0.5-4 angstrom
 # channel is known as the "xrsa" channel and the 1-8 angstrom channel is known
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
@@ -218,6 +218,7 @@ def _parse_hdus(cls, hdulist):
                 raise ValueError("Date not recognized")
             xrsb = hdulist[2].data['FLUX'][0][:, 0]
             xrsa = hdulist[2].data['FLUX'][0][:, 1]
+            # TODO how to extract quality flags from HDU?
             seconds_from_start = hdulist[2].data['TIME'][0]
         elif 1 <= len(hdulist) <= 3:
             start_time = parse_time(header['TIMEZERO'], format='utime')
@@ -263,6 +264,8 @@ def _parse_netcdf(filepath):
             if "a_flux" in d.variables:
                 xrsa = np.array(d["a_flux"])
                 xrsb = np.array(d["b_flux"])
+                xrsa_quality = np.array(d['a_flags'])
+                xrsb_quality = np.array(d['b_flags'])
 
                 start_time_str = d["time"].attrs["units"]
                 if not isinstance(start_time_str, str):
@@ -274,6 +277,9 @@ def _parse_netcdf(filepath):
             elif "xrsa_flux" in d.variables:
                 xrsa = np.array(d["xrsa_flux"])
                 xrsb = np.array(d["xrsb_flux"])
+                xrsa_quality = np.array(d['xrsa_flags'])
+                xrsb_quality = np.array(d['xrsb_flags'])
+
                 start_time_str = d["time"].attrs["units"]
                 if not isinstance(start_time_str, str):
                     # For h5netcdf<0.14
@@ -285,10 +291,13 @@ def _parse_netcdf(filepath):
             else:
                 raise ValueError(f"The file {filepath} doesn't seem to be a GOES netcdf file.")
 
-        data = DataFrame({"xrsa": xrsa, "xrsb": xrsb}, index=times.datetime)
+        data = DataFrame({"xrsa": xrsa, "xrsb": xrsb, "xrsa_quality": xrsa_quality,
+                         "xrsb_quality": xrsb_quality}, index=times.datetime)
         data = data.replace(-9999, np.nan)
         units = OrderedDict([("xrsa", u.W/u.m**2),
-                             ("xrsb", u.W/u.m**2)])
+                             ("xrsb", u.W/u.m**2),
+                             ("xrsa_quality", int),
+                             ("xrsb_quality", int)])
 
         return data, header, units
 


Test patch: