diff --git a/edosl0util/rdrgen.py b/edosl0util/rdrgen.py
index cf527b41dc69e949094867b9c7761d2fe2f951ee..674976969856ba22f4d72315b16c3fc80d2d5c3c 100644
--- a/edosl0util/rdrgen.py
+++ b/edosl0util/rdrgen.py
@@ -16,83 +16,6 @@ from edosl0util.jpssrdr import (
 from edosl0util.stream import jpss_packet_stream
 
 
-class GetJpssPacketTime(object):
-    def __init__(self):
-        self._viirs_tracker = ViirsGroupedPacketTimeTracker()
-
-    def __call__(self, pkt):
-        if self._viirs_tracker.handles(pkt.apid):
-            return self._viirs_tracker.get_iet(pkt)
-        else:
-            return get_packet_iet(pkt)
-
-
-class ViirsGroupedPacketTimeTracker(object):
-    grouped_apids = list(range(800, 824)) + [825]
-
-    @classmethod
-    def handles(cls, apid):
-        return apid in cls.grouped_apids
-
-    def __init__(self):
-        self._db = {}
-
-    def get_iet(self, pkt):
-        if not self.handles(pkt.apid):
-            raise ValueError('APID {} not a VIIRS grouped packet type'.format(pkt.apid))
-        if pkt.is_first():
-            obs_iet = get_packet_iet(pkt)
-            self._db[pkt.apid] = (obs_iet, pkt.seqid)
-            return obs_iet
-        else:
-            last_obs_iet, last_seq = self._db[pkt.apid]
-            group_size = ViirsScienceApidInfo.get_packets_per_scan(pkt.apid)
-            if not self.check_sequence_number(pkt.seqid, last_seq, group_size):
-                raise OrphanedViirsPacket(pkt)
-            if not self.check_packet_iet(self.get_viirs_iet(pkt), last_obs_iet):
-                raise OrphanedViirsPacket(pkt)
-            return last_obs_iet
-
-    @staticmethod
-    def get_viirs_iet(pkt):
-        if pkt.is_standalone():
-            idx = 18
-        elif pkt.is_first():
-            idx = 20
-        else:
-            idx = 10
-        arr = np.frombuffer(pkt.bytes()[idx:idx+8], 'B')
-        days = arr[0:2].view('>u2')[0]
-        ms = arr[2:6].view('>u4')[0]
-        us = arr[6:8].view('>u2')[0]
-        return timecode_parts_to_iet(days, ms, us)
-
-    @staticmethod
-    def check_sequence_number(nonfirst_seq_num, first_seq_num, group_size):
-        seq_limit = 2**14
-        group_end = first_seq_num + group_size
-        # the 2nd check below is needed to handle wrap-around
-        return (first_seq_num < nonfirst_seq_num < group_end
-                or first_seq_num < nonfirst_seq_num + seq_limit < group_end)
-
-    @staticmethod
-    def check_packet_iet(pkt_iet, obs_iet):
-        # this can be a pretty loose check since it is only needed to prevent
-        # the very rare case where the sequence number check yields a false
-        # positive
-        permitted_lag = 5  # seconds
-        lag = (pkt_iet - obs_iet) * 1e-6
-        return 0 <= lag <= permitted_lag
-
-
-class OrphanedViirsPacket(Exception):
-    def __init__(self, pkt):
-        self.packet = pkt
-
-    def __str__(self):
-        return repr(self.packet)
-
-
 def packets_to_rdrs(sat, pkt_files):
     # FIXME: refactor!!!
     rdr_pkt_files = {}
@@ -292,6 +215,17 @@ def build_rdr_blob(sat, pkt_stream):
     return buf
 
 
+class GetJpssPacketTime(object):
+    def __init__(self):
+        self._viirs_tracker = ViirsGroupedPacketTimeTracker()
+
+    def __call__(self, pkt):
+        if self._viirs_tracker.tracks_apid(pkt.apid):
+            return self._viirs_tracker.get_iet(pkt)
+        else:
+            return get_packet_iet(pkt)
+
+
 class ViirsScienceApidInfo(object):
     apids = list(x for x in range(800, 827) if x != 824)
     names = ['M04', 'M05', 'M03', 'M02', 'M01', 'M06', 'M07', 'M09', 'M10',
@@ -326,39 +260,43 @@ class ViirsScienceApidInfo(object):
             return 33
 
 
-def get_cris_science_apids():
-    return ([ApidSpec(1289, 'EIGHT_S_SCI', 5), ApidSpec(1290, 'ENG', 1)]
-            + get_cris_obs_apids())
-
-
-def get_cris_obs_apids():
-    view_types = ['N', 'S', 'C']  # "normal", "space", "calibration"
-    bands = ['LW', 'MW', 'SW']
-    num_fovs = 9
-    base_apid = 1315
-    apids = []
-    for i in range(len(view_types) * len(bands) * num_fovs):
-        apid = base_apid + i
-        view_type = view_types[i // (num_fovs * len(bands))]
-        band = bands[i // num_fovs % len(bands)]
-        fov = str(i % num_fovs + 1)
-        apid_name = view_type + band + fov
-        max_expected = get_max_expected_cris_packets(apid_name)
-        apids.append(ApidSpec(apid, apid_name, max_expected))
-    return apids
-
-
-def get_max_expected_cris_packets(apid_name):
-    if apid_name == 'EIGHT_S_SCI':
-        return 5
-    elif apid_name == 'ENG':
-        return 1
-    else:
-        view_type = apid_name[0]
-        if view_type == 'N':
-            return 121
+class CrisScienceApidInfo(object):
+    apids = [1289, 1290] + list(range(1315, 1396))
+
+    @classmethod
+    def get_specs(cls):
+        return [ApidSpec(apid, cls.get_name(apid), cls.get_max_expected(apid))
+                for apid in cls.apids]
+
+    @classmethod
+    def get_name(cls, apid):
+        if apid == 1289:
+            return 'EIGHT_S_SCI'
+        elif apid == 1290:
+            return 'ENG'
         else:
-            return 9
+            offset = apid - 1315
+            view_types = ['N', 'S', 'C']
+            bands = ['LW', 'MW', 'SW']
+            num_fovs = 9
+            view_type = view_types[offset // (num_fovs * len(bands))]
+            band = bands[offset // num_fovs % len(bands)]
+            fov = str(offset % num_fovs + 1)
+            return view_type + band + fov
+
+    @classmethod
+    def get_max_expected(cls, apid):
+        name = cls.get_name(apid)
+        if name == 'EIGHT_S_SCI':
+            return 5
+        elif name == 'ENG':
+            return 1
+        else:
+            view_type = name[0]
+            if view_type == 'N':
+                return 121
+            else:
+                return 9
 
 
 @attr.s
@@ -407,7 +345,7 @@ class CrisScienceRdrType(object):
     sensor = 'cris'
     type_id = 'SCIENCE'
     document = '474-00448-02-03_JPSS-DD-Vol-II-Part-3_0200B.pdf'
-    apids = get_cris_science_apids()
+    apids = CrisScienceApidInfo.get_specs()
 
 
 @rdr_type_spec
@@ -423,6 +361,72 @@ class SpacecraftDiaryRdrType(object):
              ApidSpec(11, 'DIARY', max_expected=21)]
 
 
+class ViirsGroupedPacketTimeTracker(object):
+    grouped_apids = list(range(800, 824)) + [825]
+
+    @classmethod
+    def tracks_apid(cls, apid):
+        return apid in cls.grouped_apids
+
+    def __init__(self):
+        self._db = {}
+
+    def get_iet(self, pkt):
+        if not self.tracks_apid(pkt.apid):
+            raise ValueError('APID {} not a VIIRS grouped packet type'.format(pkt.apid))
+        if pkt.is_first():
+            obs_iet = get_packet_iet(pkt)
+            self._db[pkt.apid] = (obs_iet, pkt.seqid)
+            return obs_iet
+        else:
+            last_obs_iet, last_seq = self._db[pkt.apid]
+            group_size = ViirsScienceApidInfo.get_packets_per_scan(pkt.apid)
+            if not self.check_sequence_number(pkt.seqid, last_seq, group_size):
+                raise OrphanedViirsPacket(pkt)
+            if not self.check_packet_iet(self.get_viirs_iet(pkt), last_obs_iet):
+                raise OrphanedViirsPacket(pkt)
+            return last_obs_iet
+
+    @staticmethod
+    def get_viirs_iet(pkt):
+        if pkt.is_standalone():
+            idx = 18
+        elif pkt.is_first():
+            idx = 20
+        else:
+            idx = 10
+        arr = np.frombuffer(pkt.bytes()[idx:idx+8], 'B')
+        days = arr[0:2].view('>u2')[0]
+        ms = arr[2:6].view('>u4')[0]
+        us = arr[6:8].view('>u2')[0]
+        return timecode_parts_to_iet(days, ms, us)
+
+    @staticmethod
+    def check_sequence_number(nonfirst_seq_num, first_seq_num, group_size):
+        seq_limit = 2**14
+        group_end = first_seq_num + group_size
+        # the 2nd check below is needed to handle wrap-around
+        return (first_seq_num < nonfirst_seq_num < group_end
+                or first_seq_num < nonfirst_seq_num + seq_limit < group_end)
+
+    @staticmethod
+    def check_packet_iet(pkt_iet, obs_iet):
+        # this can be a pretty loose check since it is only needed to prevent
+        # the very rare case where the sequence number check yields a false
+        # positive
+        permitted_lag = 5  # seconds
+        lag = (pkt_iet - obs_iet) * 1e-6
+        return 0 <= lag <= permitted_lag
+
+
+class OrphanedViirsPacket(Exception):
+    def __init__(self, pkt):
+        self.packet = pkt
+
+    def __str__(self):
+        return repr(self.packet)
+
+
 def make_rdr_filename(prod_id, sat, gran_time, gran_end_time, orbit_num, creation_time,
                       origin, domain, compressed):
     sat_strs = {'snpp': 'npp'}