diff --git a/scene_checks.py b/scene_checks.py
index ccdf896b3ee441e638e1984ac5c05367913b3539..17c6c13186e4e6be9391d252dbdc2da048b8a287 100644
--- a/scene_checks.py
+++ b/scene_checks.py
@@ -1,15 +1,69 @@
-from util import BaseCheckList
+from util import BaseCheckList, invalidate_records
+import pandas as pd
+import numpy as np
 
 def hatch_check(frame, parameters):
+    """
+    Check that the hatch is open on sky views
+    """
+    if not np.in1d(['hatchOpen','sceneMirrorPosition'], frame.columns).all():
+        return frame
+
+    frame['hatch_check'] = ((frame.hatchOpen != 1) &
+        (~frame.sceneMirrorPosition.isin([ord('H'), ord('A')]))) * 1
+
     return frame
 
+
 def safing_check(frame, parameters):
+    """
+    Check that the mirror doesn't safe during a calibration view and contaminate other records
+    """
+    if not np.in1d(['hatchOpen','sceneMirrorPosition'], frame.columns).all():
+        return frame
+
+    hatch_closing = ((frame.hatchOpen == 1)  & ((frame.hatchOpen == -3).diff(-1) == 1)).shift(1)
+    frame['safing_check'] = hatch_closing & frame.sceneMirrorPosition.isin([ord('H'), ord('A')])
+    frame = invalidate_records(frame, 'safing_check')
     return frame
 
+
 def encoder_check(frame, parameters):
     return frame
 
+
 class CheckList(BaseCheckList):
 
     checks = [hatch_check, safing_check, encoder_check]
     
+
+#### TESTS #####
+
+def test_hatch_check():
+    frame = pd.DataFrame({
+        'hatchOpen':[1,1,0],
+        'sceneMirrorPosition':[ord('H'), ord('A'), ord('S')]
+    })
+    assert hatch_check(frame, {})['hatch_check'].values.tolist() == [0,0,1]
+
+    frame = pd.DataFrame({
+        'hatchOpen':[1,0,1],
+        'sceneMirrorPosition':[ord('H'), ord('A'), ord('S')]
+    })
+    assert hatch_check(frame, {})['hatch_check'].values.tolist() == [0,0,0]
+    
+
+def test_safing_check():
+    frame = pd.DataFrame({
+        'hatchOpen':[0,0,0],
+        'sceneMirrorPosition':[ord('H'), ord('A'), ord('S')]
+    })
+    assert safing_check(frame, {})['safing_check'].values.tolist() == [0,0,0]
+
+    frame = pd.DataFrame({
+        'hatchOpen':[1,-3,0,0],
+        'sceneMirrorPosition':[ord('S'), ord('H'), ord('A'), ord('S')]
+    })
+    assert safing_check(frame, {})['safing_check'].values.tolist() == [1,1,0,1]
+
+
diff --git a/state_checks.py b/state_checks.py
index 0af097e3bff32af0ab22b3d1ebd6f8143cd954f8..df3f37460723e25fd95a5dc96eacf75ff999e004 100644
--- a/state_checks.py
+++ b/state_checks.py
@@ -1,9 +1,29 @@
-from util import BaseCheckList
+from util import BaseCheckList, invalidate_records
+import pandas as pd
+import numpy as np
 
 def detector_check(frame, parameters):
+    """
+    Check that the detector temp is in range
+    """
+    if 'detectorTemp' not in frame.columns:
+        return frame
+    frame['detector_check'] = (frame['detectorTemp'] > 90) * 1
+    frame = invalidate_records(frame, 'detector_check')
     return frame
 
 def hbb_thermistor_check(frame, parameters):
+    """
+    Check that all HBB thermistor temps are in range
+    """
+    if not np.in1d(['HBBbottomTemp','HBBapexTemp','HBBtopTemp'], frame.columns).all():
+        return frame
+    frame['hbb_thermistor_check'] = (
+        (abs(frame['HBBbottomTemp'] - 333) > 2) |
+        (abs(frame['HBBapexTemp'] - 333) > 2) |
+        (abs(frame['HBBtopTemp'] - 333) > 2)
+    ) * 1
+    frame = invalidate_records(frame, 'hbb_thermistor_check')
     return frame
 
 def hbb_stable_check(frame, parameters):
@@ -11,3 +31,32 @@ def hbb_stable_check(frame, parameters):
 
 class CheckList(BaseCheckList):
     checks = [detector_check, hbb_thermistor_check, hbb_stable_check]
+
+
+#### TESTS ####
+
+def test_hbb_thermistor_check():
+    frame = hbb_thermistor_check(pd.DataFrame({
+        'HBBbottomTemp':[300,333,336],
+        'HBBapexTemp':[300,333,336],
+        'HBBtopTemp':[300,333,336],
+        'sceneMirrorPosition':[ord('H'), ord('A'), ord('S')]
+    }), {})
+    assert all(frame['hbb_thermistor_check'] == [1,0,1])
+
+def test_hbb_thermistor_check2():
+    frame = hbb_thermistor_check(pd.DataFrame({
+        'HBBbottomTemp':[300,333,333],
+        'HBBapexTemp':[300,333,333],
+        'HBBtopTemp':[300,333,333],
+        'sceneMirrorPosition':[ord('H'), ord('A'), ord('S')]
+    }), {})
+    assert all(frame['hbb_thermistor_check'] == [1,0,1])
+
+def test_detector_check():
+    frame = detector_check(pd.DataFrame({
+        'detectorTemp':[50,100],
+        'sceneMirrorPosition':[ord('H'), ord('A')]
+    }), {})
+    assert all(frame['detector_check'] == [0,1])
+
diff --git a/test.py b/test.py
index 79a0d007f97d921e0aeb81d4f159fb716bea6f90..5e759c1557a49c63dc87b5c89b652e459a6a2cf8 100644
--- a/test.py
+++ b/test.py
@@ -1,4 +1,5 @@
 from main import check_frame
+from util import invalidate_record, invalidate_records
 
 import pandas as pd
 
@@ -9,3 +10,33 @@ def test_it_works():
     qc_frame = check_frame(random_dataframe, {})
     assert 'qc_percent' in qc_frame.columns
     print(qc_frame)
+
+def test_invalidate_record():
+    invalidated = invalidate_record(three_scenes.copy(), 2, 'test', .5)
+    assert invalidated.iloc[2].test == .5
+    assert pd.np.isnan(invalidated.iloc[3].test)
+    assert pd.np.isnan(invalidated.iloc[1].test)
+
+    tricky_index = three_scenes.copy()
+    tricky_index.index = tricky_index.index - 1
+    invalidated = invalidate_record(tricky_index, 2, 'test', .5)
+    assert invalidated.iloc[2].test == .5, invalidated.test
+    assert pd.np.isnan(invalidated.iloc[3].test)
+    assert pd.np.isnan(invalidated.iloc[1].test)
+
+    corrupt_cal = three_scenes.copy()
+    invalidated = invalidate_record(corrupt_cal, 3, 'test', .5)
+    assert invalidated.iloc[2].test == .5, invalidated.test
+    assert invalidated.iloc[5].test == .5, invalidated.test
+
+
+def test_invalidate_records():
+    corrupt_cal = three_scenes.copy()
+    corrupt_cal['test'] = 0
+    corrupt_cal['test'][3] = 1
+    
+    invalidated = invalidate_records(corrupt_cal, 'test')
+    assert invalidated.iloc[2].test == 1, invalidated.test
+    assert invalidated.iloc[5].test == 1, invalidated.test
+
+three_scenes = pd.DataFrame({'sceneMirrorPosition': list(map(ord, 'HASAHSAHSAH'))})
diff --git a/util.py b/util.py
index 188905534b0ce608c101b57c1a78cbf4f9e95321..3209df47435b3899871efa36208288e5bc66b9a3 100644
--- a/util.py
+++ b/util.py
@@ -1,7 +1,47 @@
+from itertools import takewhile
+import numpy as np
+import pandas as pd
 
-def invalidate_record(frame, record, reason):
-    pass
+def invalidate_records(frame, check_name):
+    for index,percent in frame.ix[frame[check_name] > 0, check_name].iteritems():
+        invalidate_record(frame, index, check_name, percent)
+    return frame
 
+def invalidate_record(frame, index, check_name, value):
+    frame.ix[frame.index[index], check_name] = value
+    corrupt_view = frame.iloc[index].sceneMirrorPosition
+    if corrupt_view in [ord('H'),ord('A')]:
+
+        # Corrupt calibration view, must also invalidate neighboring scenes
+        _idx = index + 1
+        while _idx < len(frame):
+            if frame.sceneMirrorPosition.iloc[_idx] == corrupt_view:
+                # Made one cycle
+                break
+            elif frame.sceneMirrorPosition.iloc[_idx] in [ord('H'), ord('A')]:
+                # Skip opposite calibration views
+                _idx += 1
+                continue
+            else:
+                # Invalidate non-calibration views
+                frame.ix[frame.index[_idx], check_name] = value
+                _idx += 1
+
+        _idx = index - 1
+        while _idx >= 0:
+            if frame.sceneMirrorPosition.iloc[_idx] == corrupt_view:
+                # Made one cycle
+                break
+            elif frame.sceneMirrorPosition.iloc[_idx] in [ord('H'), ord('A')]:
+                # Skip opposite calibration views
+                _idx -= 1
+                continue
+            else:
+                # Invalidate non-calibration views
+                frame.ix[frame.index[_idx], check_name] = value
+                _idx -= 1
+
+    return frame
 
 class BaseCheckList: