Repository: spacetelescope/jwst

Issue:
Background subtraction of 5 point nods in S1600A1 subtracts too much source flux
_Issue [JP-3551](https://jira.stsci.edu/browse/JP-3551) was created on JIRA by [Charles Proffitt](https://jira.stsci.edu/secure/ViewProfile.jspa?name=proffitt):_

For the five point dither pattern in the NIRSpec S1600A1 in FS mode, the primary dither spacing is only 0.2571", (with some additional variation if spatial sub-dithers are also included). This leads to significant overlap of the PSF wings of point sources in each image with the wings of the PSF in the background images and can reduce the net extracted flux by about 25% relative to the baseline calibration when the usual nod subtraction is done.  But since we have 5 dither positions, this could be significantly reduced by not including the one or two immediately adjacent dither positions as background members in the level 2 files. One way to do this would be to require a minimum separation in the cross-dispersion direction before allowing another association member to be used as a background member for a given science exposure.  Taking into account the additional sub-pixel dither offsets, and the dither patterns used for other slits, I believe that a requirement that the cross dispersion position of background members be offset by no less than 0.375" would have the desired effect. For my tests, I used the YOFFSET keyword to determine the offset between the dithers, but this might not be a general solution as one doesn't want to impact the use of a dedicated background observation at a different pointing. This should perhaps be a tunable parameter in a reference file in case other instruments or setups would benefit from a similar restriction. Even if we eventually come up with an improved scheme for correcting for the flux changes resulting from nod overlap in general, not subtracting such closely spaced members from each other should still improve data quality as subtracting the overlap and then adding back in an approximate correction would add noise, and so this fix would remain in place.

A workaround is available, as the user can always edit the level 2 association files to exclude the nearest background members and then re-run spec2 and spec3, but a large fraction of the NIRSpec fixed slit calibration observations taken of standard stars use the S1600A1 with a 5 position dither and it is a bit awkward for the pipeline to give default reductions that are so far off in flux for standard stars.  If nothing else it has generated a number of help desk questions.

The rate files from Obs 160 and 107 of PID 1538 should provide good examples, as this star is bright enough that there is no significant real sky background.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/spacetelescope/jwst/pull/8744?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) Report
Attention: Patch coverage is `90.32258%` with `6 lines` in your changes missing coverage. Please review.
> Project coverage is 60.65%. Comparing base [(`e43af51`)](https://app.codecov.io/gh/spacetelescope/jwst/commit/e43af518b3a7035a1a8ea77b1201cd818db696a2?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) to head [(`279aa23`)](https://app.codecov.io/gh/spacetelescope/jwst/commit/279aa23dfca15038ae422a0ea31b03eb1dfcb596?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope).
> Report is 1 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/spacetelescope/jwst/pull/8744?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) | Patch % | Lines |
|---|---|---|
| [jwst/associations/lib/dms\_base.py](https://app.codecov.io/gh/spacetelescope/jwst/pull/8744?src=pr&el=tree&filepath=jwst%2Fassociations%2Flib%2Fdms_base.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope#diff-andzdC9hc3NvY2lhdGlvbnMvbGliL2Rtc19iYXNlLnB5) | 83.33% | [3 Missing :warning: ](https://app.codecov.io/gh/spacetelescope/jwst/pull/8744?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) |
| [jwst/associations/lib/rules\_level2\_base.py](https://app.codecov.io/gh/spacetelescope/jwst/pull/8744?src=pr&el=tree&filepath=jwst%2Fassociations%2Flib%2Frules_level2_base.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope#diff-andzdC9hc3NvY2lhdGlvbnMvbGliL3J1bGVzX2xldmVsMl9iYXNlLnB5) | 93.18% | [3 Missing :warning: ](https://app.codecov.io/gh/spacetelescope/jwst/pull/8744?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #8744      +/-   ##
==========================================
+ Coverage   60.64%   60.65%   +0.01%     
==========================================
  Files         372      372              
  Lines       38371    38389      +18     
==========================================
+ Hits        23269    23285      +16     
- Misses      15102    15104       +2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/spacetelescope/jwst/pull/8744?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spacetelescope).

melanieclarke: @hayescr - I think I have the logic right for subpixel dithers now.  I consolidated the S1600A1 overlap check with the existing check on primary dither position, so it's all in the same place now, and added some unit tests specifically for this condition.  Let me know if you think this is sufficient now.


melanieclarke: Regression tests started here:
https://github.com/spacetelescope/RegressionTests/actions/runs/10741232573
hayescr: @melanieclarke the changes look good to me!
melanieclarke: Report generation failed for the regression tests, but looking at the detailed output, I think this is the only real failure:
[test_against_standard[jw01678_20240721t195707_pool]](https://github.com/spacetelescope/RegressionTests/actions/runs/10741232573/job/29791261043#step:29:413)

This is the pool file I added to test these changes: the new association files for 5 point dithers with S1600A1 are subsets of the previous ones.
melanieclarke: Started regression tests on Jenkins here:
https://plwishmaster.stsci.edu:8081/job/RT/job/JWST-Developers-Pull-Requests/1716/

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 51fdfe145..d40d6fa86 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -36,6 +36,10 @@ associations
 
 - Remove ``MultilineLogger`` and no longer set it as the default logger. [#8781]
 
+- Excluded nearby background candidates from NIRSpec fixed slit associations
+  for S1600A1 with 5 point dithers, to reduce overlap between background nods
+  and science exposure. [#8744]
+  
 badpix_selfcal
 --------------
 
diff --git a/jwst/associations/lib/rules_level2_base.py b/jwst/associations/lib/rules_level2_base.py
index def32a8f2..a152e1048 100644
--- a/jwst/associations/lib/rules_level2_base.py
+++ b/jwst/associations/lib/rules_level2_base.py
@@ -26,7 +26,7 @@ from jwst.associations.lib.dms_base import (
     IMAGE2_NONSCIENCE_EXP_TYPES,
     IMAGE2_SCIENCE_EXP_TYPES,
     PRODUCT_NAME_DEFAULT,
-    SPEC2_SCIENCE_EXP_TYPES,
+    SPEC2_SCIENCE_EXP_TYPES
 )
 from jwst.associations.lib.member import Member
 from jwst.associations.lib.process_list import ListCategory
@@ -391,98 +391,6 @@ class DMSLevel2bBase(DMSBaseMixin, Association):
         # fail.
         return False
 
-    def make_nod_asns(self):
-        """Make background nod Associations
-
-        For observing modes, such as NIRSpec MSA, exposures can be
-        nodded, such that the object is in a different position in the
-        slitlet. The association creation simply groups these all
-        together as a single association, all exposures marked as
-        `science`. When complete, this method will create separate
-        associations each exposure becoming the single science
-        exposure, and the other exposures then become `background`.
-
-        Returns
-        -------
-        associations : [association[, ...]]
-            List of new associations to be used in place of
-            the current one.
-
-        """
-
-        for product in self['products']:
-            members = product['members']
-
-            # Split out the science vs. non-science
-            # The non-science exposures will get attached
-            # to every resulting association.
-            science_exps = [
-                member
-                for member in members
-                if member['exptype'] == 'science'
-            ]
-            nonscience_exps = [
-                member
-                for member in members
-                if member['exptype'] != 'science'
-            ]
-
-            # Create new associations for each science, using
-            # the other science as background.
-            results = []
-            for science_exp in science_exps:
-                asn = copy.deepcopy(self)
-                asn.data['products'] = None
-
-                product_name = remove_suffix(
-                    splitext(split(science_exp['expname'])[1])[0]
-                )[0]
-                asn.new_product(product_name)
-                new_members = asn.current_product['members']
-                new_members.append(science_exp)
-
-                for other_science in science_exps:
-                    if other_science['expname'] != science_exp['expname']:
-                        if science_exp.item['exp_type'] in ['nrs_fixedslit', 'nrs_msaspec']:
-                            try:
-                                sci_prim_dithpt = (int(science_exp.item['patt_num']) - 1) // \
-                                                  int(science_exp.item['subpxpts'])
-                                other_prim_dithpt = (int(other_science.item['patt_num']) - 1) // \
-                                                    int(other_science.item['subpxpts'])
-                                if sci_prim_dithpt != other_prim_dithpt:
-                                    now_background = Member(other_science)
-                                    now_background['exptype'] = 'background'
-                                    new_members.append(now_background)
-                            #  Catch missing values with KeyError, NULL values with ValueError
-                            except (ValueError, KeyError):
-                                try:
-                                    sci_prim_dithpt = (int(science_exp.item['patt_num']) - 1) // \
-                                                      int(science_exp.item['subpxpns'])
-                                    other_prim_dithpt = (int(other_science.item['patt_num']) - 1) // \
-                                                        int(other_science.item['subpxpns'])
-                                    if sci_prim_dithpt != other_prim_dithpt:
-                                        now_background = Member(other_science)
-                                        now_background['exptype'] = 'background'
-                                        new_members.append(now_background)
-                                except (ValueError, KeyError, ZeroDivisionError):
-                                    if science_exp.item['exp_type'] == 'nrs_msaspec':
-                                        now_background = Member(other_science)
-                                        now_background['exptype'] = 'background'
-                                        new_members.append(now_background)
-                                    else:
-                                        pass
-                        else:
-                            now_background = Member(other_science)
-                            now_background['exptype'] = 'background'
-                            new_members.append(now_background)
-
-                new_members += nonscience_exps
-
-                if asn.is_valid:
-                    results.append(asn)
-
-            return results
-
     def __repr__(self):
         try:
             file_name, json_repr = self.ioregistry['json'].dump(self)
@@ -1060,7 +968,7 @@ class AsnMixin_Lv2Image:
 class AsnMixin_Lv2Nod:
     """Associations that need to create nodding associations
 
-    For some spectragraphic modes, background spectra are taken by
+    For some spectrographic modes, background spectra are taken by
     nodding between different slits, or internal slit positions.
     The main associations rules will collect all the exposures of all the nods
     into a single association. Then, on finalization, this one association
@@ -1068,10 +976,173 @@ class AsnMixin_Lv2Nod:
     as science, and the other nods are treated as background.
     """
 
+    @staticmethod
+    def nod_background_overlap(science_item, background_item):
+        """
+        Check that a candidate background nod will not overlap with science.
+
+        For NIRSpec fixed slit or MOS data, this returns True if the
+        background candidate shares a primary dither point with the
+        science.
+
+        In addition, for NIRSpec fixed slit exposures taken with slit
+        S1600A1 in a 5-point nod pattern, this returns True when the
+        background candidate is in the next closest primary position
+        to the science.
+
+        For any other data, this function returns False (no overlap).
+        """
+        # Get exp_type, needed for any data:
+        # if not present, return False
+        try:
+            exptype = str(science_item['exp_type']).lower()
+        except KeyError:
+            return False
+
+        # Return False for any non-FS or MOS data
+        if exptype not in ['nrs_fixedslit', 'nrs_msaspec']:
+            return False
+
+        # Get pattern number values, needed for FS or MOS
+        try:
+            numdthpt = int(science_item['numdthpt'])
+            patt_num = int(science_item['patt_num'])
+            bkg_num = int(background_item['patt_num'])
+        except (KeyError, ValueError):
+            numdthpt = 0
+            patt_num = None
+            bkg_num = None
+
+        # Get subpxpts (or old alternate 'subpxpns')
+        try:
+            subpx = int(science_item['subpxpts'])
+        except (KeyError, ValueError):
+            try:
+                subpx = int(science_item['subpxpns'])
+            except (KeyError, ValueError):
+                subpx = None
+        try:
+            bkg_subpx = int(background_item['subpxpts'])
+        except (KeyError, ValueError):
+            try:
+                bkg_subpx = int(background_item['subpxpns'])
+            except (KeyError, ValueError):
+                bkg_subpx = None
+
+        # If values not found, return False for MOS, True for FS
+        if (None in [patt_num, bkg_num, subpx, bkg_subpx]
+                or subpx == 0 or bkg_subpx == 0):
+            if exptype == 'nrs_fixedslit':
+                # These values are required for background
+                # candidates for FS: report an overlap.
+                return True
+            else:
+                # For MOS, it's okay to go ahead and use them.
+                # Report no overlap.
+                return False
+
+        # Check for primary point overlap
+        sci_prim_dithpt = (patt_num - 1) // subpx
+        bkg_prim_dithpt = (bkg_num - 1) // bkg_subpx
+        if sci_prim_dithpt == bkg_prim_dithpt:
+            # Primary points are the same - overlap is present
+            return True
+
+        # Primary points are not the same -
+        # no further check needed for MOS
+        if exptype == 'nrs_msaspec':
+            return False
+
+        # Get slit name, needed only for FS S1600A1 check
+        try:
+            slit = str(science_item['fxd_slit']).lower()
+        except KeyError:
+            slit = None
+
+        # Background is currently only expected to overlap severely for
+        # 5 point dithers with fixed slit S1600A1.  Only the nearest
+        # dithers to the science observation need to be excluded.
+        if (exptype == 'nrs_fixedslit'
+                and slit == 's1600a1'
+                and numdthpt // subpx == 5
+                and abs(sci_prim_dithpt - bkg_prim_dithpt) <= 1):
+            return True
+
+        # For anything else, return False
+        return False
+
+    def make_nod_asns(self):
+        """Make background nod Associations
+
+        For observing modes, such as NIRSpec MSA, exposures can be
+        nodded, such that the object is in a different position in the
+        slitlet. The association creation simply groups these all
+        together as a single association, all exposures marked as
+        `science`. When complete, this method will create separate
+        associations each exposure becoming the single science
+        exposure, and the other exposures then become `background`.
+
+        Returns
+        -------
+        associations : [association[, ...]]
+            List of new associations to be used in place of
+            the current one.
+
+        """
+
+        for product in self['products']:
+            members = product['members']
+
+            # Split out the science vs. non-science
+            # The non-science exposures will get attached
+            # to every resulting association.
+            science_exps = [
+                member
+                for member in members
+                if member['exptype'] == 'science'
+            ]
+            nonscience_exps = [
+                member
+                for member in members
+                if member['exptype'] != 'science'
+            ]
+
+            # Create new associations for each science, using
+            # the other science as background.
+            results = []
+            for science_exp in science_exps:
+                asn = copy.deepcopy(self)
+                asn.data['products'] = None
+
+                product_name = remove_suffix(
+                    splitext(split(science_exp['expname'])[1])[0]
+                )[0]
+                asn.new_product(product_name)
+                new_members = asn.current_product['members']
+                new_members.append(science_exp)
+
+                for other_science in science_exps:
+                    if other_science['expname'] != science_exp['expname']:
+                        # check for likely overlap between science
+                        # and background candidate
+                        overlap = self.nod_background_overlap(
+                            science_exp.item, other_science.item)
+                        if not overlap:
+                            now_background = Member(other_science)
+                            now_background['exptype'] = 'background'
+                            new_members.append(now_background)
+
+                new_members += nonscience_exps
+
+                if asn.is_valid:
+                    results.append(asn)
+
+            return results
+
     def finalize(self):
         """Finalize association
 
-        For some spectragraphic modes, background spectra and taken by
+        For some spectrographic modes, background spectra are taken by
         nodding between different slits, or internal slit positions.
         The main associations rules will collect all the exposures of all the nods
         into a single association. Then, on finalization, this one association


Test patch:
diff --git a/jwst/associations/tests/data/pool_024b_nirspec_fss_nods.csv b/jwst/associations/tests/data/pool_024b_nirspec_fss_nods.csv
new file mode 100755
index 000000000..805a66a98
--- /dev/null
+++ b/jwst/associations/tests/data/pool_024b_nirspec_fss_nods.csv
@@ -0,0 +1,20 @@
+# NIRSpec Fixed-slit with various nods and dithers
+#
+# This example contains a 5 point dither for S1600A1, no subpixel dither
+#
+FILENAME|OBS_ID|PROGRAM|OBS_NUM|VISIT|VISIT_ID|VISITGRP|VISITYPE|SEQ_ID|ACT_ID|EXPOSURE|EXP_TYPE|NEXPOSUR|EXPCOUNT|INSTRUME|DETECTOR|CHANNEL|TARGETID|TARGPROP|TARGNAME|TARGTYPE|TEMPLATE|PNTGTYPE|PNTG_SEQ|TARGORDN|EXPSPCIN|DITHPTIN|MOSTILNO|MODULE|FILTER|PUPIL|DITHERID|PATTTYPE|PATTSTRT|NUMDTHPT|PATTSIZE|SUBPXPNS|PATT_NUM|SUBPXNUM|SUBPIXEL|APERNAME|SDP_VER|SUBARRAY|GRATING|FXD_SLIT|BAND|GWA_XTIL|GWA_YTIL|ASN_CANDIDATE|EXPOSERR|IS_PSF|IS_IMPRT|BKGDTARG|TSOVISIT|MSAMETFL|SPAT_NUM|SPEC_NUM
+#
+# Initialize ACID. This is done with a dummy exposure that will not match any association
+set acid|OBS_ID|PROGRAM|OBS_NUM|VISIT|VISIT_ID|VISITGRP|VISITYPE|SEQ_ID|ACT_ID|EXPOSURE|EXP_TYPE|NEXPOSUR|EXPCOUNT|INSTRUME|DETECTOR|CHANNEL|TARGETID|TARGPROP|TARGNAME|TARGTYPE|TEMPLATE|PNTGTYPE|PNTG_SEQ|TARGORDN|EXPSPCIN|DITHPTIN|MOSTILNO|MODULE|FILTER|PUPIL|DITHERID|PATTTYPE|PATTSTRT|NUMDTHPT|PATTSIZE|SUBPXPNS|PATT_NUM|SUBPXNUM|SUBPIXEL|APERNAME|SDP_VER|SUBARRAY|GRATING|FXD_SLIT|BAND|GWA_XTIL|GWA_YTIL|@!next(acid)|EXPOSERR|IS_PSF|IS_IMPRT|BKGDTARG|TSOVISIT|MSAMETFL|SPAT_NUM|SPEC_NUM
+#
+# Primary Dither=5, Sub-pixel Pattern=None, F290LP/G395H/S1600A1
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!next(obsnum)|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|1|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|1|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|2|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|2|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|3|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|3|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|4|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|4|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|5|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|5|NULL|1|5|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|NULL
diff --git a/jwst/associations/tests/data/pool_024c_nirspec_fss_nods.csv b/jwst/associations/tests/data/pool_024c_nirspec_fss_nods.csv
new file mode 100755
index 000000000..67dfcb8cd
--- /dev/null
+++ b/jwst/associations/tests/data/pool_024c_nirspec_fss_nods.csv
@@ -0,0 +1,50 @@
+# NIRSpec Fixed-slit with various nods and dithers
+#
+# This example contains a 5 point dither for S1600A1, subpixel pattern both
+#
+FILENAME|OBS_ID|PROGRAM|OBS_NUM|VISIT|VISIT_ID|VISITGRP|VISITYPE|SEQ_ID|ACT_ID|EXPOSURE|EXP_TYPE|NEXPOSUR|EXPCOUNT|INSTRUME|DETECTOR|CHANNEL|TARGETID|TARGPROP|TARGNAME|TARGTYPE|TEMPLATE|PNTGTYPE|PNTG_SEQ|TARGORDN|EXPSPCIN|DITHPTIN|MOSTILNO|MODULE|FILTER|PUPIL|DITHERID|PATTTYPE|PATTSTRT|NUMDTHPT|PATTSIZE|SUBPXPNS|PATT_NUM|SUBPXNUM|SUBPIXEL|APERNAME|SDP_VER|SUBARRAY|GRATING|FXD_SLIT|BAND|GWA_XTIL|GWA_YTIL|ASN_CANDIDATE|EXPOSERR|IS_PSF|IS_IMPRT|BKGDTARG|TSOVISIT|MSAMETFL|SPAT_NUM|SPEC_NUM
+#
+# Initialize ACID. This is done with a dummy exposure that will not match any association
+set acid|OBS_ID|PROGRAM|OBS_NUM|VISIT|VISIT_ID|VISITGRP|VISITYPE|SEQ_ID|ACT_ID|EXPOSURE|EXP_TYPE|NEXPOSUR|EXPCOUNT|INSTRUME|DETECTOR|CHANNEL|TARGETID|TARGPROP|TARGNAME|TARGTYPE|TEMPLATE|PNTGTYPE|PNTG_SEQ|TARGORDN|EXPSPCIN|DITHPTIN|MOSTILNO|MODULE|FILTER|PUPIL|DITHERID|PATTTYPE|PATTSTRT|NUMDTHPT|PATTSIZE|SUBPXPNS|PATT_NUM|SUBPXNUM|SUBPIXEL|APERNAME|SDP_VER|SUBARRAY|GRATING|FXD_SLIT|BAND|GWA_XTIL|GWA_YTIL|@!next(acid)|EXPOSERR|IS_PSF|IS_IMPRT|BKGDTARG|TSOVISIT|MSAMETFL|SPAT_NUM|SPEC_NUM
+#
+# Primary Dither=5, Sub-pixel Pattern=Both, F290LP/G395H/S1600A1
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!next(obsnum)|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|1|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|1
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|1|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|1
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|2|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|2
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|2|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|2
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|3|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|3
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|3|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|3
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|4|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|4|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|1|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|5|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|1
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|5|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|1
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|6|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|2
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|6|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|2
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|7|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|3
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|7|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|3
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|8|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|8|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|9|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|9|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|10|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|10|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|11|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|11|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|12|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|12|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|13|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|13|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|14|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|14|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|15|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|15|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|16|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|16|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|17|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|17|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|18|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|18|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|19|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|19|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS1|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|20|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
+@!fmt_fname(next(expnum))|V93125002001P0000000002106|93125|@!obsnum.value|1|93125002001|2|PRIME_TARGETED_FIXED|1|06|1|NRS_FIXEDSLIT|6|6|NIRSPEC|NRS2|NULL|1|TARGET-OBSERVATION-2|NULL|FIXED|NIRSpec Fixed-slit Spectroscopy|SCIENCE|6|1|1|1|1|NULL|F290LP|NULL|NULL|NULL|NULL|20|NULL|4|20|NULL|NULL|S200A1|2017_1|SUB200A1|G395H|S1600A1|NULL|0.2866025270000001|0.17855911|@!fmt_cand([(obsnum.value, 'OBSERVATION')])|NULL|NULL|F|F|F|F|2|4
diff --git a/jwst/associations/tests/test_level2_background.py b/jwst/associations/tests/test_level2_background.py
index 4af3eb868..8fb7ed2e1 100644
--- a/jwst/associations/tests/test_level2_background.py
+++ b/jwst/associations/tests/test_level2_background.py
@@ -1,4 +1,6 @@
 """Test Level2 background nods"""
+import pytest
+
 from jwst.associations.tests.helpers import (
     combine_pools,
     registry_level2_only,
@@ -15,6 +17,7 @@ DITHER_PATTERN_MULTIPLIER = {
     '4': 4,  # Both, 4-to-1 exposure count
 }
 
+
 def test_nrs_msa_nod():
     pool = combine_pools(t_path('data/pool_023_nirspec_msa_3nod.csv'))
     all_candidates = constrain_on_candidates(None)
@@ -81,3 +84,41 @@ def test_nrs_fixedslit_nod_chop():
             if asn['asn_id'].startswith('c'):
                 nods += 1
             assert len(asn['products'][0]['members']) == nods
+
+
+@pytest.mark.parametrize('pool_name,n_asn',
+                         [('pool_024b_nirspec_fss_nods', 10),
+                          ('pool_024c_nirspec_fss_nods', 40)])
+def test_nrs_fixedslit_5point(pool_name, n_asn):
+    """Test NIRSpec Fixed-slit background nod S1600A1 5 point + subpixel"""
+    pool = combine_pools(t_path(f'data/{pool_name}.csv'))
+    constraint_all_candidates = constrain_on_candidates(None)
+    asns = generate(pool, registry_level2_only(
+        global_constraints=constraint_all_candidates)
+    )
+    assert len(asns) == n_asn
+    for asn in asns:
+        n_dithers = int(asn.constraints['nods'].value)
+        n_spectral_dithers = int(asn.constraints['subpxpts'].value)
+
+        sci_expnames = []
+        for member in asn['products'][0]['members']:
+            if member['exptype'] == 'science':
+                sci_expnames.append(member['expname'])
+        assert len(sci_expnames) == 1
+
+        # Expect self + all exposures not at the same primary dither.
+        # Also expect nearest 2 dithers to be excluded,
+        # or 1 if it's the first or last primary dither.
+        first_last_files = list(range(1, n_spectral_dithers * 2 + 1))
+        first_last_files += list(range(n_asn,
+                                       n_asn - n_spectral_dithers * 2, -1))
+        first_last = [f'jw_000{i:02d}_rate.fits' for i in first_last_files]
+
+        if sci_expnames[0] in first_last:
+            n_extra = 1
+        else:
+            n_extra = 2
+        n_members = n_dithers - (1 + n_extra) * n_spectral_dithers + 1
+
+        assert len(asn['products'][0]['members']) == n_members
diff --git a/jwst/regtest/test_associations_sdp_pools.py b/jwst/regtest/test_associations_sdp_pools.py
index 4444e40a3..3c63554fd 100644
--- a/jwst/regtest/test_associations_sdp_pools.py
+++ b/jwst/regtest/test_associations_sdp_pools.py
@@ -132,6 +132,11 @@ SPECIAL_POOLS = {
         'xfail': None,
         'slow': False,
     },
+    'jw01678_20240721t195707_pool': {
+        'args': [],
+        'xfail': None,
+        'slow': False,
+    },
     'jw02064_20230302t112350_pool': {
         'args': [],
         'xfail': None,