Repository: kratsg/itksn

Issue:
flex pcb 1st classification digit update
![Screenshot 2024-11-04 at 1 16 37 PM](https://github.com/user-attachments/assets/73e639d5-fc10-4587-a617-d6746eaf51c3)

Update SN definitions for local supports
https://indico.cern.ch/event/1481888/contributions/6244101/attachments/2974222/5235081/Inner_System_PDB_update_25_11_2024-3.pdf

Discussion:

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e460580..bc17fed 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -78,7 +78,7 @@ jobs:
         with:
           path: dist
 
-      - uses: pypa/gh-action-pypi-publish@v1.10.2
+      - uses: pypa/gh-action-pypi-publish@v1.12.2
         if: github.event_name == 'release' && github.event.action == 'published'
         with:
           user: __token__
diff --git a/README.md b/README.md
index d969052..516d473 100644
--- a/README.md
+++ b/README.md
@@ -67,12 +67,12 @@ Container:
         manufacturer = b'9' (total 1)
         number = b'1234' (total 4)
 
-$ itksn parse 20UPGR40012345
+$ itksn parse 20UPGR90012345
 Container:
     atlas_project = (enum) atlas_detector b'20'
     system_code = (enum) phaseII_upgrade b'U'
     project_code = (enum) pixel_general b'PG'
-    subproject_code = (enum) Digital_quad_module b'R4'
+    subproject_code = (enum) Digital_quad_module b'R9'
     identifier = Container:
         FE_chip_version = (enum) RD53A b'0'
         reserved = b'0' (total 1)
@@ -118,6 +118,39 @@ obj = {
 itksn.core.SerialNumberStruct.build(obj)  # b'20UPGFC0131224'
 ```
 
+or even from python, one can parse
+
+```python
+import itksn
+
+results = itksn.parse(b"20UPGR90012345")
+assert results.atlas_project == "atlas_detector"
+assert results.project_code == "pixel"
+assert results.identifier.PCB_manufacturer == "Dummy"
+```
+
+or build
+
+```python
+from itksn.core import SerialNumberStruct
+
+sn = SerialNumberStruct.build(
+    {
+        "atlas_project": "atlas_detector",
+        "system_code": "phaseII_upgrade",
+        "project_code": "pixel",
+        "subproject_code": "pixel_general",
+        "component_code": "Digital_quad_module",
+        "identifier": {
+            "FE_chip_version": "RD53A",
+            "PCB_manufacturer": "Dummy",
+            "number": b"12345",
+        },
+    }
+)
+assert sn == b"20UPGR90012345"
+```
+
 ## Installation
 
 In a fresh virtual environment, you can install from `PyPI`:
diff --git a/pyproject.toml b/pyproject.toml
index ee7cc75..88d5d8b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -98,7 +98,7 @@ dependencies = [
 ]
 
 [tool.hatch.envs.dev.scripts]
-test = "pytest -ra"
+test = "pytest -ra {args}"
 
 [[tool.hatch.envs.dev.matrix]]
 python = ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.8"]
@@ -167,40 +167,36 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
 warn_unreachable = true
 
 [tool.ruff]
+target-version = "py38"
 src = ["src"]
+exclude = []
 
 [tool.ruff.lint]
-extend-select = [
-  "B",        # flake8-bugbear
-  "I",        # isort
-  "ARG",      # flake8-unused-arguments
-  "C4",       # flake8-comprehensions
-  "EM",       # flake8-errmsg
-  "ICN",      # flake8-import-conventions
-  "G",        # flake8-logging-format
-  "PGH",      # pygrep-hooks
-  "PIE",      # flake8-pie
-  "PL",       # pylint
-  "PT",       # flake8-pytest-style
-  "PTH",      # flake8-use-pathlib
-  "RET",      # flake8-return
-  "RUF",      # Ruff-specific
-  "SIM",      # flake8-simplify
-  "T20",      # flake8-print
-  "UP",       # pyupgrade
-  "YTT",      # flake8-2020
-  "EXE",      # flake8-executable
-  "NPY",      # NumPy specific rules
-  "PD",       # pandas-vet
+select = [
+  "E", "F", "W", # flake8
+  "B",  "B904",  # flake8-bugbear
+  "I",           # isort
+  "ARG",         # flake8-unused-arguments
+  "C4",          # flake8-comprehensions
+  "EM",          # flake8-errmsg
+  "ICN",         # flake8-import-conventions
+  "ISC",         # flake8-implicit-str-concat
+  "PGH",         # pygrep-hooks
+  "PIE",         # flake8-pie
+  "PL",          # pylint
+  "PT",          # flake8-pytest-style
+  "PTH",         # flake8-use-pathlib
+  "RET",         # flake8-return
+  "RUF",         # Ruff-specific
+  "SIM",         # flake8-simplify
+  "T20",         # flake8-print
+  "UP",          # pyupgrade
+  "YTT",         # flake8-2020
 ]
-ignore = [
-  "PLR09",    # Too many <...>
-  "PLR2004",  # Magic value used in comparison
-  "ISC001",   # Conflicts with formatter
-]
-isort.required-imports = ["from __future__ import annotations"]
-# Uncomment if using a _compat.typing backport
+ignore = ["PLR", "E501", "ISC001"]
 typing-modules = ["itksn.typing"]
+unfixable = ["T20", "F841"]
+isort.required-imports = ["from __future__ import annotations"]
 
 [tool.ruff.lint.per-file-ignores]
 "tests/**" = ["T20"]
diff --git a/src/itksn/core.py b/src/itksn/core.py
index b6e5f3e..e5132c9 100644
--- a/src/itksn/core.py
+++ b/src/itksn/core.py
@@ -21,34 +21,54 @@ SerialNumberStruct = "SerialNumber" / Struct(
     ),
     "project_code"
     / EnumStr(
-        Bytes(2),
-        inner_pixel=b"PI",
-        outer_pixel_barrel=b"PB",
-        pixel_general=b"PG",
-        pixel_endcaps=b"PE",
-        strip_general=b"SG",
-        strip_barrel=b"SB",
-        strip_endcaps=b"SE",
-        common_mechanics=b"CM",
-        common_electronics=b"CE",
+        Bytes(1),
+        pixel=b"P",
+        strip=b"S",
+        common=b"C",
     ),
     "subproject_code"
     / Switch(
-        this.project_code,
+        lambda ctx: ctx.project_code,
+        {
+            "pixel": EnumStr(
+                Bytes(1),
+                inner_pixel=b"I",
+                outer_pixel_barrel=b"B",
+                pixel_general=b"G",
+                pixel_endcaps=b"E",
+            ),
+            "strip": EnumStr(
+                Bytes(1),
+                strip_general=b"G",
+                strip_barrel=b"B",
+                strip_endcaps=b"E",
+            ),
+            "common": EnumStr(
+                Bytes(1),
+                common_mechanics=b"CM",
+                common_electronics=b"CE",
+            ),
+        },
+    ),
+    "component_code"
+    / Switch(
+        this.subproject_code,
         {
             "inner_pixel": pixels.subproject_codes["PI"],
             "outer_pixel_barrel": pixels.subproject_codes["PB"],
             "pixel_general": pixels.subproject_codes["PG"],
+            "pixel_endcaps": pixels.subproject_codes["PE"],
         },
         default=PaddedString(2, "utf8"),
     ),
     "identifier"
     / Switch(
-        this.project_code,
+        this.subproject_code,
         {
             "inner_pixel": pixels.identifiers,
             "outer_pixel_barrel": pixels.identifiers,
             "pixel_general": pixels.identifiers,
+            "pixel_endcaps": pixels.identifiers,
         },
         default=Bytes(7),
     ),
diff --git a/src/itksn/pixels/__init__.py b/src/itksn/pixels/__init__.py
new file mode 100644
index 0000000..b54463e
--- /dev/null
+++ b/src/itksn/pixels/__init__.py
@@ -0,0 +1,327 @@
+from __future__ import annotations
+
+from construct import (
+    Bytes,
+    Error,
+    Switch,
+)
+
+from itksn.common import EnumStr
+from itksn.pixels import local_supports, modules, services, utils
+
+yy_identifiers = {
+    **modules.yy_identifiers,
+    **services.yy_identifiers,
+    **local_supports.yy_identifiers,
+}
+
+subproject_codes = {
+    subproject_code: EnumStr(
+        Bytes(2),
+        **{
+            k: v.encode("utf-8")
+            for k, (v, *allowed_subprojects) in yy_identifiers.items()
+            if subproject_code in allowed_subprojects
+        },
+    )
+    for subproject_code in ["PI", "PG", "PB", "PE"]
+}
+
+identifiers = Switch(
+    lambda ctx: ctx.component_code,
+    {
+        "FE_chip_wafer": modules.fe_chip,
+        "FE_chip": modules.fe_chip,
+        "Planar_sensor_wafer_100um_thickness": modules.sensor,
+        "Planar_sensor_wafer_150um_thickness": modules.sensor,
+        "ThreeD_sensor_wafer": modules.sensor,
+        "L0_inner_pixel_3D_sensor_wafer_25x100um": modules.sensor,
+        "L0_inner_pixel_3D_sensor_wafer_50x50um": modules.sensor,
+        "L1_inner_pixel_sensor_wafer_100um_thickness": modules.sensor,
+        "Outer_pixel_sensor_wafer_150um_thickness": modules.sensor,
+        "Half_size_planar_sensor_tile_100um_thickness": modules.sensor,
+        "Half_size_planar_sensor_tile_150um_thickness": modules.sensor,
+        "Full_size_planar_sensor_tile_100um_thickness": modules.sensor,
+        "Full_size_planar_sensor_tile_150um_thickness": modules.sensor,
+        "Half_size_3D_sensor_tile_25x100um": modules.sensor,
+        "Full_size_3D_sensor_tile_25x100um": modules.sensor,
+        "Half_size_3D_sensor_tile_50x50um": modules.sensor,
+        "Full_size_3D_sensor_tile_50x50um": modules.sensor,
+        "L0_inner_pixel_3D_sensor_tile_25x100um": modules.sensor,
+        "L0_inner_pixel_3D_sensor_tile_50x50um": modules.sensor,
+        "L1_inner_pixel_quad_sensor_tile": modules.sensor,
+        "Outer_pixel_quad_sensor_tile": modules.sensor,
+        "Planar_Sensor_test_structure_100um_thickness": modules.sensor,
+        "Planar_Sensor_test_structure_150um_thickness": modules.sensor,
+        "ThreeD_Sensor_test_structure_25x100um": modules.sensor,
+        "ThreeD_Sensor_test_structure_50x50um": modules.sensor,
+        "Planar_Sensor_half_moon_100um_thickness": modules.sensor,
+        "Planar_Sensor_half_moon_150um_thickness": modules.sensor,
+        "ThreeD_Sensor_half_moon_25x100um": modules.sensor,
+        "ThreeD_Sensor_half_moon_50x50um": modules.sensor,
+        "Single_bare_module": modules.bare_module,
+        "Dual_bare_module": modules.bare_module,
+        "Quad_bare_module": modules.bare_module,
+        "Digital_single_bare_module": modules.bare_module,
+        "Digital_quad_bare_module": modules.bare_module,
+        "FourInch_bare_module_gel_pack": Error,
+        "SixInch_bare_module_gel_pack": Error,
+        "Triplet_L0_Stave_PCB": modules.pcb_triplets,
+        "Triplet_L0_R0_PCB": modules.pcb_triplets,
+        "Triplet_L0_R0p5_PCB": modules.pcb_triplets,
+        "Quad_PCB": modules.pcb,
+        "Dual_PCB": modules.pcb,
+        "PCB_test_coupon": modules.pcb,
+        "OB_wirebond_protection_roof": Error,
+        "Triplet_L0_stave_module": modules.triplet_module,
+        "Triplet_L0_Ring0_module": modules.triplet_module,
+        "Triplet_L0_Ring0p5_module": modules.triplet_module,
+        "L1_quad_module": modules.module,
+        "Outer_system_quad_module": modules.module,
+        "Dual_chip_module": modules.module,
+        "Single_chip_module": modules.module,
+        "Digital_triplet_L0_stave_module": modules.triplet_module,
+        "Digital_triplet_L0_ring0_module": modules.triplet_module,
+        "Digital_triplet_L0_ring0p5_module": modules.triplet_module,
+        "Digital_quad_module": modules.module,
+        "Digital_L1_quad_module": modules.module,
+        "Dummy_triplet_L0_stave_module": modules.triplet_module,
+        "Dummy_triplet_L0_ring0_module": modules.triplet_module,
+        "Dummy_triplet_L0_ring0p5_module": modules.triplet_module,
+        "Dummy_quad_module": modules.module,
+        "Dummy_L1_quad_module": modules.module,
+        "Module_carrier": modules.module_carrier,
+        # dummy for testing of GUIs/tutorials
+        "Dummy_FE_chip_wafer": modules.fe_chip,
+        "Dummy_tutorial_FE_chip": modules.fe_chip,
+        "Dummy_sensor_wafer": modules.sensor,
+        "Dummy_tutorial_sensor_tile": modules.sensor,
+        "Dummy_sensor_test_structure": modules.sensor,
+        "Dummy_sensor_half_moon": modules.sensor,
+        "Dummy_single_bare_module": modules.bare_module,
+        "Dummy_quad_bare_module": modules.bare_module,
+        "Dummy_tutorial_bare_module": modules.bare_module,
+        "Dummy_bare_module_gel_pack": Error,
+        "Dummy_tutorial_PCB": modules.pcb,
+        "Dummy_PCB_test_coupon": modules.pcb,
+        "Dummy_OB_wirebond_protection_roof": Error,
+        "Dummy_tutorial_module": modules.module,
+        "Dummy_module_carrier": modules.module_carrier,
+        # local supports
+        "IS_capillary": local_supports.local_supports_is,
+        "IS_end_tube": local_supports.local_supports_is,
+        "IS_cooling_tube": local_supports.local_supports_is,
+        "IS_bare_local_support_stave": local_supports.local_supports_is,
+        "IS_bare_local_support_ring": local_supports.local_supports_is,
+        "IS_ring_local_support_assembly_loaded_ring": local_supports.local_supports,
+        "IS_barrel_stave_assembly_loaded_stave": local_supports.local_supports,
+        "OB_Base_Block": local_supports.local_supports,
+        "OB_Cooling_Block": local_supports.local_supports,
+        "OB_TPG_Tile": local_supports.local_supports,
+        "OB_Local_Support_Inserts": local_supports.local_supports,
+        "OB_Gusset": local_supports.local_supports,
+        "OB_Truss": local_supports.local_supports,
+        "OB_Half_Ring_Shell": local_supports.local_supports,
+        "OB_End_of_longeron_Bracket_End_Gusset": local_supports.local_supports,
+        "OB_Pipe_Support": local_supports.local_supports,
+        "OB_Evaporator_Sleeves": local_supports.local_supports,
+        "OB_Cooling_Pipe_IHR": local_supports.local_supports,
+        "OB_Cooling_Pipe_Longeron": local_supports.local_supports,
+        "OB_Functional_Pipe_for_IHR": local_supports.local_supports,
+        "OB_Functional_Pipe_for_Longeron": local_supports.local_supports,
+        "OB_End_of_longeron_Support": local_supports.local_supports,
+        "OB_Bare_Module_Cell": local_supports.local_supports,
+        "OB_Functional_IHR": local_supports.local_supports_ihr,
+        "OB_Functional_Longeron": local_supports.local_supports_longeron,
+        "OB_Loaded_Module_Cell": local_supports.loaded_local_supports_ob_module,
+        "OB_Loaded_IHR": local_supports.local_supports,
+        "OB_Loaded_Longeron": local_supports.local_supports,
+        "OB_IHR_Handling_Frame": local_supports.local_supports,
+        "OB_Longeron_Handling_Frame": local_supports.local_supports,
+        "OB_Bare_Cell_Transport_Box": local_supports.local_supports,
+        "OEC_Trapezoids": local_supports.local_supports,
+        "OEC_Electrical break": local_supports.local_supports,
+        "OEC_Inner_Rim_Insert": local_supports.local_supports,
+        "OEC_Outer_Rim_mounting_lugs": local_supports.local_supports,
+        "OEC_Inner_Rim_Closeout": local_supports.local_supports,
+        "OEC_Outer_Rim_Closeout": local_supports.local_supports,
+        "OEC_Pipe_Closeout_support_closeout": local_supports.local_supports,
+        "OEC_Half_Sandwich": local_supports.local_supports,
+        "OEC_Evaporator": local_supports.local_supports,
+        "OEC_Bare_half_ring_assembly_Bare_support": local_supports.local_supports,
+        "OEC_Loaded_local_support_loaded_support": local_supports.local_supports,
+        "OEC_Handling_frame_support_frame": local_supports.local_supports,
+        "OEC_Transport/storage_box_support_box": local_supports.local_supports,
+        "Local_support_handling_frame_box": local_supports.local_supports_frame_box,
+        "High_voltage_group": local_supports.local_supports,
+        "Serial_powering_scheme": local_supports.local_supports,
+        # dummy local supports
+        "Dummy_IS_capillary": local_supports.local_supports_is,
+        "Dummy_IS_end_tube": local_supports.local_supports_is,
+        "Dummy_IS_cooling_tube": local_supports.local_supports_is,
+        "Dummy_IS_bare_local_support_stave": local_supports.local_supports_is,
+        "Dummy_IS_bare_local_support_ring": local_supports.local_supports_is,
+        "Dummy_IS_ring_local_support_assembly_loaded_ring": local_supports.local_supports,
+        "Dummy_IS_barrel_stave_assembly_loaded_stave": local_supports.local_supports,
+        "Dummy_OB_Base_Block": local_supports.local_supports,
+        "Dummy_OB_Cooling_Block": local_supports.local_supports,
+        "Dummy_OB_TPG_Tile": local_supports.local_supports,
+        "Dummy_OB_Local_Support_Inserts": local_supports.local_supports,
+        "Dummy_OB_Gusset": local_supports.local_supports,
+        "Dummy_OB_Truss": local_supports.local_supports,
+        "Dummy_OB_Half_Ring_Shell": local_supports.local_supports,
+        "Dummy_OB_End_of_longeron_Bracket_End_Gusset": local_supports.local_supports,
+        "Dummy_OB_Pipe_Support": local_supports.local_supports,
+        "Dummy_OB_Evaporator_Sleeves": local_supports.local_supports,
+        "Dummy_OB_Cooling_Pipe_IHR": local_supports.local_supports,
+        "Dummy_OB_Cooling_Pipe_Longeron": local_supports.local_supports,
+        "Dummy_OB_Functional_Pipe_for_IHR": local_supports.local_supports,
+        "Dummy_OB_Functional_Pipe_for_Longeron": local_supports.local_supports,
+        "Dummy_OB_End_of_longeron_Support": local_supports.local_supports,
+        "Dummy_OB_Bare_Module_Cell": local_supports.local_supports,
+        "Dummy_OB_Functional_IHR": local_supports.local_supports_ihr,
+        "Dummy_OB_Functional_Longeron": local_supports.local_supports_longeron,
+        "Dummy_OB_Loaded_Module_Cell": local_supports.loaded_local_supports_ob_module,
+        "Dummy_OB_Loaded_IHR": local_supports.local_supports,
+        "Dummy_OB_Loaded_Longeron": local_supports.local_supports,
+        "Dummy_OB_IHR_Handling_Frame": local_supports.local_supports,
+        "Dummy_OB_Longeron_Handling_Frame": local_supports.local_supports,
+        "Dummy_OB_Bare_Cell_Transport_Box": local_supports.local_supports,
+        "Dummy_OEC_Trapezoids": local_supports.local_supports,
+        "Dummy_OEC_Electrical break": local_supports.local_supports,
+        "Dummy_OEC_Inner_Rim_Insert": local_supports.local_supports,
+        "Dummy_OEC_Outer_Rim_mounting_lugs": local_supports.local_supports,
+        "Dummy_OEC_Inner_Rim_Closeout": local_supports.local_supports,
+        "Dummy_OEC_Outer_Rim_Closeout": local_supports.local_supports,
+        "Dummy_OEC_Pipe_Closeout_support_closeout": local_supports.local_supports,
+        "Dummy_OEC_Half_Sandwich": local_supports.local_supports,
+        "Dummy_OEC_Evaporator": local_supports.local_supports,
+        "Dummy_OEC_Bare_half_ring_assembly_Bare_support": local_supports.local_supports,
+        "Dummy_OEC_Loaded_local_support_loaded_support": local_supports.local_supports,
+        "Dummy_OEC_Handling_frame_support_frame": local_supports.local_supports,
+        "Dummy_OEC_Transport/storage_box_support_box": local_supports.local_supports,
+        "Dummy_Local_support_handling_frame_box": local_supports.local_supports_frame_box,
+        "Dummy_High_voltage_group": local_supports.local_supports,
+        "Dummy_Serial_powering_scheme": local_supports.local_supports,
+        # services
+        "Optoboard": services.optoboard,
+        "Optoboard_termination_board": services.termination_board,  # OB Type-1 Termination Board (FIXME: only PG, no PB)
+        "Optobox": services.optobox,
+        "Optobox_powerbox": services.optobox,
+        "Optobox_connector_board": services.optobox_powerboard_connector,
+        "Optobox_optical_fan_out": services.optobox,
+        "Optopanel": Error,  # FIXME
+        "Optopanel_cooling_plate": Error,  # FIXME
+        "Optobox_powerboard": services.optobox_powerboard_connector,
+        "LpGBT_chip": Error,  # FIXME
+        "GBCR_chip": Error,  # FIXME
+        "Vtrx_module": Error,  # FIXME
+        "Bpol2V5_chip": Error,  # FIXME
+        "Bpol2V5_carrier_board": Error,  # FIXME
+        "Bpol12V_chip": Error,  # FIXME
+        "MOPS_chip": services.mops_chip,  # FIXME: phrasing in the document is awful
+        "Power_cables": Error,  # FIXME
+        "CAN_bus_cable": services.canbus,
+        # Type-0 and Type-1 cables below
+        "Pigtail": utils.subproject_switch(
+            pi=services.is_type0_cable, pb=services.pb_type0_cable
+        ),  # IS Type-0  (FIXME: no PI supported)
+        "Rigid_flex": utils.subproject_switch(
+            pi=services.is_type0_cable, pb=services.pb_type0_pp0
+        ),  # IS Type-0
+        "Data_PP0": utils.subproject_switch(
+            pi=services.is_type0_cable, pe=services.pe_type0_data
+        ),  # IS Type-0  # OB Type-1 Inclined PCB??? (FIXME: not PB)
+        "Power_pigtail": utils.subproject_switch(
+            pi=services.is_type0_cable, pe=services.pe_type0_power
+        ),  # IS Type-0  # OE Type-0
+        "Power_bustape": utils.subproject_switch(
+            pe=services.pe_type0_power
+        ),  # FIXME: only PE needed, not PI/PB
+        "Bare_bustape": Error,  # FIXME: not used?
+        "Pigtail_panel": services.pb_type0_cable,
+        "PP0": utils.subproject_switch(pi=services.pi_type0_pp0),  # IS only
+        "Finger": Error,  # FIXME: not used/defined?
+        "Type_1_Data_link ": utils.subproject_switch(
+            pb=services.pb_type1_data
+        ),  # IS Type-1, (FIXME: only PB/PI needed, not PE)
+        "Type_1_Power_DCS_line": utils.subproject_switch(
+            pe=services.pe_type1,
+            pb=services.pb_type1_power,
+            pi=services.is_type1_cable,
+        ),  # IS Type-1, (FIXME: only PB/PI needed, not PE)
+        "Environmental_link": Error,  # Type-0 and Type-1 cable (FIXME: not defined?)
+        "PP1_connector": utils.subproject_switch(
+            pi=services.pp1, pe=services.pe_type1
+        ),  # FIXME: not defined well)
+        "PP1_connector_pieces_segments": utils.subproject_switch(),  # FIXME: not defined well)
+        # End Type-0 and Type-1
+        "Strain_relief": Error,
+        "Type_2_power_cable": services.type2,
+        "Type_2_optobox_cable": services.type2,
+        "PP2_box": services.type2,
+        "Type_3_HV_cable": services.type3,
+        "Type_3_LV_cable": services.type3,
+        "MOPS_cable": services.type3,
+        "TiLock": services.type3,
+        "OPTO_MOPS": services.type3,
+        "PP3_power": services.type4,
+        # Dummy services
+        "Dummy_Optoboard": services.optoboard,
+        "Dummy_Optoboard_termination_board": services.termination_board,
+        "Dummy_Optobox": services.optobox,
+        "Dummy_Optobox_powerbox": services.optobox,
+        "Dummy_Optobox_connector_board": services.optobox_powerboard_connector,
+        "Dummy_Optobox_optical_fan_out": services.optobox,
+        "Dummy_Optopanel": Error,
+        "Dummy_Optopanel_cooling_plate": Error,
+        "Dummy_Optobox_powerboard": services.optobox_powerboard_connector,
+        "Dummy_GBCR_chip": Error,
+        "Dummy_Vtrx_module": Error,
+        "Dummy_Bpol2V5_chip": Error,
+        "Dummy_Bpol2V5_carrier_board": Error,
+        "Dummy_Bpol12V_chip": Error,
+        "Dummy_MOPS_chip": services.mops_chip,
+        "Dummy_Power_cables": Error,
+        "Dummy_CAN_bus_cable": services.canbus,
+        "Dummy_Pigtail": utils.subproject_switch(
+            pi=services.is_type0_cable, pb=services.pb_type0_cable
+        ),  # IS Type-0
+        "Dummy_Rigid_flex": utils.subproject_switch(
+            pi=services.is_type0_cable, pb=services.pb_type0_pp0
+        ),  # IS Type-0
+        "Dummy_Data_PP0": utils.subproject_switch(
+            pi=services.is_type0_cable, pe=services.pe_type0_data
+        ),  # IS Type-0  # OB Type-1 Inclined PCB???
+        "Dummy_Power_pigtail": utils.subproject_switch(
+            pi=services.is_type0_cable, pe=services.pe_type0_power
+        ),  # IS Type-0  # OE Type-0
+        "Dummy_Power_bustape": utils.subproject_switch(pe=services.pe_type0_power),
+        "Dummy_Pigtail_panel": services.pb_type0_cable,
+        "Dummy_PP0": utils.subproject_switch(pi=services.pi_type0_pp0),  # IS only
+        "Dummy_Finger": Error,
+        "Dummy_Type_1_Data_link ": utils.subproject_switch(
+            pb=services.pb_type1_data
+        ),  # IS Type-1,
+        "Dummy_Type_1_Power_DCS_line": utils.subproject_switch(
+            pe=services.pe_type1, pb=services.pb_type1_power, pi=services.is_type1_cable
+        ),  # IS Type-1,
+        "Dummy_Environmental_link": Error,
+        "Dummy_PP1_connector": utils.subproject_switch(
+            pi=services.pp1, pe=services.pe_type1
+        ),
+        "Dummy_PP1_connector_pieces_segments": utils.subproject_switch(),
+        "Dummy_Type_2_power_cable": services.type2,
+        "Dummy_Type_2_optobox_cable": services.type2,
+        "Dummy_PP2_box": services.type2,
+        "Dummy_Type_3_HV_cable": services.type3,
+        "Dummy_Type_3_LV_cable": services.type3,
+        "Dummy_MOPS_cable": services.type3,
+        "Dummy_TiLock": services.type3,
+        "Dummy_OPTO_MOPS": services.type3,
+        "Dummy_PP3_power": services.type4,
+    },
+    # default=Bytes(7),
+)
+
+__all__ = ("identifiers",)
diff --git a/src/itksn/pixels/common.py b/src/itksn/pixels/common.py
new file mode 100644
index 0000000..e3aaf3b
--- /dev/null
+++ b/src/itksn/pixels/common.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+from construct import Bytes
+
+from itksn.common import EnumStr
+
+pcb_manufacturer = EnumStr(
+    Bytes(1),
+    Dummy=b"0",
+    EPEC=b"1",
+    NCAB_100um=b"2",
+    ATLAFLEX=b"3",
+    SFCircuits=b"4",
+    PHOENIX=b"5",
+    Yamashita_Material=b"6",
+    NCAB_75um=b"7",
+    Tecnomec=b"8",
+)
diff --git a/src/itksn/pixels/local_supports.py b/src/itksn/pixels/local_supports.py
new file mode 100644
index 0000000..38ef588
--- /dev/null
+++ b/src/itksn/pixels/local_supports.py
@@ -0,0 +1,177 @@
+from __future__ import annotations
+
+from construct import (
+    Bytes,
+    Struct,
+)
+
+from itksn.common import EnumStr
+from itksn.pixels.common import pcb_manufacturer
+
+local_supports_production_type = EnumStr(
+    Bytes(1),
+    Pre_production=b"0",
+    Production=b"1",
+    Prototype=b"2",
+    All_types=b"9",
+)
+
+local_supports_flavor = EnumStr(
+    Bytes(1),
+    L0=b"0",
+    L1=b"1",
+    R01=b"2",
+    R0p5=b"3",
+    R1=b"4",
+)
+
+local_supports_longeron_flavor = EnumStr(
+    Bytes(1),
+    L0=b"0",
+    L1=b"1",
+    R01=b"2",
+    R0p5=b"3",
+    R1=b"4",
+    A_side=b"5",
+    C_side=b"6",
+)
+
+local_supports = Struct(
+    "layer" / Bytes(1),
+    "production_type" / local_supports_production_type,
+    "number" / Bytes(5),
+)
+
+local_supports_is = Struct(
+    "production_type" / local_supports_production_type,
+    "type" / local_supports_flavor,
+    "number" / Bytes(5),
+)
+
+local_supports_longeron = Struct(
+    "production_type" / local_supports_production_type,
+    "type" / local_supports_longeron_flavor,
+    "number" / Bytes(5),
+)
+
+local_supports_ihr = Struct(
+    "production_type" / local_supports_production_type,
+    "type" / local_supports_flavor,
+    "position"
+    / EnumStr(
+        Bytes(1),
+        Standard=b"1",
+        Last=b"2",
+    ),
+    "number" / Bytes(4),
+)
+
+loaded_local_supports_ob_module = Struct(
+    "production_type" / local_supports_production_type,
+    "PCB_manufacturer" / pcb_manufacturer,
+    "number" / Bytes(5),
+)
+
+# FIXME: is the rest not defined?
+local_supports_frame_box = Struct(
+    "type" / EnumStr(Bytes(1), Longeron=b"1", HR=b"2"), "number" / Bytes(6)
+)
+
+yy_identifiers = {
+    # local supports
+    "IS_capillary": ("CP", "PI"),
+    "IS_end_tube": ("ET", "PI"),
+    "IS_cooling_tube": ("CA", "PI"),
+    "IS_bare_local_support_stave": ("SS", "PI"),
+    "IS_bare_local_support_ring": ("RS", "PI"),
+    "IS_ring_local_support_assembly_loaded_ring": ("RL", "PI"),
+    "IS_barrel_stave_assembly_loaded_stave": ("SL", "PI"),
+    "OB_Base_Block": ("BB", "PB"),
+    "OB_Cooling_Block": ("CB", "PB"),
+    "OB_TPG_Tile": ("GT", "PB"),
+    "OB_Local_Support_Inserts": ("IN", "PB"),
+    "OB_Gusset": ("RG", "PB"),
+    "OB_Truss": ("LT", "PB"),
+    "OB_Half_Ring_Shell": ("RS", "PB"),
+    "OB_End_of_longeron_Bracket_End_Gusset": ("EG", "PB"),
+    "OB_Pipe_Support": ("PS", "PB"),
+    "OB_Evaporator_Sleeves": ("ES", "PB"),
+    "OB_Cooling_Pipe_IHR": ("RP", "PB"),
+    "OB_Cooling_Pipe_Longeron": ("LP", "PB"),
+    "OB_Functional_Pipe_for_IHR": ("RE", "PB"),
+    "OB_Functional_Pipe_for_Longeron": ("LE", "PB"),
+    "OB_End_of_longeron_Support": ("EL", "PB"),
+    "OB_Bare_Module_Cell": ("BC", "PB"),
+    "OB_Functional_IHR": ("FR", "PB"),
+    "OB_Functional_Longeron": ("FL", "PB"),
+    "OB_Loaded_Module_Cell": ("LC", "PB"),
+    "OB_Loaded_IHR": ("LR", "PB"),
+    "OB_Loaded_Longeron": ("LL", "PB"),
+    "OB_IHR_Handling_Frame": ("HR", "PB"),
+    "OB_Longeron_Handling_Frame": ("HL", "PB"),
+    "OB_Bare_Cell_Transport_Box": ("TB", "PB"),
+    "OEC_Trapezoids": ("TZ", "PE"),
+    "OEC_Electrical break": ("EB", "PE"),
+    "OEC_Inner_Rim_Insert": ("II", "PE"),
+    "OEC_Outer_Rim_mounting_lugs": ("ML", "PE"),
+    "OEC_Inner_Rim_Closeout": ("IC", "PE"),
+    "OEC_Outer_Rim_Closeout": ("OC", "PE"),
+    "OEC_Pipe_Closeout_support_closeout": ("SC", "PE"),
+    "OEC_Half_Sandwich": ("HS", "PE"),
+    "OEC_Evaporator": ("EV", "PE"),
+    "OEC_Bare_half_ring_assembly_Bare_support": ("BH", "PE"),
+    "OEC_Loaded_local_support_loaded_support": ("LS", "PE"),
+    "OEC_Handling_frame_support_frame": ("SF", "PE"),
+    "OEC_Transport/storage_box_support_box": ("SB", "PE"),
+    "Local_support_handling_frame_box": ("LB", "PE", "PI", "PB"),
+    "High_voltage_group": ("VG", "PE", "PI", "PB"),
+    "Serial_powering_scheme": ("SP", "PE", "PI", "PB"),
+    # dummy local supports
+    "Dummy_IS_capillary": ("YA", "PI"),
+    "Dummy_IS_end_tube": ("YB", "PI"),
+    "Dummy_IS_cooling_tube": ("YD", "PI"),
+    "Dummy_IS_bare_local_support_stave": ("YE", "PI"),
+    "Dummy_IS_bare_local_support_ring": ("YF", "PI"),
+    "Dummy_IS_ring_local_support_assembly_loaded_ring": ("YG", "PI"),
+    "Dummy_IS_barrel_stave_assembly_loaded_stave": ("YH", "PI"),
+    "Dummy_OB_Base_Block": ("ZA", "PB"),
+    "Dummy_OB_Cooling_Block": ("ZB", "PB"),
+    "Dummy_OB_TPG_Tile": ("ZC", "PB"),
+    "Dummy_OB_Local_Support_Inserts": ("ZD", "PB"),
+    "Dummy_OB_Gusset": ("ZE", "PB"),
+    "Dummy_OB_Truss": ("ZF", "PB"),
+    "Dummy_OB_Half_Ring_Shell": ("ZG", "PB"),
+    "Dummy_OB_End_of_longeron_Bracket_End_Gusset": ("ZH", "PB"),
+    "Dummy_OB_Pipe_Support": ("ZI", "PB"),
+    "Dummy_OB_Evaporator_Sleeves": ("ZJ", "PB"),
+    "Dummy_OB_Cooling_Pipe_IHR": ("ZK", "PB"),
+    "Dummy_OB_Cooling_Pipe_Longeron": ("ZL", "PB"),
+    "Dummy_OB_Functional_Pipe_for_IHR": ("ZM", "PB"),
+    "Dummy_OB_Functional_Pipe_for_Longeron": ("ZN", "PB"),
+    "Dummy_OB_End_of_longeron_Support": ("ZO", "PB"),
+    "Dummy_OB_Bare_Module_Cell": ("ZP", "PB"),
+    "Dummy_OB_Functional_IHR": ("ZQ", "PB"),
+    "Dummy_OB_Functional_Longeron": ("ZR", "PB"),
+    "Dummy_OB_Loaded_Module_Cell": ("ZS", "PB"),
+    "Dummy_OB_Loaded_IHR": ("ZT", "PB"),
+    "Dummy_OB_Loaded_Longeron": ("ZU", "PB"),
+    "Dummy_OB_IHR_Handling_Frame": ("ZV", "PB"),
+    "Dummy_OB_Longeron_Handling_Frame": ("ZW", "PB"),
+    "Dummy_OB_Bare_Cell_Transport_Box": ("ZX", "PB"),
+    "Dummy_OEC_Trapezoids": ("YI", "PE"),
+    "Dummy_OEC_Electrical break": ("YJ", "PE"),
+    "Dummy_OEC_Inner_Rim_Insert": ("YK", "PE"),
+    "Dummy_OEC_Outer_Rim_mounting_lugs": ("YL", "PE"),
+    "Dummy_OEC_Inner_Rim_Closeout": ("YM", "PE"),
+    "Dummy_OEC_Outer_Rim_Closeout": ("YN", "PE"),
+    "Dummy_OEC_Pipe_Closeout_support_closeout": ("YO", "PE"),
+    "Dummy_OEC_Half_Sandwich": ("YP", "PE"),
+    "Dummy_OEC_Evaporator": ("YQ", "PE"),
+    "Dummy_OEC_Bare_half_ring_assembly_Bare_support": ("YR", "PE"),
+    "Dummy_OEC_Loaded_local_support_loaded_support": ("YS", "PE"),
+    "Dummy_OEC_Handling_frame_support_frame": ("YT", "PE"),
+    "Dummy_OEC_Transport/storage_box_support_box": ("YU", "PE"),
+    "Dummy_Local_support_handling_frame_box": ("YV", "PE", "PI", "PB"),
+    "Dummy_High_voltage_group": ("YW", "PE", "PI", "PB"),
+    "Dummy_Serial_powering_scheme": ("YX", "PE", "PI", "PB"),
+}
diff --git a/src/itksn/pixels.py b/src/itksn/pixels/modules.py
similarity index 59%
rename from src/itksn/pixels.py
rename to src/itksn/pixels/modules.py
index 843f072..ec7d821 100644
--- a/src/itksn/pixels.py
+++ b/src/itksn/pixels/modules.py
@@ -3,24 +3,14 @@ from __future__ import annotations
 from construct import (
     Bytes,
     Computed,
-    Error,
+    GreedyBytes,
+    Pass,
     Struct,
     Switch,
 )
 
 from itksn.common import EnumStr
-
-pcb_manufacturer = EnumStr(
-    Bytes(1),
-    EPEC=b"1",
-    NCAB_100um=b"2",
-    ATLAFLEX=b"3",
-    SFCircuits=b"4",
-    PHOENIX=b"5",
-    Yamashita_Material=b"6",
-    NCAB_75um=b"7",
-    Tecnomec=b"8",
-)
+from itksn.pixels.common import pcb_manufacturer
 
 triplet_assembly_site = EnumStr(
     Bytes(1),
@@ -31,17 +21,29 @@ triplet_assembly_site = EnumStr(
     LBNL=b"4",
 )
 
-batch_number = EnumStr(
-    Bytes(1),
-    RD53A=b"0",
-    ITkpix_v1=b"1",
-    ITkpix_v2=b"2",
-)
+batch_number = {
+    0: "RD53A",
+    1: "ITkpix_v1",
+    2: "ITkpix_v2",
+    3: "ITkpix_v2",
+    4: "ITkpix_v2",
+    5: "ITkpix_v2",
+    6: "ITkpix_v2",
+    7: "ITkpix_v2",
+    8: "ITkpix_v2",
+    9: "ITkpix_v2",
+    10: "ITkpix_v2",
+    11: "ITkpix_v2",
+    12: "ITkpix_v2",
+    13: "ITkpix_v2",
+    14: "ITkpix_v2",
+    15: "ITkpix_v2",
+}
 
 fe_chip = Struct(
     "number" / Bytes(7),
     "batch_number" / Computed(lambda ctx: (int(ctx.number) & 0xF0000) >> 16),  # type: ignore[arg-type,return-value]
-    "batch" / Computed(lambda ctx: batch_number.parse(str(ctx.batch_number).encode())),  # type: ignore[arg-type,return-value]
+    "batch" / Computed(lambda ctx: batch_number[ctx.batch_number]),  # type: ignore[arg-type,return-value]
     "wafer" / Computed(lambda ctx: (int(ctx.number) & 0x0FF00) >> 8),  # type: ignore[arg-type,return-value]
     "row" / Computed(lambda ctx: (int(ctx.number) & 0x000F0) >> 4),  # type: ignore[arg-type,return-value]
     "column" / Computed(lambda ctx: (int(ctx.number) & 0x0000F) >> 0),  # type: ignore[arg-type,return-value]
@@ -53,8 +55,21 @@ fe_chip_version = EnumStr(
     ITkpix_v1=b"1",
     ITkpix_v1p1=b"2",
     ITkpix_v2=b"3",
+    No_chip=b"9",
+)
+
+fe_chip_version_pcb = EnumStr(
+    Bytes(1),
+    RD53A=b"0",
+    Prototype_ITkpix_v1=b"1",
+    Pre_production_OS_ITkpix_v1=b"2",
+    Pre_production_IS_ITkpix_v1=b"3",
+    Production_OS_ITkpix_v2=b"4",
+    Production_IS_ITkpix_v2=b"5",
+    No_chip=b"9",
 )
 
+
 sensor = Struct(
     "manufacturer"
     / EnumStr(
@@ -98,20 +113,60 @@ bare_module = Struct(
         L0_inner_pixel_planar_sensor_tile=b"3",
         L1_inner_pixel_quad_sensor_tile=b"4",
         Outer_pixel_quad_sensor_tile=b"5",
+        Dummy_sensor_tile=b"9",
     ),
     "number" / Bytes(5),
 )
 
+pcb_loading_site = EnumStr(
+    Bytes(1),
+    outdated=b"0",
+    Oslo=b"2",
+    TOPRO=b"5",
+    NORBIT=b"6",
+    CERN=b"7",
+    unloaded=b"9",
+)
+
+pcb_reception_site = EnumStr(
+    Bytes(1),
+    Genova=b"0",
+    Barcelona=b"1",
+    Oslo=b"2",
+    Milano=b"3",
+    Bergen=b"4",
+)
+
 pcb = Struct(
-    "FE_chip_version" / fe_chip_version,
+    "FE_chip_version" / fe_chip_version_pcb,
     "PCB_manufacturer" / pcb_manufacturer,
     "number" / Bytes(5),
 )
 
+pcb_triplets = Struct(
+    "FE_chip_version" / fe_chip_version_pcb,
+    "PCB_manufacturer" / pcb_manufacturer,
+    "loading"
+    / Switch(
+        lambda ctx: ctx.PCB_manufacturer, {"Dummy": Bytes(1)}, default=pcb_loading_site
+    ),  # esdape hatch for some dummy/digitals
+    "reception"
+    / Switch(
+        lambda ctx: ctx.PCB_manufacturer,
+        {"Dummy": Bytes(1)},
+        default=pcb_reception_site,
+    ),  # esdape hatch for some dummy/digitals
+    "number" / Bytes(3),
+)
+
+
 module = Struct(
     "FE_chip_version" / fe_chip_version,
-    "PCB_manufacturer" / pcb_manufacturer,
-    "number" / Bytes(5),
+    "PCB_manufacturer"
+    / Switch(
+        lambda ctx: ctx.FE_chip_version, {"RD53A": pcb_manufacturer}, default=Pass
+    ),
+    "number" / GreedyBytes,
 )
 triplet_module = Struct(
     "FE_chip_version" / fe_chip_version,
@@ -133,14 +188,19 @@ module_carrier = Struct(
     "module_version"
     / EnumStr(
         Bytes(1),
+        Not_used=b"0",
         Triplet_v1p0=b"1",
         Quad_v2p1=b"2",
+        Quad_v4p1=b"3",
+        Quad_v4p2=b"4",
+        Quad_v4p3=b"5",
     ),
     "manufacturer" / Bytes(1),
     "number" / Bytes(4),
 )
 
 yy_identifiers = {
+    # pixel modules and subcomponents
     "FE_chip_wafer": ("FW", "PG"),
     "FE_chip": ("FC", "PG"),
     "Planar_sensor_wafer_100um_thickness": ("W6", "PI", "PG"),
@@ -175,13 +235,11 @@ yy_identifiers = {
     "Quad_bare_module": ("B4", "PG"),
     "Digital_single_bare_module": ("BS", "PG"),
     "Digital_quad_bare_module": ("BQ", "PG"),
-    "Dummy_quad_bare_module": ("BR", "PG"),
-    "Dummy_single_bare_module": ("BT", "PG"),
     "FourInch_bare_module_gel_pack": ("G4", "PG"),
     "SixInch_bare_module_gel_pack": ("G6", "PG"),
-    "Triplet_L0_Stave_PC": ("PT", "PI"),
-    "Triplet_L0_R0_PC": ("P0", "PI"),
-    "Triplet_L0_R0p5_PC": ("P5", "PI"),
+    "Triplet_L0_Stave_PCB": ("PT", "PI"),
+    "Triplet_L0_R0_PCB": ("P0", "PI"),
+    "Triplet_L0_R0p5_PCB": ("P5", "PI"),
     "Quad_PCB": ("PQ", "PG"),
     "Dual_PCB": ("PD", "PG"),
     "PCB_test_coupon": ("PC", "PG"),
@@ -204,86 +262,20 @@ yy_identifiers = {
     "Dummy_quad_module": ("RQ", "PG"),
     "Dummy_L1_quad_module": ("RR", "PG"),
     "Module_carrier": ("MC", "PG"),
+    # dummy for testing of GUIs/tutorials
+    "Dummy_FE_chip_wafer": ("XW", "PG"),
+    "Dummy_tutorial_FE_chip": ("XF", "PG"),
+    "Dummy_sensor_wafer (with dummy tiles/test structures": ("XH", "PG"),
+    "Dummy_tutorial_sensor_tile": ("XS", "PG"),
+    "Dummy_sensor_test_structure": ("XT", "PG"),
+    "Dummy_sensor_half_moon": ("XH", "PG"),
+    "Dummy_single_bare_module": ("BT", "PG"),
+    "Dummy_quad_bare_module": ("BR", "PG"),
+    "Dummy_tutorial_bare_module": ("XB", "PG"),
+    "Dummy_bare_module_gel_pack": ("XG", "PG"),
+    "Dummy_tutorial_PCB": ("XP", "PG"),
+    "Dummy_PCB_test_coupon": ("XD", "PG"),
+    "Dummy_OB_wirebond_protection_roof": ("XE", "PB"),
+    "Dummy_tutorial_module": ("XM", "PG"),
+    "Dummy_module_carrier": ("XC", "PG"),
 }
-
-subproject_codes = {
-    subproject_code: EnumStr(
-        Bytes(2),
-        **{
-            k: v.encode("utf-8")
-            for k, (v, *allowed_subprojects) in yy_identifiers.items()
-            if subproject_code in allowed_subprojects
-        },
-    )
-    for subproject_code in ["PI", "PG", "PB"]
-}
-
-identifiers = Switch(
-    lambda ctx: ctx.subproject_code,
-    {
-        "FE_chip_wafer": fe_chip,
-        "FE_chip": fe_chip,
-        "Planar_sensor_wafer_100um_thickness": sensor,
-        "Planar_sensor_wafer_150um_thickness": sensor,
-        "ThreeD_sensor_wafer": sensor,
-        "L0_inner_pixel_3D_sensor_wafer_25x100um": sensor,
-        "L0_inner_pixel_3D_sensor_wafer_50x50um": sensor,
-        "L1_inner_pixel_sensor_wafer_100um_thickness": sensor,
-        "Outer_pixel_sensor_wafer_150um_thickness": sensor,
-        "Half_size_planar_sensor_tile_100um_thickness": sensor,
-        "Half_size_planar_sensor_tile_150um_thickness": sensor,
-        "Full_size_planar_sensor_tile_100um_thickness": sensor,
-        "Full_size_planar_sensor_tile_150um_thickness": sensor,
-        "Half_size_3D_sensor_tile_25x100um": sensor,
-        "Full_size_3D_sensor_tile_25x100um": sensor,
-        "Half_size_3D_sensor_tile_50x50um": sensor,
-        "Full_size_3D_sensor_tile_50x50um": sensor,
-        "L0_inner_pixel_3D_sensor_tile_25x100um": sensor,
-        "L0_inner_pixel_3D_sensor_tile_50x50um": sensor,
-        "L1_inner_pixel_quad_sensor_tile": sensor,
-        "Outer_pixel_quad_sensor_tile": sensor,
-        "Planar_Sensor_test_structure_100um_thickness": sensor,
-        "Planar_Sensor_test_structure_150um_thickness": sensor,
-        "ThreeD_Sensor_test_structure_25x100um": sensor,
-        "ThreeD_Sensor_test_structure_50x50um": sensor,
-        "Planar_Sensor_half_moon_100um_thickness": sensor,
-        "Planar_Sensor_half_moon_150um_thickness": sensor,
-        "ThreeD_Sensor_half_moon_25x100um": sensor,
-        "ThreeD_Sensor_half_moon_50x50um": sensor,
-        "Single_bare_module": bare_module,
-        "Dual_bare_module": bare_module,
-        "Quad_bare_module": bare_module,
-        "Digital_single_bare_module": bare_module,
-        "Digital_quad_bare_module": bare_module,
-        "Dummy_quad_bare_module": bare_module,
-        "Dummy_single_bare_module": bare_module,
-        "FourInch_bare_module_gel_pack": Error,
-        "SixInch_bare_module_gel_pack": Error,
-        "Triplet_L0_Stave_PCB": pcb,
-        "Triplet_L0_R0_PCB": pcb,
-        "Triplet_L0_R0p5_PCB": pcb,
-        "Quad_PCB": pcb,
-        "Dual_PCB": pcb,
-        "PCB_test_coupon": pcb,
-        "OB_wirebond_protection_roof": Error,
-        "Triplet_L0_stave_module": triplet_module,
-        "Triplet_L0_Ring0_module": triplet_module,
-        "Triplet_L0_Ring0p5_module": triplet_module,
-        "L1_quad_module": module,
-        "Outer_system_quad_module": module,
-        "Dual_chip_module": module,
-        "Single_chip_module": module,
-        "Digital_triplet_L0_stave_module": triplet_module,
-        "Digital_triplet_L0_ring0_module": triplet_module,
-        "Digital_triplet_L0_ring0p5_module": triplet_module,
-        "Digital_quad_module": module,
-        "Digital_L1_quad_module": module,
-        "Dummy_triplet_L0_stave_module": triplet_module,
-        "Dummy_triplet_L0_ring0_module": triplet_module,
-        "Dummy_triplet_L0_ring0p5_module": triplet_module,
-        "Dummy_quad_module": module,
-        "Dummy_L1_quad_module": module,
-        "Module_carrier": module_carrier,
-    },
-    default=Bytes(7),
-)
diff --git a/src/itksn/pixels/services.py b/src/itksn/pixels/services.py
new file mode 100644
index 0000000..8836cd1
--- /dev/null
+++ b/src/itksn/pixels/services.py
@@ -0,0 +1,535 @@
+from __future__ import annotations
+
+from construct import (
+    Bytes,
+    Computed,
+    Const,
+    Pointer,
+    Select,
+    Struct,
+    Switch,
+)
+
+from itksn.common import EnumStr
+
+orientation = EnumStr(
+    Bytes(1),
+    normal=b"0",
+    mirror=b"1",
+)
+
+optoboard = Struct(
+    "production_version"
+    / Select(
+        Const(b"2"), Const(b"3"), Const(b"4"), Const(b"5")
+    ),  # FIXME: "2", "3", "5" not defined in table 6
+    "LpGBT_count"
+    / EnumStr(
+        Bytes(1),
+        one=b"1",
+        two=b"2",
+        four=b"4",
+    ),
+    "number" / Bytes(5),
+)
+
+termination_board = Struct(
+    "production_version" / Const(b"0"),
+    "flavor"
+    / EnumStr(
+        Bytes(1),
+        Normal_L_long=b"0",
+        Normal_L_short=b"1",
+        Mirror_L_long=b"2",
+        Mirror_L_short=b"3",
+        Normal_slim=b"4",
+        Mirror_slim=b"5",
+        Normal_extended_slim=b"6",  # FIXME: OB Type-1 Termination Board does not have this
+    ),
+    "number" / Bytes(5),
+)
+
+optobox_powerboard_connector = Struct(
+    "production_version"
+    / Select(
+        Const(b"0"), Const(b"2"), Const(b"3")
+    ),  # FIXME: "0" and "3" not in table 9
+    "orientation" / orientation,
+    "number" / Bytes(5),
+)
+
+optobox = Struct(
+    "production_version" / Const(b"0"),
+    "orientation" / orientation,
+    "number" / Bytes(5),
+)
+
+canbus = Struct(
+    "production_version" / Const(b"0"),
+    "connector_type"
+    / EnumStr(
+        Bytes(1),
+        six=b"6",
+        eight=b"8",
+    ),
+    "number" / Bytes(5),
+)
+
+type0_component: dict[str, dict[tuple[str, str], str]] = {
+    "Data_PP0": {  # "DP"
+        ("Barrel_Triplet", "NA"): "L0 Barrel Data Flex",
+        ("Barrel_Quad", "F1"): "L1 Barrel Data Flex",
+        ("Barrel_Quad", "F2"): "L1 Barrel Data Flex",
+        ("Barrel_Quad", "F3"): "L1 Barrel Data Flex",
+        ("Barrel_Quad", "F4"): "L1 Barrel Data Flex",
+        ("Ring_Triplet", "F1"): "R0 Data Flex",
+        ("Ring_Triplet", "F2"): "R0 Data Flex",
+        ("Ring_Triplet", "F3"): "R0 Data Flex",
+        ("Ring_Triplet", "F4"): "R0.5 Data Flex",
+        ("Ring_Triplet", "F5"): "R0.5 Data Flex",
+        ("Ring_Triplet", "F6"): "R0.5 Data Flex",
+    },
+    "Power_pigtail": {  # "PP"
+        ("Barrel_Triplet", "NA"): "L0 Barrel Power Flex",
+        ("Barrel_Quad", "F1"): "L1 Barrel Power Flex",
+        ("Barrel_Quad", "F2"): "L1 Barrel Power Flex",
+        ("Ring_Triplet", "F1"): "R0 Power",
+        ("Ring_Triplet", "F2"): "R0 Power Jumper",
+    },
+    "Rigid_flex": {  # "RF"
+        ("Ring_Both", "F3"): "Coupled Ring R0/R1",
+        ("Ring_Quad", "F3"): "Quad Ring R1",
+        ("Ring_Triplet", "F2"): "Intermediate Ring",
+    },
+    "Pigtail": {  # "PG"
+        ("Ring_Quad", "F3"): "Quad Module Z-Ray Flex",
+        ("Ring_Both", "F1"): "Type-0 to PP0",
+        ("Ring_Both", "F2"): "Type-0 to PP0",
+    },
+}
+
+is_type0_cable = Struct(
+    "production_version"
+    / EnumStr(
+        Bytes(1),
+        Pre_production=b"0",
+        Production=b"1",
+        Dummy=b"9",
+    ),
+    "flavor"
+    / EnumStr(
+        Bytes(1),
+        Barrel_Triplet=b"0",
+        Barrel_Quad=b"1",
+        Ring_Triplet=b"2",
+        Ring_Quad=b"3",
+        Ring_Both=b"4",
+    ),
+    "subflavor"
+    / EnumStr(
+        Bytes(1),
+        NA=b"0",
+        F1=b"1",
+        F2=b"2",
+        F3=b"3",
+        F4=b"4",
+        F5=b"5",
+        F6=b"6",
+    ),
+    "component"
+    / Computed(
+        lambda ctx: type0_component[ctx._.subproject_code][(ctx.flavor, ctx.subflavor)],  # type: ignore[arg-type,return-value]
+    ),
+    "number" / Bytes(4),
+)
+
+type1_production_version = EnumStr(
+    Bytes(1),
+    Prototype=b"0",
+    Pre_production=b"1",
+    Production=b"2",
+    Dummy=b"9",
+)
+
+is_type1_cable = Struct(
+    "production_version" / type1_production_version,
+    "flavor"
+    / EnumStr(
+        Bytes(1),
+        L0L1=b"0",
+        L02xL1=b"1",
+        Coupled_Ring=b"2",
+        Intermediate_Ring=b"3",
+        QR1=b"4",
+        QR2=b"5",
+    ),
+    "number" / Bytes(5),
+)
+
+pp1 = Struct(
+    "production_version" / type1_production_version,
+    "glenair_part"
+    / EnumStr(
+        Bytes(1),
+        Type_2_Header=b"0",
+        Type_1_Receptacle=b"1",
+        Strain_Arm=b"3",
+        Filter_Plug=b"9",
+    ),
+    "flavor"
+    / EnumStr(
+        Bytes(1),
+        N=b"0",
+        A=b"1",
+        B=b"2",
+        C=b"3",
+        D=b"4",
+        E=b"5",
+    ),
+    "number" / Bytes(4),
+)
+
+pi_type0_pp0 = Struct(
+    "production_version"
+    / EnumStr(
+        Bytes(1),
+        Prototype=b"0",
+        Pre_production=b"1",
+        Production=b"2",
+        Dummy=b"9",
+    ),
+    "flavor"
+    / EnumStr(
+        Bytes(1),
+        Zp_3Stave=b"0",
+        Zm_3Stave=b"1",
+        Zp_2Stave=b"2",
+        Zm_2Stave=b"3",
+        Coupled_Ring=b"4",
+        Intermediate_Ring=b"5",
+        Quad_Ring=b"6",
+    ),
+    "number" / Bytes(5),
+)
+
+# PB-PL, PB-PG
+pb_type0_cable = Struct(
+    "type" / EnumStr(Bytes(1), Flat=b"0", Inclined=b"1", Inclined_test_coupon=b"2"),
+    "flavor"
+    / Switch(
+        lambda ctx: ctx.type,
+        {
+            "Flat": EnumStr(Bytes(1), Bottom=b"0", Top=b"1"),
+            "Inclined": EnumStr(
+                Bytes(1),
+                Front=b"0",
+                Back=b"1",
+                Front_Last_ring=b"2",
+                Back_Last_ring=b"3",
+            ),
+        },
+    ),
+    "version"
+    / EnumStr(
+        Bytes(1),
+        Pre_production=b"0",
+        Production=b"1",
+        Prototype=b"9",
+    ),
+    "number" / Bytes(4),
+)
+
+# PB-RF
+pb_type0_pp0 = Struct(
+    "type" / EnumStr(Bytes(1), Flat=b"0", Inclined=b"1", Inclined_test_coupon=b"2"),
+    "flavor"
+    / Switch(
+        lambda ctx: ctx.type,
+        {
+            "Flat": EnumStr(
+                Bytes(1), Short_L2=b"0", Long_L2=b"1", Short_L3_L4=b"2", Long_L3_L4=b"3"
+            ),
+            "Inclined": EnumStr(
+                Bytes(1),
+                L2_SP1=b"0",
+                L2_SP2=b"1",
+                L3_SP1=b"2",
+                L3_SP2=b"3",
+                L4_SP1=b"4",
+                L4_SP2=b"5",
+                Dummy=b"9",
+            ),
+            "Inclined_test_coupon": EnumStr(  # FIXME: not specified in Table 21
+                Bytes(1),
+                L2_SP1=b"0",
+                L2_SP2=b"1",
+                L3_SP1=b"2",
+                L3_SP2=b"3",
+                L4_SP1=b"4",
+                L4_SP2=b"5",
+                Dummy=b"9",
+            ),
+        },
+    ),
+    "version"
+    / EnumStr(
+        Bytes(1),
+        Pre_production=b"0",
+        Production=b"1",
+        Prototype=b"9",
+    ),
+    "number" / Bytes(4),
+)
+
+pb_type1_power = Struct(
+    "type"
+    / EnumStr(
+        Bytes(1),
+        Flat=b"0",
+        Inclined=b"1",
+    ),
+    "serial_power_chains"
+    / EnumStr(
+        Bytes(1),
+        F1=b"4",
+        F2=b"2",
+    ),
+    "number" / Bytes(5),
+)
+
+pb_type1_data_bundle = Struct(
+    "flavor"
+    / EnumStr(
+        Bytes(2),
+        Flat_L2_Normal_Slim=b"00",
+        Flat_L2_Mirror_Slim=b"01",
+        Flat_L3_L4_Mirror_Slim=b"02",
+        Flat_L3_L4_Normal_Slim=b"03",
+        Inclined_L2_Normal_L_short=b"04",
+        Inclined_L2_Normal_L_long=b"05",
+        Inclined_L2_Mirror_L_short=b"06",
+        Inclined_L2_Mirror_L_long=b"07",
+        Inclined_L3_Normal_Slim10=b"08",
+        Inclined_L3_Mirror_Slim10=b"09",
+        Inclined_L4_Mirror_Slim16=b"10",
+        Inclined_L4_Normal_Slim=b"11",
+        Inclined_L3_L4_Mirror_Slim12=b"12",
+        Inclined_L3_L4_Normal_Slim12=b"13",
+    ),
+)
+
+pb_type1_data_inclined = Struct(
+    "version" / Bytes(1),
+    "manufacturer" / Bytes(1),
+)
+
+pb_type1_data = Struct(
+    "_reserved" / Pointer(2, Bytes(2)),
+    "data"
+    / Switch(
+        lambda ctx: ctx._reserved,  # pylint: disable=protected-access
+        {
+            b"00": pb_type1_data_inclined,
+        },
+        default=pb_type1_data_bundle,
+    ),
+    "length" / Bytes(2),
+    "number" / Bytes(3),
+)
+
+pe_type0_data_mapping = Switch(
+    lambda ctx: ctx.layer,
+    {
+        "L2": EnumStr(
+            Bytes(1),
+            ring15_Front=b"0",
+            ring15_Back=b"1",
+            ring611_Front=b"2",
+            ring611_Back=b"3",
+        ),
+        "L3": EnumStr(
+            Bytes(1),
+            Front_5downlinks=b"0",
+            Back_5downlinks=b"1",
+            Front_6downlinks=b"2",
+            Back_6downlinks=b"3",
+        ),
+        "L4": EnumStr(
+            Bytes(1),
+            ring17_Front_4downlinks=b"0",
+            ring17_Back_4downlinks=b"1",
+            ring17_Front_5downlinks=b"2",
+            ring17_Back_5downlinks=b"3",
+            ring89_Front_4downlinks=b"4",
+            ring89_Back_4downlinks=b"5",
+            ring89_Front_5downlinks=b"6",
+            ring89_Back_5downlinks=b"7",
+        ),
+    },
+)
+
+pe_type0_data = Struct(
+    "layer" / EnumStr(Bytes(1), L2=b"2", L3=b"3", L4=b"4", All=b"9"),
+    "flavor" / pe_type0_data_mapping,
+    "reserved" / Const(b"0"),
+    "number" / Bytes(4),
+)
+
+pe_type0_power = Struct(
+    "layer" / EnumStr(Bytes(1), L2=b"2", L3=b"3", L4=b"4", All=b"9"),
+    "flavor" / Bytes(1),
+    "reserved" / Const(b"0"),
+    "number" / Bytes(4),
+)
+
+# FIXME: merge with pe_type0_power?
+# 1P (PP1_connector) or PB (Power_bustape)
+pe_type1 = Struct(
+    "flavor" / Bytes(1),
+    "reserved" / Const(b"0"),
+    "length"
+    / EnumStr(
+        Bytes(1),
+        _50cm=b"0",
+        _250cm=b"9",
+    ),
+    "number" / Bytes(4),
+)
+
+type2 = Struct(
+    "flavor"
+    / Switch(
+        lambda ctx: ctx._.component_code,
+        {
+            "Type_2_power_cable": EnumStr(Bytes(1), normal=b"1", abnormal=b"2"),
+            "Type_2_optobox_cable": Const(b"0"),
+            "PP2_box": EnumStr(
+                Bytes(1),
+                F1=b"1",
+                F2=b"2",
+                F3=b"3",
+                F4=b"4",
+                F5=b"5",
+                F6=b"6",
+                F7=b"7",
+                F8=b"8",
+            ),
+        },
+    ),
+    "number" / Bytes(6),
+)
+
+type3 = Struct(
+    "flavor" / Const(b"0"),
+    "numbers" / Bytes(6),
+)
+
+type4 = Struct(
+    "flavor"
+    / EnumStr(
+        Bytes(1),
+        Pre_production_non_rad_hard=b"0",
+        Pre_production_rad_hard=b"1",
+        Production=b"2",
+    )
+)
+
+mops_chip = Struct(
+    "reserved" / Const(b"00"),
+    "production_version"
+    / EnumStr(
+        Bytes(1),
+        Pre_production=b"0",
+        Production=b"3",
+    ),
+    "vendor" / Bytes(4),
+)
+
+yy_identifiers = {
+    # services
+    "Optoboard": ("OB", "PG"),
+    "Optoboard_termination_board": ("OT", "PG"),  # codespell:ignore
+    "Optobox": ("OX", "PG"),
+    "Optobox_powerbox": ("OW", "PG"),
+    "Optobox_connector_board": ("OC", "PG"),
+    "Optobox_optical_fan_out": ("OF", "PG"),
+    "Optopanel": ("OS", "PG"),
+    "Optopanel_cooling_plate": ("OO", "PG"),
+    "Optobox_powerboard": ("OP", "PG"),
+    "LpGBT_chip": ("OL", "PG"),
+    "GBCR_chip": ("OG", "PG"),
+    "Vtrx_module": ("OV", "PG"),
+    "Bpol2V5_chip": ("O5", "PG"),
+    "Bpol2V5_carrier_board": ("OK", "PG"),
+    "Bpol12V_chip": ("O2", "PG"),
+    "MOPS_chip": ("MP", "PG"),
+    "Power_cables": ("OI", "PG"),
+    "CAN_bus_cable": ("OD", "PG"),
+    "Pigtail": ("PG", "PB"),
+    "Rigid_flex": ("RF", "PI", "PB"),
+    "Data_PP0": ("DP", "PI", "PE"),
+    "Power_pigtail": ("PP", "PI", "PE"),
+    "Power_bustape": ("PB", "PI", "PB", "PE"),
+    "Bare_bustape": ("NB", "PE"),
+    "Pigtail_panel": ("PL", "PB"),
+    "PP0": ("0P", "PI"),  # FIXME: conflict with Triplet_L0_R0_PC
+    "Finger": ("FI", "PI"),
+    "Type_1_Data_link ": ("D1", "PI", "PB", "PE"),
+    "Type_1_Power_DCS_line": ("P1", "PI", "PB", "PE"),
+    "Environmental_link": ("E1", "PI", "PB", "PE"),
+    "PP1_connector": ("1P", "PI", "PB", "PE"),
+    "PP1_connector_pieces_segments": ("CS", "PI", "PB", "PE"),
+    "strain_relief": ("SR", "PB"),
+    "Type_2_power_cable": ("P2", "PI", "PB", "PE"),
+    "Type_2_optobox_cable": ("20", "PI", "PB", "PE"),
+    "PP2_box": ("2P", "PG"),
+    "Type_3_HV_cable": ("P3", "PI", "PB", "PE"),
+    "Type_3_LV_cable": ("L3", "PI", "PB", "PE"),
+    "MOPS_cable": ("M3", "PG"),
+    "TiLock": ("TL", "PG"),
+    "OPTO_MOPS": ("OM", "PG"),
+    "PP3_power": ("3P", "PG"),
+    # Dummy services
+    "Dummy_Optoboard": ("Q1", "PG"),
+    "Dummy_Optoboard_termination_board": ("Q2", "PG"),  # codespell:ignore
+    "Dummy_Optobox": ("Q3", "PG"),
+    "Dummy_Optobox_powerbox": ("Q4", "PG"),
+    "Dummy_Optobox_connector_board": ("Q5", "PG"),
+    "Dummy_Optobox_optical_fan_out": ("Q6", "PG"),
+    "Dummy_Optopanel": ("Q7", "PG"),
+    "Dummy_Optopanel_cooling_plate": ("Q8", "PG"),
+    "Dummy_Optobox_powerboard": ("Q9", "PG"),
+    # "Dummy_LpGBT_chip": ("", "PG"), # FIXME: missing dummy
+    "Dummy_GBCR_chip": ("QA", "PG"),
+    "Dummy_Vtrx_module": ("QB", "PG"),
+    "Dummy_Bpol2V5_chip": ("QC", "PG"),
+    "Dummy_Bpol2V5_carrier_board": ("QD", "PG"),
+    "Dummy_Bpol12V_chip": ("QE", "PG"),
+    "Dummy_MOPS_chip": ("QF", "PG"),
+    "Dummy_Power_cables": ("QG", "PG"),
+    "Dummy_CAN_bus_cable": ("QH", "PG"),
+    "Dummy_Pigtail": ("QI", "PB"),
+    "Dummy_Rigid_flex": ("QK", "PI", "PB"),  # FIXME: "QJ" skipped
+    "Dummy_Data_PP0": ("QL", "PI", "PE"),  # FIXME: typo in table 3
+    "Dummy_Power_pigtail": ("QM", "PI", "PE"),
+    "Dummy_Power_bustape": ("QN", "PI", "PB", "PE"),
+    # "Dummy_Bare_bustape": ("", "PE"), # FIXME: missing dummy
+    "Dummy_Pigtail_panel": ("QO", "PB"),
+    "Dummy_PP0": ("QP", "PI"),
+    "Dummy_Finger": ("QV", "PI"),
+    "Dummy_Type_1_Data_link ": ("QQ", "PI", "PB", "PE"),
+    "Dummy_Type_1_Power_DCS_line": ("QR", "PI", "PB", "PE"),
+    "Dummy_Environmental_link": ("QS", "PI", "PB", "PE"),
+    "Dummy_PP1_connector": ("QT", "PI", "PB", "PE"),
+    "Dummy_PP1_connector_pieces_segments": ("QU", "PI", "PB", "PE"),
+    # "Dummy_strain_relief": ("", "PB"), # FIXME: missing dummy
+    "Dummy_Type_2_power_cable": ("QW", "PI", "PB", "PE"),
+    "Dummy_Type_2_optobox_cable": ("QX", "PI", "PB", "PE"),
+    "Dummy_PP2_box": ("QY", "PG"),
+    "Dummy_Type_3_HV_cable": ("QZ", "PI", "PB", "PE"),
+    "Dummy_Type_3_LV_cable": ("Q1", "PI", "PB", "PE"),
+    "Dummy_MOPS_cable": ("Q2", "PG"),
+    "Dummy_TiLock": ("Q3", "PG"),
+    "Dummy_OPTO_MOPS": ("Q4", "PG"),
+    "Dummy_PP3_power": ("Q5", "PG"),
+}
diff --git a/src/itksn/pixels/utils.py b/src/itksn/pixels/utils.py
new file mode 100644
index 0000000..7fcf11b
--- /dev/null
+++ b/src/itksn/pixels/utils.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from typing import TypeVar
+
+from construct import (
+    Construct,
+    Error,
+    Switch,
+)
+
+# from construct-typing
+ParsedType_co = TypeVar("ParsedType_co", covariant=True)  # pylint:disable=invalid-name
+BuildTypes_contra = TypeVar("BuildTypes_contra", contravariant=True)
+
+
+def subproject_switch(
+    pg: Construct[ParsedType_co, BuildTypes_contra] | None = None,
+    pi: Construct[ParsedType_co, BuildTypes_contra] | None = None,
+    pe: Construct[ParsedType_co, BuildTypes_contra] | None = None,
+    pb: Construct[ParsedType_co, BuildTypes_contra] | None = None,
+) -> Construct[ParsedType_co, BuildTypes_contra]:
+    """
+    helper utility to pick up a different Construct for different subprojects
+    """
+    return Switch(
+        lambda ctx: ctx.subproject_code,
+        {
+            "inner_pixel": pi or Error,
+            "outer_pixel_barrel": pb or Error,
+            "pixel_general": pg or Error,
+            "pixel_endcaps": pe or Error,
+        },
+    )


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..a3af36d
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+import pathlib
+
+# shutil is nicer, but doesn't work: https://bugs.python.org/issue20849
+from functools import partial
+from shutil import copytree as _copytree
+
+import pytest
+
+copytree = partial(_copytree, dirs_exist_ok=True)
+
+
+@pytest.fixture
+def datadir(tmp_path, request):
+    """
+    Fixture responsible for searching a folder with the same name of test
+    module and, if available, moving all contents to a temporary directory so
+    tests can use them freely.
+    """
+    # this gets the module name (e.g. /path/to/module-qc-analysis-tools/tests/test_cli.py)
+    # and then gets the directory by removing the suffix (e.g. /path/to/module-qc-analysis-tools/tests/test_cli)
+    test_dir = pathlib.Path(request.module.__file__).with_suffix("")
+
+    if test_dir.is_dir():
+        copytree(test_dir, str(tmp_path))
+
+    return tmp_path
diff --git a/tests/test_core.py b/tests/test_core.py
index a18a746..e41c393 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -20,8 +20,9 @@ def test_parse_fe_wafer():
     parsed = itksn.parse(b"20UPGFW2123456")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "FE_chip_wafer"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "FE_chip_wafer"
     assert parsed.identifier.batch == "RD53A"
     assert parsed.identifier.number == b"2123456"
 
@@ -35,8 +36,9 @@ def test_parse_sensor():
     parsed = itksn.parse(b"20UPGW34212345")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "Outer_pixel_sensor_wafer_150um_thickness"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "Outer_pixel_sensor_wafer_150um_thickness"
     assert parsed.identifier.manufacturer == "V5_LFoundry"
     assert parsed.identifier.sensor_type == "Halfmoon_preproduction_Double_MS"
     assert parsed.identifier.number == b"12345"
@@ -46,8 +48,9 @@ def test_parse_bare_module():
     parsed = itksn.parse(b"20UPGBS1112345")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "Digital_single_bare_module"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "Digital_single_bare_module"
     assert parsed.identifier.FE_chip_version == "ITkpix_v1"
     assert parsed.identifier.sensor_type == "Market_survey_sensor_tile"
     assert parsed.identifier.number == b"12345"
@@ -57,8 +60,9 @@ def test_parse_pcb():
     parsed = itksn.parse(b"20UPGPD0112345")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "Dual_PCB"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "Dual_PCB"
     assert parsed.identifier.FE_chip_version == "RD53A"
     assert parsed.identifier.PCB_manufacturer == "EPEC"
     assert parsed.identifier.number == b"12345"
@@ -68,8 +72,9 @@ def test_parse_module():
     parsed = itksn.parse(b"20UPGR90112345")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "Digital_quad_module"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "Digital_quad_module"
     assert parsed.identifier.FE_chip_version == "RD53A"
     assert parsed.identifier.PCB_manufacturer == "EPEC"
     assert parsed.identifier.number == b"12345"
@@ -79,9 +84,22 @@ def test_parse_quad_module():
     parsed = itksn.parse(b"20UPIM12602173")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "inner_pixel"
-    assert parsed.subproject_code == "L1_quad_module"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "inner_pixel"
+    assert parsed.component_code == "L1_quad_module"
     assert parsed.identifier.FE_chip_version == "ITkpix_v1p1"
+    assert parsed.identifier.PCB_manufacturer is None
+    assert parsed.identifier.number == b"602173"
+
+
+def test_parse_quad_module_rd53a():
+    parsed = itksn.parse(b"20UPIM10602173")
+    assert parsed.atlas_project == "atlas_detector"
+    assert parsed.system_code == "phaseII_upgrade"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "inner_pixel"
+    assert parsed.component_code == "L1_quad_module"
+    assert parsed.identifier.FE_chip_version == "RD53A"
     assert parsed.identifier.PCB_manufacturer == "Yamashita_Material"
     assert parsed.identifier.number == b"02173"
 
@@ -90,8 +108,9 @@ def test_parse_module_carrier():
     parsed = itksn.parse(b"20UPGMC2291234")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "Module_carrier"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "Module_carrier"
     assert parsed.identifier.module_type == "Linear_triplet_module_carrier"
     assert parsed.identifier.module_version == "Quad_v2p1"
     assert parsed.identifier.manufacturer == b"9"
@@ -107,15 +126,28 @@ def test_parse_digital_quad_module():
     parsed = itksn.parse(b"20UPGR92101041")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "Digital_quad_module"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "Digital_quad_module"
     assert parsed.identifier.FE_chip_version == "ITkpix_v1p1"
+    assert parsed.identifier.PCB_manufacturer is None
+    assert parsed.identifier.number == b"101041"
+
+
+def test_parse_digital_quad_module_rd53a():
+    parsed = itksn.parse(b"20UPGR90101041")
+    assert parsed.atlas_project == "atlas_detector"
+    assert parsed.system_code == "phaseII_upgrade"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "Digital_quad_module"
+    assert parsed.identifier.FE_chip_version == "RD53A"
     assert parsed.identifier.PCB_manufacturer == "EPEC"
     assert parsed.identifier.number == b"01041"
 
 
 @pytest.mark.parametrize(
-    ("serial_number", "subproject_code", "assembly_site", "number"),
+    ("serial_number", "component_code", "assembly_site", "number"),
     [
         ("20UPIMS2002140", "Triplet_L0_stave_module", "Genova", b"2140"),
         ("20UPIM52002140", "Triplet_L0_Ring0p5_module", "Genova", b"2140"),
@@ -124,12 +156,13 @@ def test_parse_digital_quad_module():
     ],
     ids=["normal", "R05", "R0", "L0"],
 )
-def test_triplet_modules(serial_number, subproject_code, assembly_site, number):
+def test_triplet_modules(serial_number, component_code, assembly_site, number):
     parsed = itksn.parse(serial_number.encode())
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "inner_pixel"
-    assert parsed.subproject_code == subproject_code
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "inner_pixel"
+    assert parsed.component_code == component_code
     assert parsed.identifier.assembly_site == assembly_site
     assert parsed.identifier.not_used == b"0"
     assert parsed.identifier.number == number
@@ -139,9 +172,70 @@ def test_fe_chip():
     parsed = itksn.parse(b"20UPGFC1048575")
     assert parsed.atlas_project == "atlas_detector"
     assert parsed.system_code == "phaseII_upgrade"
-    assert parsed.project_code == "pixel_general"
-    assert parsed.subproject_code == "FE_chip"
-    assert parsed.identifier.batch == "ITkpix_v1"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_general"
+    assert parsed.component_code == "FE_chip"
+    assert parsed.identifier.batch == "ITkpix_v2"
     assert parsed.identifier.wafer == 255
     assert parsed.identifier.row == 15
     assert parsed.identifier.column == 15
+
+
+def test_is_capillary():
+    parsed = itksn.parse(b"20UPICP1299999")
+    assert parsed.atlas_project == "atlas_detector"
+    assert parsed.system_code == "phaseII_upgrade"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "inner_pixel"
+    assert parsed.component_code == "IS_capillary"
+    assert parsed.identifier.production_type == "Production"
+    assert parsed.identifier.type == "R01"
+    assert parsed.identifier.number == b"99999"
+
+
+def test_outer_endcap_data_pp0():
+    parsed = itksn.parse(b"20UPEDP2209999")
+    assert parsed.atlas_project == "atlas_detector"
+    assert parsed.system_code == "phaseII_upgrade"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "pixel_endcaps"
+    assert parsed.component_code == "Data_PP0"
+    assert parsed.identifier.layer == "L2"
+    assert parsed.identifier.flavor == "ring611_Front"
+    assert parsed.identifier.number == b"9999"
+
+
+def test_inner_system_pp1():
+    parsed = itksn.parse(b"20UPI1P1000002")
+    assert parsed.atlas_project == "atlas_detector"
+    assert parsed.system_code == "phaseII_upgrade"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "inner_pixel"
+    assert parsed.component_code == "PP1_connector"
+    assert parsed.identifier.production_version == "Pre_production"
+    assert parsed.identifier.glenair_part == "Type_2_Header"
+    assert parsed.identifier.number == b"0002"
+
+
+def test_inner_system_type1_power_L02xL1():
+    parsed = itksn.parse(b"20UPIP19100001")
+    assert parsed.atlas_project == "atlas_detector"
+    assert parsed.system_code == "phaseII_upgrade"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "inner_pixel"
+    assert parsed.component_code == "Type_1_Power_DCS_line"
+    assert parsed.identifier.production_version == "Dummy"
+    assert parsed.identifier.flavor == "L02xL1"
+    assert parsed.identifier.number == b"00001"
+
+
+def test_inner_system_type1_power_CR():
+    parsed = itksn.parse(b"20UPIP10200001")
+    assert parsed.atlas_project == "atlas_detector"
+    assert parsed.system_code == "phaseII_upgrade"
+    assert parsed.project_code == "pixel"
+    assert parsed.subproject_code == "inner_pixel"
+    assert parsed.component_code == "Type_1_Power_DCS_line"
+    assert parsed.identifier.production_version == "Prototype"
+    assert parsed.identifier.flavor == "Coupled_Ring"
+    assert parsed.identifier.number == b"00001"
diff --git a/tests/test_integration.py b/tests/test_integration.py
new file mode 100644
index 0000000..56e70e7
--- /dev/null
+++ b/tests/test_integration.py
@@ -0,0 +1,20 @@
+from __future__ import annotations
+
+import json
+import pathlib
+
+import pytest
+
+import itksn
+
+valid_sns = json.loads(
+    (pathlib.Path(__file__).with_suffix("") / "valid_sns.json").read_text()
+)
+
+
+@pytest.mark.parametrize(
+    "serial_number",
+    valid_sns,
+)
+def test_sn(serial_number):
+    itksn.parse(serial_number.encode("utf-8"))
diff --git a/tests/test_integration/valid_sns.json b/tests/test_integration/valid_sns.json
new file mode 100644
index 0000000..fc96cba
--- /dev/null
+++ b/tests/test_integration/valid_sns.json
@@ -0,0 +1,500 @@
+[
+  "20UPBBB0000001",
+  "20UPBBC0000001",
+  "20UPBCB0000001",
+  "20UPBGT0000001",
+  "20UPBHL5000001",
+  "20UPBHL5100026",
+  "20UPBHL6000002",
+  "20UPBHL6100004",
+  "20UPBHL6200001",
+  "20UPBHLA000001",
+  "20UPBHLC000004",
+  "20UPBHR2010001",
+  "20UPBHR2110001",
+  "20UPBHR2120002",
+  "20UPBHR3010001",
+  "20UPBHR3110001",
+  "20UPBHR3210001",
+  "20UPBHR4010001",
+  "20UPBHR4110001",
+  "20UPBHR4120001",
+  "20UPBLB1000001",
+  "20UPBLB1100001",
+  "20UPBLB1200001",
+  "20UPBLB1900001",
+  "20UPBLB2000001",
+  "20UPBLB2100001",
+  "20UPBLC2110482",
+  "20UPBPG0010001",
+  "20UPBPG0110001",
+  "20UPBPG1010001",
+  "20UPBPG1110001",
+  "20UPBPL0010001",
+  "20UPBPL0110001",
+  "20UPBPL1010001",
+  "20UPBPL1110001",
+  "20UPBQK0000001",
+  "20UPBQK0100001",
+  "20UPBQR0200001",
+  "20UPBQR0400001",
+  "20UPBRF0010001",
+  "20UPBRF0110001",
+  "20UPBRF0210001",
+  "20UPBRF0310001",
+  "20UPBRF1000101",
+  "20UPBRF1090101",
+  "20UPBRF1097926",
+  "20UPBRF1100101",
+  "20UPBRF1100201",
+  "20UPBRF1100301",
+  "20UPBRF1100401",
+  "20UPBRF1100501",
+  "20UPBRF1100601",
+  "20UPBRF1200101",
+  "20UPBRF1200201",
+  "20UPBRF1200301",
+  "20UPBRF1300101",
+  "20UPBRF1300201",
+  "20UPBRF1390101",
+  "20UPBRF1400301",
+  "20UPBRF1400502",
+  "20UPBRF1400702",
+  "20UPBRF1400901",
+  "20UPBRF1500101",
+  "20UPBRF1500201",
+  "20UPBRF1500301",
+  "20UPBRF1500401",
+  "20UPBRF1500501",
+  "20UPBRF1500601",
+  "20UPBRF1590101",
+  "20UPBRF2000100",
+  "20UPBRF2000200",
+  "20UPBRF2000300",
+  "20UPBRF2000400",
+  "20UPBRF2000500",
+  "20UPBRF2100100",
+  "20UPBRF2100200",
+  "20UPBRF2100300",
+  "20UPBRF2100400",
+  "20UPBRF2100500",
+  "20UPBRF2100600",
+  "20UPBRF2100700",
+  "20UPBRF2100800",
+  "20UPBRF2200100",
+  "20UPBRF2200200",
+  "20UPBRF2200300",
+  "20UPBRF2300100",
+  "20UPBRF2300200",
+  "20UPBRF2390100",
+  "20UPBRF2400100",
+  "20UPBRF2400200",
+  "20UPBRF2400300",
+  "20UPBRF2400400",
+  "20UPBRF2400500",
+  "20UPBRF2400600",
+  "20UPBRF2500100",
+  "20UPBRF2500200",
+  "20UPBRF2500300",
+  "20UPBRF2500400",
+  "20UPBRF2500500",
+  "20UPBRF2500600",
+  "20UPBRF2590100",
+  "20UPBRF2900100",
+  "20UPBTB0000031",
+  "20UPBYW0000001",
+  "20UPBZP0000001",
+  "20UPBZS0000001",
+  "20UPBZU0000001",
+  "20UPE200000001",
+  "20UPEBH0000001",
+  "20UPEBH2000002",
+  "20UPEBH2100001",
+  "20UPEBH2200003",
+  "20UPEBH2900001",
+  "20UPEBH4200002",
+  "20UPEEB9000001",
+  "20UPEEB9200001",
+  "20UPEEV2000001",
+  "20UPEEV2100005",
+  "20UPEEV2200001",
+  "20UPEEV2900001",
+  "20UPEEV3000001",
+  "20UPEEV3100001",
+  "20UPEEV3200001",
+  "20UPEEV4000001",
+  "20UPEEV4100001",
+  "20UPEEV4200001",
+  "20UPEHS2000001",
+  "20UPEHS2100001",
+  "20UPEHS2200001",
+  "20UPEHS3000001",
+  "20UPEHS3200001",
+  "20UPEHS4200001",
+  "20UPELS4900001",
+  "20UPEQR0000002",
+  "20UPESB0000002",
+  "20UPESF2000001",
+  "20UPESF3000001",
+  "20UPESF4000001",
+  "20UPESP2020005",
+  "20UPG2P1000001",
+  "20UPG2P2000001",
+  "20UPGB10000001",
+  "20UPGB10100001",
+  "20UPGB11200111",
+  "20UPGB12200001",
+  "20UPGB40000001",
+  "20UPGB40024601",
+  "20UPGB40100000",
+  "20UPGB40400001",
+  "20UPGB40500019",
+  "20UPGB41200001",
+  "20UPGB41301089",
+  "20UPGB41920023",
+  "20UPGB42000001",
+  "20UPGB42001005",
+  "20UPGB42002016",
+  "20UPGB42006004",
+  "20UPGB42007028",
+  "20UPGB42012001",
+  "20UPGB42099013",
+  "20UPGB42100001",
+  "20UPGB42102012",
+  "20UPGB42103009",
+  "20UPGB42105001",
+  "20UPGB42112008",
+  "20UPGB42200001",
+  "20UPGB42201006",
+  "20UPGB42202007",
+  "20UPGB42205017",
+  "20UPGB42207012",
+  "20UPGB42300002",
+  "20UPGB42301004",
+  "20UPGB42302001",
+  "20UPGB42303001",
+  "20UPGB42304001",
+  "20UPGB42305002",
+  "20UPGB42306001",
+  "20UPGB42307001",
+  "20UPGB42308001",
+  "20UPGB42312002",
+  "20UPGB42314001",
+  "20UPGB42900002",
+  "20UPGB42901008",
+  "20UPGB42902006",
+  "20UPGB42903003",
+  "20UPGB42904006",
+  "20UPGB42907008",
+  "20UPGB42912009",
+  "20UPGB43000001",
+  "20UPGB43109001",
+  "20UPGB43200001",
+  "20UPGB43309001",
+  "20UPGB43310001",
+  "20UPGB43311001",
+  "20UPGB43312007",
+  "20UPGB43313001",
+  "20UPGB43320001",
+  "20UPGB43321001",
+  "20UPGBQ0000001",
+  "20UPGBQ0100025",
+  "20UPGBQ1001012",
+  "20UPGBQ1301046",
+  "20UPGBQ1900001",
+  "20UPGBQ2000800",
+  "20UPGBQ2001F00",
+  "20UPGBQ2008016",
+  "20UPGBQ2101012",
+  "20UPGBQ2201045",
+  "20UPGBQ2900001",
+  "20UPGBQ3900001",
+  "20UPGBR0000001",
+  "20UPGBR0114825",
+  "20UPGBR9000001",
+  "20UPGBR9900001",
+  "20UPGBR9902056",
+  "20UPGBS0000001",
+  "20UPGBS0083299",
+  "20UPGBS0085156",
+  "20UPGBS0087082",
+  "20UPGBS0100001",
+  "20UPGBS1111111",
+  "20UPGBS2900001",
+  "20UPGBS2982798",
+  "20UPGBS2982830",
+  "20UPGBS3935959",
+  "20UPGBT0000001",
+  "20UPGBT2900001",
+  "20UPGBT9900001",
+  "20UPGFC0000000",
+  "20UPGFC0105433",
+  "20UPGFC0114821",
+  "20UPGFC0131094",
+  "20UPGFC0209431",
+  "20UPGFC1000000",
+  "20UPGFC9999991",
+  "20UPGFW0000003",
+  "20UPGHU3200001",
+  "20UPGHU5200001",
+  "20UPGM20012019",
+  "20UPGM20023043",
+  "20UPGM20024001",
+  "20UPGM20030003",
+  "20UPGM20121011",
+  "20UPGM20124015",
+  "20UPGM20521028",
+  "20UPGM21210410",
+  "20UPGM21301089",
+  "20UPGM22001120",
+  "20UPGM22101121",
+  "20UPGM22110005",
+  "20UPGM22200105",
+  "20UPGM22201249",
+  "20UPGM22210002",
+  "20UPGM22211032",
+  "20UPGM22601012",
+  "20UPGM22691013",
+  "20UPGM23110488",
+  "20UPGM23210060",
+  "20UPGM23601108",
+  "20UPGM23610020",
+  "20UPGMC0000000",
+  "20UPGMC0009999",
+  "20UPGMC0200007",
+  "20UPGMC0200116",
+  "20UPGMC0210001",
+  "20UPGMC0410002",
+  "20UPGMC0410120",
+  "20UPGMC0410235",
+  "20UPGMC0419302",
+  "20UPGMC0421301",
+  "20UPGMC0422336",
+  "20UPGMC0423270",
+  "20UPGMC0424291",
+  "20UPGMC0425304",
+  "20UPGMC0426262",
+  "20UPGMC0427300",
+  "20UPGMC0428290",
+  "20UPGMC0429303",
+  "20UPGMC0430298",
+  "20UPGMC0431267",
+  "20UPGMC0432295",
+  "20UPGMC0433306",
+  "20UPGMC0434264",
+  "20UPGMC0510501",
+  "20UPGMC0511202",
+  "20UPGMC0512501",
+  "20UPGMC0513033",
+  "20UPGMC0513300",
+  "20UPGMC2100000",
+  "20UPGMC2420071",
+  "20UPGMC3100000",
+  "20UPGOB2152401",
+  "20UPGOB2400003",
+  "20UPGOB3200032",
+  "20UPGOB4100267",
+  "20UPGOB4200039",
+  "20UPGOB4400038",
+  "20UPGOB4499996",
+  "20UPGOB5240007",
+  "20UPGOC3100028",
+  "20UPGOC3199999",
+  "20UPGOD0699909",
+  "20UPGOD0899909",
+  "20UPGOP0000001",
+  "20UPGOP2000019",
+  "20UPGOP2100043",
+  "20UPGPC2210002",
+  "20UPGPC2810005",
+  "20UPGPC3710003",
+  "20UPGPQ0000004",
+  "20UPGPQ0012001",
+  "20UPGPQ0020044",
+  "20UPGPQ0021003",
+  "20UPGPQ0022023",
+  "20UPGPQ0023062",
+  "20UPGPQ0024014",
+  "20UPGPQ0024129",
+  "20UPGPQ0025008",
+  "20UPGPQ0025183",
+  "20UPGPQ0029990",
+  "20UPGPQ0030001",
+  "20UPGPQ1001013",
+  "20UPGPQ1110329",
+  "20UPGPQ1202016",
+  "20UPGPQ1301030",
+  "20UPGPQ1501001",
+  "20UPGPQ1601012",
+  "20UPGPQ1691015",
+  "20UPGPQ2110001",
+  "20UPGPQ2210131",
+  "20UPGPQ2211133",
+  "20UPGPQ2601015",
+  "20UPGPQ2810003",
+  "20UPGPQ3602031",
+  "20UPGPQ3710010",
+  "20UPGPQ4210052",
+  "20UPGPQ4610029",
+  "20UPGPQ4616195",
+  "20UPGPQ4617065",
+  "20UPGPQ4620051",
+  "20UPGPQ5710023",
+  "20UPGPQ9099999",
+  "20UPGQF0000002",
+  "20UPGR90000004",
+  "20UPGR91110242",
+  "20UPGR92101011",
+  "20UPGR92110060",
+  "20UPGR92201045",
+  "20UPGR92901165",
+  "20UPGR92999999",
+  "20UPGR93110390",
+  "20UPGR93210363",
+  "20UPGR93602068",
+  "20UPGRQ9010003",
+  "20UPGS33300005",
+  "20UPGS33304461",
+  "20UPGS60100001",
+  "20UPGS65100001",
+  "20UPGS75000001",
+  "20UPGS75300022",
+  "20UPGS93300005",
+  "20UPGSU3400021",
+  "20UPGSU3500120",
+  "20UPGSU3600061",
+  "20UPGSU3700052",
+  "20UPGSU3800033",
+  "20UPGSU5100121",
+  "20UPGSU5400381",
+  "20UPGSU5500333",
+  "20UPGSU5600303",
+  "20UPGW33000031",
+  "20UPGW60000001",
+  "20UPGW61000003",
+  "20UPGW62000003",
+  "20UPGW71000001",
+  "20UPGW73000002",
+  "20UPGW75000004",
+  "20UPGW79000001",
+  "20UPGXB0000001",
+  "20UPGXB0100001",
+  "20UPGXB1000004",
+  "20UPGXB1200103",
+  "20UPGXB1278787",
+  "20UPGXB2000011",
+  "20UPGXB2099999",
+  "20UPGXB9000122",
+  "20UPGXC1000200",
+  "20UPGXF0000000",
+  "20UPGXF0082501",
+  "20UPGXF1000001",
+  "20UPGXM0000002",
+  "20UPGXM1000005",
+  "20UPGXM1017878",
+  "20UPGXM1207878",
+  "20UPGXM2099998",
+  "20UPGXM2600053",
+  "20UPGXM9856231",
+  "20UPGXP0000000",
+  "20UPGXP1000101",
+  "20UPGXP2099998",
+  "20UPGXP2600051",
+  "20UPGXS1100002",
+  "20UPGXS2200001",
+  "20UPGXS5000002",
+  "20UPGXS5300012",
+  "20UPGXS9000002",
+  "20UPGXT0000001",
+  "20UPGXT5100003",
+  "20UPGXT5400001",
+  "20UPGXT5500003",
+  "20UPGXT5600019",
+  "20UPGXW0000036",
+  "20UPICA0000001",
+  "20UPICA0100001",
+  "20UPICA1000001",
+  "20UPICA1100005",
+  "20UPICA1200004",
+  "20UPICA1300001",
+  "20UPICA1400001",
+  "20UPIHT2200001",
+  "20UPIHT5200009",
+  "20UPIHV7200013",
+  "20UPIHW7200037",
+  "20UPIM00000016",
+  "20UPIM02202111",
+  "20UPIM10012005",
+  "20UPIM12001159",
+  "20UPIM12110575",
+  "20UPIM12602133",
+  "20UPIM13602155",
+  "20UPIM52002147",
+  "20UPIMS0010003",
+  "20UPIMS0022002",
+  "20UPIMS2102133",
+  "20UPIP00000001",
+  "20UPIP01222002",
+  "20UPIP01292022",
+  "20UPIP03252113",
+  "20UPIP03272149",
+  "20UPIP19000001",
+  "20UPIP51202001",
+  "20UPIP51222006",
+  "20UPIP51292015",
+  "20UPIP53222151",
+  "20UPIP53264102",
+  "20UPIP53264111",
+  "20UPIP53272132",
+  "20UPIPT0000001",
+  "20UPIPT0021003",
+  "20UPIPT1252006",
+  "20UPIPT1562031",
+  "20UPIPT3222101",
+  "20UPIPT3264125",
+  "20UPIPT3272131",
+  "20UPIQR9000001",
+  "20UPIR62102005",
+  "20UPIR62202004",
+  "20UPIR62402013",
+  "20UPIR70000001",
+  "20UPIR72202133",
+  "20UPIR73202121",
+  "20UPIR82204114",
+  "20UPIR82302155",
+  "20UPIRS0000001",
+  "20UPIRU2202004",
+  "20UPIRV9122142",
+  "20UPIS07100025",
+  "20UPIS17100011",
+  "20UPIS17100112",
+  "20UPIS17100710",
+  "20UPIS18100276",
+  "20UPIS22100001",
+  "20UPIS22300013",
+  "20UPIS25300011",
+  "20UPISL1000001",
+  "20UPISL9000001",
+  "20UPISL9100001",
+  "20UPISS0000001",
+  "20UPISS9000002",
+  "20UPIST2100008",
+  "20UPIST2100100",
+  "20UPIST2200008",
+  "20UPIST2400065",
+  "20UPIST2400100",
+  "20UPIST5100104",
+  "20UPIST5400100",
+  "20UPIST5500102",
+  "20UPIST5600046",
+  "20UPISV7100041",
+  "20UPISV7200012",
+  "20UPISW7200083",
+  "20UPISW7900022",
+  "20UPISW8901038",
+  "20UPIW07000011",
+  "20UPIW17000020",
+  "20UPIW18000066",
+  "20UPIW22000007",
+  "20UPIW25000003"
+]