"""Module template was autogenerated from sharepoint list schema."""

from dataclasses import dataclass
from datetime import date, datetime
from enum import Enum
from typing import List, Optional

from aind_data_schema.procedures import (
    Anaesthetic,
    BrainInjection,
    CoordinateReferenceLocation,
    Craniotomy,
    CraniotomyType,
    FiberImplant,
    Headframe,
    InjectionMaterial,
    IontophoresisInjection,
    NanojectInjection,
    OphysProbe,
    ProbeName,
    Side,
    SubjectProcedure,
)
from aind_data_schema.subject import Sex

from aind_metadata_service.sharepoint.nsb2019.models import NSBList
from aind_metadata_service.sharepoint.nsb2019.models import (
    Procedure as NSBProcedure,
)


@dataclass
class HeadPostInfo:
    """Container for head post information"""

    headframe_type: Optional[str] = None
    headframe_part_number: Optional[str] = None
    well_type: Optional[str] = None
    well_part_number: Optional[str] = None


class InjectionType(Enum):
    """Enum class for Injection Types"""

    NANOJECT = "Nanoject"
    IONTOPHORESIS = "Iontophoresis"


class InjectionRound(Enum):
    """Enum class for Injection Rounds"""

    FIRST = "First"
    SECOND = "Second"


class MappedNSBList:
    """Mapped Fields in Sharepoint list"""

    def __init__(self, nsb: NSBList):
        """Class constructor"""
        self._nsb = nsb

    @staticmethod
    def _parse_basic_float_str(float_str: Optional[str]) -> Optional[float]:
        """Parse string representation of float such as '0.25'."""
        try:
            return None if float_str is None else float(float_str)
        except ValueError:
            return None

    @staticmethod
    def _parse_ap_str(_: Optional[str]) -> Optional[float]:
        """Parse AP String"""
        # TODO: Figure out how to parse AP fields
        return None

    @staticmethod
    def _parse_dv_str(_: Optional[str]) -> Optional[float]:
        """Parse dv String"""
        # TODO: Figure out how to parse DV fields
        return None

    @staticmethod
    def _parse_ml_str(_: Optional[str]) -> Optional[float]:
        """Parse ml string"""
        # TODO: Figure out how to parse ML fields
        return None

    @staticmethod
    def _parse_iso_dur_str(_: Optional[str]) -> Optional[float]:
        """Parse iso duration strings"""
        # TODO: Figure out how to parse Iso Duration fields
        return None

    def _parse_weight_str(self, weight_str: Optional[str]) -> Optional[float]:
        """Parse weight strings"""
        # Most entries are recorded as simple floats. There are some outliers.
        # But we can map those to None for the time being.
        return self._parse_basic_float_str(weight_str)

    @staticmethod
    def _parse_alt_time_str(_: Optional[str]) -> Optional[float]:
        """Parse alternating time strings"""
        # TODO: Figure out how to parse alternating time fields
        return None

    @staticmethod
    def _parse_angle_str(_: Optional[str]) -> Optional[float]:
        """Parse angle strings"""
        # TODO: Figure out how to parse angle fields
        return None

    @staticmethod
    def _parse_current_str(_: Optional[str]) -> Optional[float]:
        """Parse current strings"""
        # TODO: Figure out how to parse current fields
        return None

    @staticmethod
    def _parse_length_of_time_str(_: Optional[str]) -> Optional[float]:
        """Parse length of time strings"""
        # TODO: Figure out how to parse length of time fields
        return None

    @staticmethod
    def _parse_virus_strain_str(_: Optional[str]) -> Optional[str]:
        """Parse virus strain strings"""
        # TODO: Figure out how to parse virus strain fields
        return None

    @staticmethod
    def _parse_inj_vol_str(_: Optional[str]) -> Optional[float]:
        """Parse injection volume strings"""
        # TODO: Figure out how to parse injection volume fields
        return None

    @staticmethod
    def _parse_datetime_to_date(dt: Optional[datetime]) -> Optional[date]:
        """Parse date from datetime"""
        return None if dt is None else dt.date()

    @property
    def aind_age_at_injection(self) -> Optional[str]:
        """Maps age_at_injection to aind model"""
        return self._nsb.age_at_injection

    @property
    def aind_ap2nd_inj(self) -> Optional[float]:
        """Maps ap2nd_inj to aind model"""
        return self._parse_ap_str(self._nsb.ap2nd_inj)

    @property
    def aind_author_id(self) -> Optional[int]:
        """Maps author_id to aind model"""
        return self._nsb.author_id

    @property
    def aind_breg2_lamb(self) -> Optional[float]:
        """Maps breg2_lamb to aind model"""
        return self._parse_basic_float_str(self._nsb.breg2_lamb)

    @property
    def aind_cage(self) -> Optional[str]:
        """Maps cage to aind model"""
        return self._nsb.cage

    @property
    def aind_color_tag(self) -> Optional[str]:
        """Maps color_tag to aind model"""
        return self._nsb.color_tag

    @property
    def aind_compliance_asset_id(self) -> Optional[str]:
        """Maps compliance_asset_id to aind model"""
        return self._nsb.compliance_asset_id

    @property
    def aind_craniotomy_type(self) -> Optional[CraniotomyType]:
        """Maps craniotomy_type to aind model"""
        return (
            CraniotomyType.OTHER
            if self._nsb.craniotomy_type is None
            else {
                self._nsb.craniotomy_type.SELECT: CraniotomyType.OTHER,
                self._nsb.craniotomy_type.VISUAL_CORTEX_5MM: (
                    CraniotomyType.VISCTX
                ),
                self._nsb.craniotomy_type.FRONTAL_WINDOW_3MM: (
                    CraniotomyType.THREE_MM
                ),
                self._nsb.craniotomy_type.WHC_NP: CraniotomyType.WHC,
                self._nsb.craniotomy_type.WHC_2_P: CraniotomyType.WHC,
            }.get(self._nsb.craniotomy_type, CraniotomyType.OTHER)
        )

    @property
    def aind_created(self) -> Optional[datetime]:
        """Maps created to aind model"""
        return self._nsb.created

    @property
    def aind_date1st_injection(self) -> Optional[date]:
        """Maps date1st_injection to aind model"""
        return self._parse_datetime_to_date(self._nsb.date1st_injection)

    @property
    def aind_date2nd_injection(self) -> Optional[date]:
        """Maps date2nd_injection to aind model"""
        return self._parse_datetime_to_date(self._nsb.date2nd_injection)

    @property
    def aind_date_of_birth(self) -> Optional[date]:
        """Maps date_of_birth to aind model"""
        return self._parse_datetime_to_date(self._nsb.date_of_birth)

    @property
    def aind_date_of_surgery(self) -> Optional[date]:
        """Maps date_of_surgery to aind model"""
        return self._parse_datetime_to_date(self._nsb.date_of_surgery)

    @property
    def aind_date_range_end(self) -> Optional[datetime]:
        """Maps date_range_end to aind model"""
        return self._nsb.date_range_end

    @property
    def aind_date_range_start(self) -> Optional[datetime]:
        """Maps date_range_start to aind model"""
        return self._nsb.date_range_start

    @property
    def aind_dv2nd_inj(self) -> Optional[float]:
        """Maps dv2nd_inj to aind model"""
        return self._parse_dv_str(self._nsb.dv2nd_inj)

    @property
    def aind_editor_id(self) -> Optional[int]:
        """Maps editor_id to aind model"""
        return self._nsb.editor_id

    @property
    def aind_end_of_week(self) -> Optional[datetime]:
        """Maps end_of_week to aind model"""
        return self._nsb.end_of_week

    @property
    def aind_fiber_implant1(self) -> Optional[bool]:
        """Maps fiber_implant1 to aind model"""
        return self._nsb.fiber_implant1

    @property
    def aind_fiber_implant1_dv(self) -> Optional[float]:
        """Maps fiber_implant1_dv to aind model"""
        return self._parse_dv_str(self._nsb.fiber_implant1_dv)

    @property
    def aind_fiber_implant2(self) -> Optional[bool]:
        """Maps fiber_implant2 to aind model"""
        return self._nsb.fiber_implant2

    @property
    def aind_fiber_implant2_dv(self) -> Optional[float]:
        """Maps fiber_implant2_dv to aind model"""
        return self._parse_dv_str(self._nsb.fiber_implant2_dv)

    @property
    def aind_field30(self) -> Optional[str]:
        """Maps field30 to aind model"""
        return self._nsb.field30

    @property
    def aind_field50(self) -> Optional[str]:
        """Maps field50 to aind model"""
        return self._nsb.field50

    @property
    def aind_first_inj_recovery(self) -> Optional[float]:
        """Maps first_inj_recovery to aind model"""
        return self._nsb.first_inj_recovery

    @property
    def aind_first_injection_iso_durat(self) -> Optional[float]:
        """Maps first_injection_iso_durat to aind model"""
        return self._parse_iso_dur_str(self._nsb.first_injection_iso_durat)

    @property
    def aind_first_injection_weight_af(self) -> Optional[str]:
        """Maps first_injection_weight_af to aind model"""
        return self._parse_weight_str(self._nsb.first_injection_weight_af)

    @property
    def aind_first_injection_weight_be(self) -> Optional[str]:
        """Maps first_injection_weight_be to aind model"""
        return self._parse_weight_str(self._nsb.first_injection_weight_be)

    @property
    def aind_headpost_type(self) -> HeadPostInfo:
        """Maps headpost_type to aind model"""
        return (
            HeadPostInfo()
            if self._nsb.headpost_type is None
            else {
                self._nsb.headpost_type.SELECT: HeadPostInfo(),
                self._nsb.headpost_type.CAMSTYLE_HEADFRAME_016010: (
                    HeadPostInfo(
                        headframe_type="CAM-style",
                        headframe_part_number="0160-100-10 Rev A",
                        well_type="CAM-style",
                    )
                ),
                self._nsb.headpost_type.NEUROPIXELSTYLE_HEADFRAME: (
                    HeadPostInfo(
                        headframe_type="Neuropixel-style",
                        headframe_part_number="0160-100-10",
                        well_type="Neuropixel-style",
                        well_part_number="0160-200-36",
                    )
                ),
                self._nsb.headpost_type.MESOSCOPESTYLE_WELL_WITH: (
                    HeadPostInfo(
                        headframe_type="NGC-style",
                        headframe_part_number="0160-100-10",
                        well_type="Mesoscope-style",
                        well_part_number="0160-200-20",
                    )
                ),
                self._nsb.headpost_type.WHC_42_WITH_NEUROPIXEL_WE: (
                    HeadPostInfo(
                        headframe_type="WHC #42",
                        headframe_part_number="42",
                        well_type="Neuropixel-style",
                        well_part_number="0160-200-36",
                    )
                ),
                self._nsb.headpost_type.NGCSTYLE_HEADFRAME_NO_WEL: (
                    HeadPostInfo(
                        headframe_type="NGC-style",
                        headframe_part_number="0160-100-10",
                    )
                ),
                self._nsb.headpost_type.AI_STRAIGHT_HEADBAR: (
                    HeadPostInfo(headframe_type="AI Straight Headbar")
                ),
            }.get(self._nsb.headpost_type, HeadPostInfo())
        )

    @property
    def aind_hemisphere2nd_inj(self) -> Optional[Side]:
        """Maps hemisphere2nd_inj to aind model"""
        return (
            None
            if self._nsb.hemisphere2nd_inj is None
            else {
                self._nsb.hemisphere2nd_inj.SELECT: None,
                self._nsb.hemisphere2nd_inj.LEFT: Side.LEFT,
                self._nsb.hemisphere2nd_inj.RIGHT: Side.RIGHT,
            }.get(self._nsb.hemisphere2nd_inj, None)
        )

    @property
    def aind_hp_a_p(self) -> Optional[float]:
        """Maps hp_a_p to aind model"""
        return self._parse_ap_str(self._nsb.hp_a_p)

    @property
    def aind_hp_diameter(self) -> Optional[str]:
        """Maps hp_diameter to aind model"""
        return self._nsb.hp_diameter

    @property
    def aind_hp_durotomy(self) -> Optional[bool]:
        """Maps hp_durotomy to aind model"""
        return {
            self._nsb.hp_durotomy.SELECT: None,
            self._nsb.hp_durotomy.YES: True,
            self._nsb.hp_durotomy.NO: False,
        }.get(self._nsb.hp_durotomy, None)

    @property
    def aind_hp_inj(self) -> Optional[bool]:
        """Maps hp_inj to aind model"""
        return (
            True
            if self._nsb.hp_inj is not None and self._nsb.hp_inj == "Yes"
            else False
        )

    @property
    def aind_hp_iso_level(self) -> Optional[float]:
        """Maps hp_iso_level to aind model"""
        return (
            None
            if self._nsb.hp_iso_level is None
            else {
                self._nsb.hp_iso_level.SELECT: None,
                self._nsb.hp_iso_level.N_025: 0.25,
                self._nsb.hp_iso_level.N_050: 0.50,
                self._nsb.hp_iso_level.N_075: 0.75,
                self._nsb.hp_iso_level.N_100: 1.00,
                self._nsb.hp_iso_level.N_125: 1.25,
                self._nsb.hp_iso_level.N_15: 1.5,
                self._nsb.hp_iso_level.N_175: 1.75,
                self._nsb.hp_iso_level.N_200: 2.00,
                self._nsb.hp_iso_level.N_225: 2.25,
                self._nsb.hp_iso_level.N_250: 2.50,
                self._nsb.hp_iso_level.N_275: 2.75,
                self._nsb.hp_iso_level.N_300: None,
            }.get(self._nsb.hp_iso_level, None)
        )

    @property
    def aind_hp_loc(self) -> Optional[Side]:
        """Maps hp_loc to aind model"""
        return (
            None
            if self._nsb.hp_loc is None
            else {
                self._nsb.hp_loc.SELECT: None,
                self._nsb.hp_loc.LEFT: Side.LEFT,
                self._nsb.hp_loc.CENTER: None,
                self._nsb.hp_loc.RIGHT: Side.RIGHT,
            }.get(self._nsb.hp_loc, None)
        )

    @property
    def aind_hp_m_l(self) -> Optional[float]:
        """Maps hp_m_l to aind model"""
        return self._parse_ml_str(self._nsb.hp_m_l)

    @property
    def aind_hp_recovery(self) -> Optional[float]:
        """Maps hp_recovery to aind model"""
        return self._nsb.hp_recovery

    @property
    def aind_hp_requestor(self) -> Optional[str]:
        """Maps hp_requestor to aind model"""
        return self._nsb.hp_requestor

    @property
    def aind_hp_requestor_comments_pla(self) -> Optional[str]:
        """Maps hp_requestor_comments_pla to aind model"""
        return self._nsb.hp_requestor_comments_pla

    @property
    def aind_hp_surgeon_comments(self) -> Optional[str]:
        """Maps hp_surgeon_comments to aind model"""
        return self._nsb.hp_surgeon_comments

    @property
    def aind_hp_work_station(self) -> Optional[str]:
        """Maps hp_work_station to aind model"""
        return (
            None
            if self._nsb.hp_work_station is None
            else {
                self._nsb.hp_work_station.SELECT: None,
                self._nsb.hp_work_station.SWS_1: (
                    self._nsb.hp_work_station.SWS_1.value
                ),
                self._nsb.hp_work_station.SWS_2: (
                    self._nsb.hp_work_station.SWS_2.value
                ),
                self._nsb.hp_work_station.SWS_3: (
                    self._nsb.hp_work_station.SWS_3.value
                ),
                self._nsb.hp_work_station.SWS_4: (
                    self._nsb.hp_work_station.SWS_4.value
                ),
                self._nsb.hp_work_station.SWS_5: (
                    self._nsb.hp_work_station.SWS_5.value
                ),
                self._nsb.hp_work_station.SWS_6: (
                    self._nsb.hp_work_station.SWS_6.value
                ),
                self._nsb.hp_work_station.SWS_7: (
                    self._nsb.hp_work_station.SWS_7.value
                ),
                self._nsb.hp_work_station.SWS_8: (
                    self._nsb.hp_work_station.SWS_8.value
                ),
                self._nsb.hp_work_station.SWS_9: (
                    self._nsb.hp_work_station.SWS_9.value
                ),
            }.get(self._nsb.hp_work_station, None)
        )

    @property
    def aind_iacuc_protocol(self) -> Optional[str]:
        """Maps iacuc_protocol to aind model"""
        return (
            None
            if self._nsb.iacuc_protocol is None
            else {
                self._nsb.iacuc_protocol.SELECT: None,
                self._nsb.iacuc_protocol.N_2001: (
                    self._nsb.iacuc_protocol.N_2001.value
                ),
                self._nsb.iacuc_protocol.N_2002: (
                    self._nsb.iacuc_protocol.N_2002.value
                ),
                self._nsb.iacuc_protocol.N_2003: (
                    self._nsb.iacuc_protocol.N_2003.value
                ),
                self._nsb.iacuc_protocol.N_2004: (
                    self._nsb.iacuc_protocol.N_2004.value
                ),
                self._nsb.iacuc_protocol.N_2005: (
                    self._nsb.iacuc_protocol.N_2005.value
                ),
                self._nsb.iacuc_protocol.N_2006: (
                    self._nsb.iacuc_protocol.N_2006.value
                ),
                self._nsb.iacuc_protocol.N_2011: (
                    self._nsb.iacuc_protocol.N_2011.value
                ),
                self._nsb.iacuc_protocol.N_2102: (
                    self._nsb.iacuc_protocol.N_2102.value
                ),
                self._nsb.iacuc_protocol.N_2103: (
                    self._nsb.iacuc_protocol.N_2103.value
                ),
                self._nsb.iacuc_protocol.N_2104: (
                    self._nsb.iacuc_protocol.N_2104.value
                ),
                self._nsb.iacuc_protocol.N_2105: (
                    self._nsb.iacuc_protocol.N_2105.value
                ),
                self._nsb.iacuc_protocol.N_2106: (
                    self._nsb.iacuc_protocol.N_2106.value
                ),
                self._nsb.iacuc_protocol.N_2107: (
                    self._nsb.iacuc_protocol.N_2107.value
                ),
                self._nsb.iacuc_protocol.N_2108: (
                    self._nsb.iacuc_protocol.N_2108.value
                ),
                self._nsb.iacuc_protocol.N_2109: (
                    self._nsb.iacuc_protocol.N_2109.value
                ),
                self._nsb.iacuc_protocol.N_2110: (
                    self._nsb.iacuc_protocol.N_2110.value
                ),
                self._nsb.iacuc_protocol.N_2113: (
                    self._nsb.iacuc_protocol.N_2113.value
                ),
                self._nsb.iacuc_protocol.N_2115: (
                    self._nsb.iacuc_protocol.N_2115.value
                ),
                self._nsb.iacuc_protocol.N_2117: (
                    self._nsb.iacuc_protocol.N_2117.value
                ),
                self._nsb.iacuc_protocol.N_2201: (
                    self._nsb.iacuc_protocol.N_2201.value
                ),
                self._nsb.iacuc_protocol.N_2202: (
                    self._nsb.iacuc_protocol.N_2202.value
                ),
                self._nsb.iacuc_protocol.N_2205: (
                    self._nsb.iacuc_protocol.N_2205.value
                ),
                self._nsb.iacuc_protocol.N_2212: (
                    self._nsb.iacuc_protocol.N_2212.value
                ),
            }.get(self._nsb.iacuc_protocol, None)
        )

    @property
    def aind_id(self) -> Optional[int]:
        """Maps id to aind model"""
        return self._nsb.id

    @property
    def aind_inj1_alternating_time(self) -> Optional[float]:
        """Maps inj1_alternating_time to aind model"""
        return self._parse_alt_time_str(self._nsb.inj1_alternating_time)

    @property
    def aind_inj1_angle_v2(self) -> Optional[float]:
        """Maps inj1_angle_v2 to aind model"""
        return self._parse_angle_str(self._nsb.inj1_angle_v2)

    @property
    def aind_inj1_current(self) -> Optional[float]:
        """Maps inj1_current to aind model"""
        return self._parse_current_str(self._nsb.inj1_current)

    @property
    def aind_inj1_lenghtof_time(self) -> Optional[float]:
        """Maps inj1_lenghtof_time to aind model"""
        return self._parse_length_of_time_str(self._nsb.inj1_lenghtof_time)

    @property
    def aind_inj1_round(self) -> Optional[InjectionRound]:
        """Maps inj1_round to aind model"""
        return (
            None
            if self._nsb.inj1_round is None
            else {
                self._nsb.inj1_round.SELECT: None,
                self._nsb.inj1_round.N_1ST: InjectionRound.FIRST,
                self._nsb.inj1_round.N_2ND: InjectionRound.SECOND,
                self._nsb.inj1_round.NA: None,
            }.get(self._nsb.inj1_round, None)
        )

    @property
    def aind_inj1_storage_location(self) -> Optional[str]:
        """Maps inj1_storage_location to aind model"""
        return self._nsb.inj1_storage_location

    @property
    def aind_inj1_type(self) -> Optional[InjectionType]:
        """Maps inj1_type to aind model"""
        return (
            None
            if self._nsb.inj1_type is None
            else {
                self._nsb.inj1_type.SELECT: None,
                self._nsb.inj1_type.IONTOPHORESIS: InjectionType.IONTOPHORESIS,
                self._nsb.inj1_type.NANOJECT_PRESSURE: InjectionType.NANOJECT,
            }.get(self._nsb.inj1_type, None)
        )

    @property
    def aind_inj1_virus_strain_rt(self) -> Optional[str]:
        """Maps inj1_virus_strain_rt to aind model"""
        return self._parse_virus_strain_str(self._nsb.inj1_virus_strain_rt)

    @property
    def aind_inj1_vol(self) -> Optional[str]:
        """Maps inj1_vol to aind model"""
        return self._parse_inj_vol_str(self._nsb.inj1_vol)

    @property
    def aind_inj1angle0(self) -> Optional[float]:
        """Maps inj1angle0 to aind model"""
        return (
            None
            if self._nsb.inj1angle0 is None
            else {
                self._nsb.inj1angle0.SELECT: None,
                self._nsb.inj1angle0.N_0_DEGREES: 0,
                self._nsb.inj1angle0.N_10_DEGREES: 10,
                self._nsb.inj1angle0.N_15_DEGREES: 15,
                self._nsb.inj1angle0.N_20_DEGREES: 20,
                self._nsb.inj1angle0.N_30_DEGREES: 30,
                self._nsb.inj1angle0.N_40_DEGREES: 40,
            }.get(self._nsb.inj1angle0, None)
        )

    @property
    def aind_inj1volperdepth(self) -> Optional[float]:
        """Maps inj1volperdepth to aind model"""
        return self._parse_inj_vol_str(self._nsb.inj1volperdepth)

    @property
    def aind_inj2_alternating_time(self) -> Optional[float]:
        """Maps inj2_alternating_time to aind model"""
        return self._parse_alt_time_str(self._nsb.inj2_alternating_time)

    @property
    def aind_inj2_angle_v2(self) -> Optional[float]:
        """Maps inj2_angle_v2 to aind model"""
        return self._parse_angle_str(self._nsb.inj2_angle_v2)

    @property
    def aind_inj2_current(self) -> Optional[float]:
        """Maps inj2_current to aind model"""
        return self._parse_current_str(self._nsb.inj2_current)

    @property
    def aind_inj2_lenghtof_time(self) -> Optional[float]:
        """Maps inj2_lenghtof_time to aind model"""
        return self._parse_length_of_time_str(self._nsb.inj2_lenghtof_time)

    @property
    def aind_inj2_round(self) -> Optional[InjectionRound]:
        """Maps inj2_round to aind model"""
        return (
            None
            if self._nsb.inj2_round is None
            else {
                self._nsb.inj2_round.SELECT: None,
                self._nsb.inj2_round.N_1ST: InjectionRound.FIRST,
                self._nsb.inj2_round.N_2ND: InjectionRound.SECOND,
                self._nsb.inj2_round.NA: None,
            }.get(self._nsb.inj2_round, None)
        )

    @property
    def aind_inj2_storage_location(self) -> Optional[str]:
        """Maps inj2_storage_location to aind model"""
        return self._nsb.inj2_storage_location

    @property
    def aind_inj2_type(self) -> Optional[InjectionType]:
        """Maps inj2_type to aind model"""
        return (
            None
            if self._nsb.inj2_type is None
            else {
                self._nsb.inj2_type.SELECT: None,
                self._nsb.inj2_type.IONTOPHORESIS: InjectionType.IONTOPHORESIS,
                self._nsb.inj2_type.NANOJECT_PRESSURE: InjectionType.NANOJECT,
            }.get(self._nsb.inj2_type, None)
        )

    @property
    def aind_inj2_virus_strain_rt(self) -> Optional[str]:
        """Maps inj2_virus_strain_rt to aind model"""
        return self._parse_virus_strain_str(self._nsb.inj2_virus_strain_rt)

    @property
    def aind_inj2_vol(self) -> Optional[float]:
        """Maps inj2_vol to aind model"""
        return self._parse_inj_vol_str(self._nsb.inj2_vol)

    @property
    def aind_inj2angle0(self) -> Optional[float]:
        """Maps inj2angle0 to aind model"""
        return (
            None
            if self._nsb.inj2angle0 is None
            else {
                self._nsb.inj2angle0.SELECT: None,
                self._nsb.inj2angle0.N_0_DEGREES: 0,
                self._nsb.inj2angle0.N_10_DEGREES: 10,
                self._nsb.inj2angle0.N_15_DEGREES: 15,
                self._nsb.inj2angle0.N_20_DEGREES: 20,
                self._nsb.inj2angle0.N_30_DEGREES: 30,
                self._nsb.inj2angle0.N_40_DEGREES: 40,
            }.get(self._nsb.inj2angle0, None)
        )

    @property
    def aind_inj2volperdepth(self) -> Optional[float]:
        """Maps inj2volperdepth to aind model"""
        return self._parse_inj_vol_str(self._nsb.inj2volperdepth)

    @property
    def aind_ionto_number_hpinj(self) -> Optional[str]:
        """Maps ionto_number_hpinj to aind model"""
        return (
            None
            if self._nsb.ionto_number_hpinj is None
            else {
                self._nsb.ionto_number_hpinj.IONTO_1: (
                    self._nsb.ionto_number_hpinj.IONTO_1.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_2: (
                    self._nsb.ionto_number_hpinj.IONTO_2.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_3: (
                    self._nsb.ionto_number_hpinj.IONTO_3.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_4: (
                    self._nsb.ionto_number_hpinj.IONTO_4.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_5: (
                    self._nsb.ionto_number_hpinj.IONTO_5.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_6: (
                    self._nsb.ionto_number_hpinj.IONTO_6.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_7: (
                    self._nsb.ionto_number_hpinj.IONTO_7.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_8: (
                    self._nsb.ionto_number_hpinj.IONTO_8.value
                ),
                self._nsb.ionto_number_hpinj.IONTO_9: (
                    self._nsb.ionto_number_hpinj.IONTO_9.value
                ),
            }.get(self._nsb.ionto_number_hpinj, None)
        )

    @property
    def aind_ionto_number_inj1(self) -> Optional[str]:
        """Maps ionto_number_inj1 to aind model"""
        return (
            None
            if self._nsb.ionto_number_inj1 is None
            else {
                self._nsb.ionto_number_inj1.SELECT: None,
                self._nsb.ionto_number_inj1.IONTO_1: (
                    self._nsb.ionto_number_inj1.IONTO_1.value
                ),
                self._nsb.ionto_number_inj1.IONTO_2: (
                    self._nsb.ionto_number_inj1.IONTO_2.value
                ),
                self._nsb.ionto_number_inj1.IONTO_3: (
                    self._nsb.ionto_number_inj1.IONTO_3.value
                ),
                self._nsb.ionto_number_inj1.IONTO_4: (
                    self._nsb.ionto_number_inj1.IONTO_4.value
                ),
                self._nsb.ionto_number_inj1.IONTO_5: (
                    self._nsb.ionto_number_inj1.IONTO_5.value
                ),
                self._nsb.ionto_number_inj1.IONTO_6: (
                    self._nsb.ionto_number_inj1.IONTO_6.value
                ),
                self._nsb.ionto_number_inj1.IONTO_7: (
                    self._nsb.ionto_number_inj1.IONTO_7.value
                ),
                self._nsb.ionto_number_inj1.IONTO_8: (
                    self._nsb.ionto_number_inj1.IONTO_8.value
                ),
                self._nsb.ionto_number_inj1.IONTO_9: (
                    self._nsb.ionto_number_inj1.IONTO_9.value
                ),
                self._nsb.ionto_number_inj1.IONTO_10: (
                    self._nsb.ionto_number_inj1.IONTO_10.value
                ),
                self._nsb.ionto_number_inj1.NA: None,
            }.get(self._nsb.ionto_number_inj1, None)
        )

    @property
    def aind_ionto_number_inj2(self) -> Optional[str]:
        """Maps ionto_number_inj2 to aind model"""
        return (
            None
            if self._nsb.ionto_number_inj2 is None
            else {
                self._nsb.ionto_number_inj2.SELECT: None,
                self._nsb.ionto_number_inj2.IONTO_1: (
                    self._nsb.ionto_number_inj2.IONTO_1.value
                ),
                self._nsb.ionto_number_inj2.IONTO_2: (
                    self._nsb.ionto_number_inj2.IONTO_2.value
                ),
                self._nsb.ionto_number_inj2.IONTO_3: (
                    self._nsb.ionto_number_inj2.IONTO_3.value
                ),
                self._nsb.ionto_number_inj2.IONTO_4: (
                    self._nsb.ionto_number_inj2.IONTO_4.value
                ),
                self._nsb.ionto_number_inj2.IONTO_5: (
                    self._nsb.ionto_number_inj2.IONTO_5.value
                ),
                self._nsb.ionto_number_inj2.IONTO_6: (
                    self._nsb.ionto_number_inj2.IONTO_6.value
                ),
                self._nsb.ionto_number_inj2.IONTO_7: (
                    self._nsb.ionto_number_inj2.IONTO_7.value
                ),
                self._nsb.ionto_number_inj2.IONTO_8: (
                    self._nsb.ionto_number_inj2.IONTO_8.value
                ),
                self._nsb.ionto_number_inj2.IONTO_9: (
                    self._nsb.ionto_number_inj2.IONTO_9.value
                ),
                self._nsb.ionto_number_inj2.IONTO_10: (
                    self._nsb.ionto_number_inj2.IONTO_10.value
                ),
                self._nsb.ionto_number_inj2.NA: None,
            }.get(self._nsb.ionto_number_inj2, None)
        )

    @property
    def aind_iso_on(self) -> Optional[float]:
        """Maps iso_on to aind model"""
        return self._nsb.iso_on

    @property
    def aind_lab_tracks_group(self) -> Optional[str]:
        """Maps lab_tracks_group to aind model"""
        return self._nsb.lab_tracks_group

    @property
    def aind_lab_tracks_id(self) -> Optional[str]:
        """Maps lab_tracks_id to aind model"""
        return self._nsb.lab_tracks_id

    @property
    def aind_lab_tracks_requestor(self) -> Optional[str]:
        """Maps lab_tracks_requestor to aind model"""
        return self._nsb.lab_tracks_requestor

    @property
    def aind_lims_link(self) -> Optional[str]:
        """Maps lims_link to aind model"""
        return self._nsb.lims_link

    @property
    def aind_long1st_round_inj_cmts(self) -> Optional[str]:
        """Maps long1st_round_inj_cmts to aind model"""
        return self._nsb.long1st_round_inj_cmts

    @property
    def aind_long2nd_rnd_inj_cmts(self) -> Optional[str]:
        """Maps long2nd_rnd_inj_cmts to aind model"""
        return self._nsb.long2nd_rnd_inj_cmts

    @property
    def aind_long_requestor_comments(self) -> Optional[str]:
        """Maps long_requestor_comments to aind model"""
        return self._nsb.long_requestor_comments

    @property
    def aind_long_surgeon_comments(self) -> Optional[str]:
        """Maps long_surgeon_comments to aind model"""
        return self._nsb.long_surgeon_comments

    @property
    def aind_ml2nd_inj(self) -> Optional[float]:
        """Maps ml2nd_inj to aind model"""
        return self._parse_ml_str(self._nsb.ml2nd_inj)

    @property
    def aind_modified(self) -> Optional[datetime]:
        """Maps modified to aind model"""
        return self._nsb.modified

    @property
    def aind_nanoject_number_inj10(self) -> Optional[str]:
        """Maps nanoject_number_inj10 to aind model"""
        return (
            None
            if self._nsb.nanoject_number_inj10 is None
            else {
                self._nsb.nanoject_number_inj10.SELECT: None,
                self._nsb.nanoject_number_inj10.NJ1: (
                    self._nsb.nanoject_number_inj10.NJ1.value
                ),
                self._nsb.nanoject_number_inj10.NJ2: (
                    self._nsb.nanoject_number_inj10.NJ2.value
                ),
                self._nsb.nanoject_number_inj10.NJ3: (
                    self._nsb.nanoject_number_inj10.NJ3.value
                ),
                self._nsb.nanoject_number_inj10.NJ4: (
                    self._nsb.nanoject_number_inj10.NJ4.value
                ),
                self._nsb.nanoject_number_inj10.NJ5: (
                    self._nsb.nanoject_number_inj10.NJ5.value
                ),
                self._nsb.nanoject_number_inj10.NJ6: (
                    self._nsb.nanoject_number_inj10.NJ6.value
                ),
                self._nsb.nanoject_number_inj10.NJ7: (
                    self._nsb.nanoject_number_inj10.NJ7.value
                ),
                self._nsb.nanoject_number_inj10.NJ8: (
                    self._nsb.nanoject_number_inj10.NJ8.value
                ),
                self._nsb.nanoject_number_inj10.NA: None,
            }.get(self._nsb.nanoject_number_inj10, None)
        )

    @property
    def aind_nanoject_number_inj2(self) -> Optional[str]:
        """Maps nanoject_number_inj2 to aind model"""
        return (
            None
            if self._nsb.nanoject_number_inj2 is None
            else {
                self._nsb.nanoject_number_inj2.SELECT: None,
                self._nsb.nanoject_number_inj2.NJ1: (
                    self._nsb.nanoject_number_inj2.NJ1
                ),
                self._nsb.nanoject_number_inj2.NJ2: (
                    self._nsb.nanoject_number_inj2.NJ2
                ),
                self._nsb.nanoject_number_inj2.NJ3: (
                    self._nsb.nanoject_number_inj2.NJ3
                ),
                self._nsb.nanoject_number_inj2.NJ4: (
                    self._nsb.nanoject_number_inj2.NJ4
                ),
                self._nsb.nanoject_number_inj2.NJ5: (
                    self._nsb.nanoject_number_inj2.NJ5
                ),
                self._nsb.nanoject_number_inj2.NJ6: (
                    self._nsb.nanoject_number_inj2.NJ6
                ),
                self._nsb.nanoject_number_inj2.NJ7: (
                    self._nsb.nanoject_number_inj2.NJ7
                ),
                self._nsb.nanoject_number_inj2.NJ8: (
                    self._nsb.nanoject_number_inj2.NJ8
                ),
                self._nsb.nanoject_number_inj2.NA: None,
            }.get(self._nsb.nanoject_number_inj2, None)
        )

    @property
    def aind_nd_roung_injection_commen(self) -> Optional[str]:
        """Maps nd_roung_injection_commen to aind model"""
        return self._nsb.nd_roung_injection_commen

    @property
    def aind_pedigree_name(self) -> Optional[str]:
        """Maps pedigree_name to aind model"""
        return self._nsb.pedigree_name

    @property
    def aind_procedure(self) -> Optional[NSBProcedure]:
        """Maps procedure to aind model"""
        return self._nsb.procedure

    @property
    def aind_round1_inj_isolevel(self) -> Optional[float]:
        """Maps round1_inj_isolevel to aind model"""
        return (
            None
            if self._nsb.round1_inj_isolevel is None
            else {
                self._nsb.round1_inj_isolevel.SELECT: None,
                self._nsb.round1_inj_isolevel.N_025: 0.25,
                self._nsb.round1_inj_isolevel.N_050: 0.50,
                self._nsb.round1_inj_isolevel.N_075: 0.75,
                self._nsb.round1_inj_isolevel.N_100: 1.00,
                self._nsb.round1_inj_isolevel.N_125: 1.25,
                self._nsb.round1_inj_isolevel.N_150: 1.50,
                self._nsb.round1_inj_isolevel.N_175: 1.75,
                self._nsb.round1_inj_isolevel.N_200: 2.00,
                self._nsb.round1_inj_isolevel.N_225: 2.25,
                self._nsb.round1_inj_isolevel.N_250: 2.50,
                self._nsb.round1_inj_isolevel.N_275: 2.75,
                self._nsb.round1_inj_isolevel.N_300: None,
            }.get(self._nsb.round1_inj_isolevel, None)
        )

    @property
    def aind_round2_inj_isolevel(self) -> Optional[float]:
        """Maps round2_inj_isolevel to aind model"""
        return (
            None
            if self._nsb.round2_inj_isolevel is None
            else {
                self._nsb.round2_inj_isolevel.SELECT: None,
                self._nsb.round2_inj_isolevel.N_025: 0.25,
                self._nsb.round2_inj_isolevel.N_050: 0.50,
                self._nsb.round2_inj_isolevel.N_075: 0.75,
                self._nsb.round2_inj_isolevel.N_100: 1.00,
                self._nsb.round2_inj_isolevel.N_125: 1.25,
                self._nsb.round2_inj_isolevel.N_150: 1.50,
                self._nsb.round2_inj_isolevel.N_175: 1.75,
                self._nsb.round2_inj_isolevel.N_200: 2.00,
                self._nsb.round2_inj_isolevel.N_225: 2.25,
                self._nsb.round2_inj_isolevel.N_250: 2.50,
                self._nsb.round2_inj_isolevel.N_275: 2.75,
                self._nsb.round2_inj_isolevel.N_300: None,
            }.get(self._nsb.round2_inj_isolevel, None)
        )

    @property
    def aind_second_inj_recover(self) -> Optional[float]:
        """Maps second_inj_recover to aind model"""
        return self._nsb.second_inj_recover

    @property
    def aind_second_injection_iso_dura(self) -> Optional[float]:
        """Maps second_injection_iso_dura to aind model"""
        return self._parse_iso_dur_str(self._nsb.second_injection_iso_dura)

    @property
    def aind_second_injection_weight_a(self) -> Optional[float]:
        """Maps second_injection_weight_a to aind model"""
        return self._parse_weight_str(self._nsb.second_injection_weight_a)

    @property
    def aind_second_injection_weight_b(self) -> Optional[float]:
        """Maps second_injection_weight_b to aind model"""
        return self._parse_weight_str(self._nsb.second_injection_weight_b)

    @property
    def aind_sex(self) -> Optional[Sex]:
        """Maps sex to aind model"""
        return (
            None
            if self._nsb.sex is None
            else {
                self._nsb.sex.SELECT: None,
                self._nsb.sex.MALE: Sex.MALE,
                self._nsb.sex.FEMALE: Sex.FEMALE,
            }.get(self._nsb.sex, None)
        )

    @property
    def aind_st_round_injection_commen(self) -> Optional[str]:
        """Maps st_round_injection_commen to aind model"""
        return self._nsb.st_round_injection_commen

    @property
    def aind_start_of_week(self) -> Optional[datetime]:
        """Maps start_of_week to aind model"""
        return self._nsb.start_of_week

    @property
    def aind_title(self) -> Optional[str]:
        """Maps title to aind model"""
        return self._nsb.title

    @property
    def aind_touch_up_comp(self) -> Optional[datetime]:
        """Maps touch_up_comp to aind model"""
        return self._nsb.touch_up_comp

    @property
    def aind_touch_up_weight(self) -> Optional[float]:
        """Maps touch_up_weight to aind model"""
        return self._parse_weight_str(self._nsb.touch_up_weight)

    @property
    def aind_ui_version_string(self) -> Optional[str]:
        """Maps ui_version_string to aind model"""
        return self._nsb.ui_version_string

    @property
    def aind_virus_a_p(self) -> Optional[float]:
        """Maps virus_a_p to aind model"""
        return self._parse_ap_str(self._nsb.virus_a_p)

    @property
    def aind_virus_d_v(self) -> Optional[float]:
        """Maps virus_d_v to aind model"""
        return self._parse_dv_str(self._nsb.virus_d_v)

    @property
    def aind_virus_hemisphere(self) -> Optional[Side]:
        """Maps virus_hemisphere to aind model"""
        return (
            None
            if self._nsb.virus_hemisphere is None
            else {
                self._nsb.virus_hemisphere.SELECT: None,
                self._nsb.virus_hemisphere.LEFT: Side.LEFT,
                self._nsb.virus_hemisphere.RIGHT: Side.RIGHT,
            }.get(self._nsb.virus_hemisphere, None)
        )

    @property
    def aind_virus_m_l(self) -> Optional[float]:
        """Maps virus_m_l to aind model"""
        return self._parse_ml_str(self._nsb.virus_m_l)

    @property
    def aind_weight_after_surgery(self) -> Optional[float]:
        """Maps weight_after_surgery to aind model"""
        return self._parse_weight_str(self._nsb.weight_after_surgery)

    @property
    def aind_weight_before_surger(self) -> Optional[float]:
        """Maps weight_before_surger to aind model"""
        return self._parse_weight_str(self._nsb.weight_before_surger)

    @property
    def aind_work_station1st_injection(self) -> Optional[str]:
        """Maps work_station1st_injection to aind model"""
        return (
            None
            if self._nsb.work_station1st_injection is None
            else {
                self._nsb.work_station1st_injection.SELECT: None,
                self._nsb.work_station1st_injection.SWS_1: (
                    self._nsb.work_station1st_injection.SWS_1.value
                ),
                self._nsb.work_station1st_injection.SWS_2: (
                    self._nsb.work_station1st_injection.SWS_2.value
                ),
                self._nsb.work_station1st_injection.SWS_3: (
                    self._nsb.work_station1st_injection.SWS_3.value
                ),
                self._nsb.work_station1st_injection.SWS_4: (
                    self._nsb.work_station1st_injection.SWS_4.value
                ),
                self._nsb.work_station1st_injection.SWS_5: (
                    self._nsb.work_station1st_injection.SWS_5.value
                ),
                self._nsb.work_station1st_injection.SWS_6: (
                    self._nsb.work_station1st_injection.SWS_6.value
                ),
                self._nsb.work_station1st_injection.SWS_7: (
                    self._nsb.work_station1st_injection.SWS_7.value
                ),
                self._nsb.work_station1st_injection.SWS_8: (
                    self._nsb.work_station1st_injection.SWS_8.value
                ),
                self._nsb.work_station1st_injection.SWS_9: (
                    self._nsb.work_station1st_injection.SWS_9.value
                ),
            }.get(self._nsb.work_station1st_injection, None)
        )

    @property
    def aind_work_station2nd_injection(self) -> Optional[str]:
        """Maps work_station2nd_injection to aind model"""
        return (
            None
            if self._nsb.work_station2nd_injection is None
            else {
                self._nsb.work_station2nd_injection.SELECT: None,
                self._nsb.work_station2nd_injection.SWS_1: (
                    self._nsb.work_station2nd_injection.SWS_1.value
                ),
                self._nsb.work_station2nd_injection.SWS_2: (
                    self._nsb.work_station2nd_injection.SWS_2.value
                ),
                self._nsb.work_station2nd_injection.SWS_3: (
                    self._nsb.work_station2nd_injection.SWS_3.value
                ),
                self._nsb.work_station2nd_injection.SWS_4: (
                    self._nsb.work_station2nd_injection.SWS_4.value
                ),
                self._nsb.work_station2nd_injection.SWS_5: (
                    self._nsb.work_station2nd_injection.SWS_5.value
                ),
                self._nsb.work_station2nd_injection.SWS_6: (
                    self._nsb.work_station2nd_injection.SWS_6.value
                ),
                self._nsb.work_station2nd_injection.SWS_7: (
                    self._nsb.work_station2nd_injection.SWS_7.value
                ),
                self._nsb.work_station2nd_injection.SWS_8: (
                    self._nsb.work_station2nd_injection.SWS_8.value
                ),
                self._nsb.work_station2nd_injection.SWS_9: (
                    self._nsb.work_station2nd_injection.SWS_9.value
                ),
            }.get(self._nsb.work_station2nd_injection, None)
        )

    @property
    def aind_experimenter_full_name(self) -> str:
        """Map author id to experimenter name"""
        return (
            "NSB"
            if self.aind_author_id is None
            else f"NSB-{self.aind_author_id}"
        )

    @property
    def aind_anaesthetic_type(self) -> str:
        """Default anaesthetic type"""
        return "isoflurane"

    @property
    def aind_craniotomy_size(self) -> Optional[float]:
        """Map craniotomy type to size in mm"""
        return (
            None
            if self.aind_craniotomy_type is None
            else {
                self.aind_craniotomy_type.FIVE_MM: 5,
                self.aind_craniotomy_type.THREE_MM: 3,
            }.get(self.aind_craniotomy_type, None)
        )

    @property
    def aind_craniotomy_coordinates_reference(
        self,
    ) -> Optional[CoordinateReferenceLocation]:
        """Map craniotomy type to CoordinateReferenceLocation"""
        return (
            None
            if self.aind_craniotomy_type is None
            else {
                self.aind_craniotomy_type.VISCTX: (
                    CoordinateReferenceLocation.LAMBDA
                )
            }.get(self.aind_craniotomy_type, None)
        )

    @property
    def aind_inj1_coordinates_reference(
        self,
    ) -> Optional[CoordinateReferenceLocation]:
        """Map nsb inj1 field to CoordinateReferenceLocation"""
        if (
            self._nsb.virus_a_p is not None
            and "LAMBDA" in self._nsb.virus_a_p.upper()
        ):
            return CoordinateReferenceLocation.LAMBDA
        else:
            return None

    @property
    def aind_inj2_coordinates_reference(
        self,
    ) -> Optional[CoordinateReferenceLocation]:
        """Map nsb inj2 field to CoordinateReferenceLocation"""
        if (
            self._nsb.ap2nd_inj is not None
            and "LAMBDA" in self._nsb.ap2nd_inj.upper()
        ):
            return CoordinateReferenceLocation.LAMBDA
        else:
            return None

    def get_head_frame_procedure(self) -> Headframe:
        """Get head frame procedure"""
        return Headframe.construct(
            start_date=self.aind_date_of_surgery,
            end_date=self.aind_date_of_surgery,
            experimenter_full_name=self.aind_experimenter_full_name,
            iacuc_protocol=self.aind_iacuc_protocol,
            animal_weight_prior=self.aind_weight_before_surger,
            animal_weight_post=self.aind_weight_after_surgery,
            anaesthesia=Anaesthetic.construct(
                type=self.aind_anaesthetic_type,
                level=self.aind_hp_iso_level,
            ),
            headframe_type=self.aind_headpost_type.headframe_type,
            headframe_part_number=(
                self.aind_headpost_type.headframe_part_number
            ),
            well_part_number=self.aind_headpost_type.well_part_number,
            well_type=self.aind_headpost_type.well_type,
        )

    def get_craniotomy_procedure(self) -> Craniotomy:
        """Get craniotomy procedure"""
        return Craniotomy.construct(
            start_date=self.aind_date_of_surgery,
            end_date=self.aind_date_of_surgery,
            experimenter_full_name=self.aind_experimenter_full_name,
            iacuc_protocol=self.aind_iacuc_protocol,
            animal_weight_prior=self.aind_weight_before_surger,
            animal_weight_post=self.aind_weight_after_surgery,
            anaesthesia=Anaesthetic.construct(
                type=self.aind_anaesthetic_type,
                level=self.aind_hp_iso_level,
            ),
            craniotomy_type=self.aind_craniotomy_type,
            craniotomy_hemisphere=self.aind_hp_loc,
            craniotomy_coordinates_ml=self.aind_hp_m_l,
            craniotomy_coordinates_ap=self.aind_hp_a_p,
            craniotomy_coordinates_reference=(
                self.aind_craniotomy_coordinates_reference
            ),
            bregma_to_lambda_distance=self.aind_breg2_lamb,
            craniotomy_size=self.aind_craniotomy_size,
            dura_removed=self.aind_hp_durotomy,
            workstation_id=self.aind_hp_work_station,
        )

    def get_first_injection_procedure(self) -> BrainInjection:
        """Get first injection procedure"""
        if self.aind_inj1_type == InjectionType.NANOJECT:
            return NanojectInjection.construct(
                start_date=self.aind_date1st_injection,
                end_date=self.aind_date1st_injection,
                experimenter_full_name=self.aind_experimenter_full_name,
                animal_weight_prior=self.aind_first_injection_weight_be,
                animal_weight_post=self.aind_first_injection_weight_af,
                anaesthesia=Anaesthetic.construct(
                    type=self.aind_anaesthetic_type,
                    duration=self.aind_first_injection_iso_durat,
                    level=self.aind_hp_iso_level,
                ),
                injection_materials=(
                    [
                        InjectionMaterial.construct(
                            full_genome_name=self.aind_inj1_virus_strain_rt
                        )
                    ]
                ),
                recovery_time=self.aind_first_inj_recovery,
                injection_duration=self.aind_inj1_lenghtof_time,
                workstation_id=self.aind_work_station1st_injection,
                instrument_id=self.aind_nanoject_number_inj10,
                injection_coordinate_ml=self.aind_virus_m_l,
                injection_coordinate_ap=self.aind_virus_a_p,
                injection_coordinate_depth=self.aind_virus_d_v,
                injection_coordinate_reference=(
                    self.aind_inj1_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj1angle0,
                injection_hemisphere=self.aind_virus_hemisphere,
                injection_volume=self.aind_inj1_vol,
            )
        elif self.aind_inj1_type == InjectionType.IONTOPHORESIS:
            return IontophoresisInjection.construct(
                start_date=self.aind_date1st_injection,
                end_date=self.aind_date1st_injection,
                experimenter_full_name=self.aind_experimenter_full_name,
                animal_weight_prior=self.aind_first_injection_weight_be,
                animal_weight_post=self.aind_first_injection_weight_af,
                anaesthesia=Anaesthetic.construct(
                    type=self.aind_anaesthetic_type,
                    duration=self.aind_first_injection_iso_durat,
                    level=self.aind_hp_iso_level,
                ),
                injection_materials=(
                    [
                        InjectionMaterial.construct(
                            full_genome_name=self.aind_inj1_virus_strain_rt
                        )
                    ]
                ),
                recovery_time=self.aind_first_inj_recovery,
                injection_duration=self.aind_inj1_lenghtof_time,
                workstation_id=self.aind_work_station1st_injection,
                instrument_id=self.aind_ionto_number_inj1,
                injection_coordinate_ml=self.aind_virus_m_l,
                injection_coordinate_ap=self.aind_virus_a_p,
                injection_coordinate_depth=self.aind_virus_d_v,
                injection_coordinate_reference=(
                    self.aind_inj1_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj1angle0,
                injection_hemisphere=self.aind_virus_hemisphere,
                injection_current=self.aind_inj1_current,
                alternating_current=self.aind_inj1_alternating_time,
            )
        else:
            return BrainInjection.construct(
                start_date=self.aind_date1st_injection,
                end_date=self.aind_date1st_injection,
                experimenter_full_name=self.aind_experimenter_full_name,
                animal_weight_prior=self.aind_first_injection_weight_be,
                animal_weight_post=self.aind_first_injection_weight_af,
                anaesthesia=Anaesthetic.construct(
                    type=self.aind_anaesthetic_type,
                    duration=self.aind_first_injection_iso_durat,
                    level=self.aind_hp_iso_level,
                ),
                injection_materials=(
                    [
                        InjectionMaterial.construct(
                            full_genome_name=self.aind_inj1_virus_strain_rt
                        )
                    ]
                ),
                recovery_time=self.aind_first_inj_recovery,
                injection_duration=self.aind_inj1_lenghtof_time,
                workstation_id=self.aind_work_station1st_injection,
                injection_coordinate_ml=self.aind_virus_m_l,
                injection_coordinate_ap=self.aind_virus_a_p,
                injection_coordinate_depth=self.aind_virus_d_v,
                injection_coordinate_reference=(
                    self.aind_inj1_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj1angle0,
                injection_hemisphere=self.aind_virus_hemisphere,
            )

    def get_second_injection_procedure(self) -> BrainInjection:
        """Get second injection procedure"""
        if self.aind_inj2_type == InjectionType.NANOJECT:
            return NanojectInjection.construct(
                start_date=self.aind_date2nd_injection,
                end_date=self.aind_date2nd_injection,
                experimenter_full_name=self.aind_experimenter_full_name,
                animal_weight_prior=self.aind_second_injection_weight_b,
                animal_weight_post=self.aind_second_injection_weight_a,
                anaesthesia=Anaesthetic.construct(
                    type=self.aind_anaesthetic_type,
                    duration=self.aind_second_injection_iso_dura,
                    level=self.aind_hp_iso_level,
                ),
                injection_materials=(
                    [
                        InjectionMaterial.construct(
                            full_genome_name=self.aind_inj2_virus_strain_rt
                        )
                    ]
                ),
                recovery_time=self.aind_second_inj_recover,
                injection_duration=self.aind_inj2_lenghtof_time,
                workstation_id=self.aind_work_station2nd_injection,
                instrument_id=self.aind_nanoject_number_inj2,
                injection_coordinate_ml=self.aind_ml2nd_inj,
                injection_coordinate_ap=self.aind_ap2nd_inj,
                injection_coordinate_depth=self.aind_dv2nd_inj,
                injection_coordinate_reference=(
                    self.aind_inj2_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj2angle0,
                injection_hemisphere=self.aind_hemisphere2nd_inj,
                injection_volume=self.aind_inj2_vol,
            )
        elif self.aind_inj2_type == InjectionType.IONTOPHORESIS:
            return IontophoresisInjection.construct(
                start_date=self.aind_date2nd_injection,
                end_date=self.aind_date2nd_injection,
                experimenter_full_name=self.aind_experimenter_full_name,
                animal_weight_prior=self.aind_second_injection_weight_b,
                animal_weight_post=self.aind_second_injection_weight_a,
                anaesthesia=Anaesthetic.construct(
                    type=self.aind_anaesthetic_type,
                    duration=self.aind_second_injection_iso_dura,
                    level=self.aind_hp_iso_level,
                ),
                injection_materials=(
                    [
                        InjectionMaterial.construct(
                            full_genome_name=self.aind_inj2_virus_strain_rt
                        )
                    ]
                ),
                recovery_time=self.aind_second_inj_recover,
                injection_duration=self.aind_inj2_lenghtof_time,
                workstation_id=self.aind_work_station2nd_injection,
                instrument_id=self.aind_ionto_number_inj2,
                injection_coordinate_ml=self.aind_ml2nd_inj,
                injection_coordinate_ap=self.aind_ap2nd_inj,
                injection_coordinate_depth=self.aind_dv2nd_inj,
                injection_coordinate_reference=(
                    self.aind_inj2_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj2angle0,
                injection_hemisphere=self.aind_hemisphere2nd_inj,
                injection_current=self.aind_inj2_current,
                alternating_current=self.aind_inj2_alternating_time,
            )
        else:
            return BrainInjection.construct(
                start_date=self.aind_date2nd_injection,
                end_date=self.aind_date2nd_injection,
                experimenter_full_name=self.aind_experimenter_full_name,
                animal_weight_prior=self.aind_second_injection_weight_b,
                animal_weight_post=self.aind_second_injection_weight_a,
                anaesthesia=Anaesthetic.construct(
                    type=self.aind_anaesthetic_type,
                    duration=self.aind_second_injection_iso_dura,
                    level=self.aind_hp_iso_level,
                ),
                injection_materials=(
                    [
                        InjectionMaterial.construct(
                            full_genome_name=self.aind_inj2_virus_strain_rt
                        )
                    ]
                ),
                recovery_time=self.aind_second_inj_recover,
                injection_duration=self.aind_inj2_lenghtof_time,
                workstation_id=self.aind_work_station2nd_injection,
                injection_coordinate_ml=self.aind_ml2nd_inj,
                injection_coordinate_ap=self.aind_ap2nd_inj,
                injection_coordinate_depth=self.aind_dv2nd_inj,
                injection_coordinate_reference=(
                    self.aind_inj2_coordinates_reference
                ),
                bregma_to_lambda_distance=self.aind_breg2_lamb,
                injection_angle=self.aind_inj2angle0,
                injection_hemisphere=self.aind_hemisphere2nd_inj,
            )

    def get_fiber_implant(self):
        """Get a fiber implant procedure"""
        ophys_probes = []
        if self.aind_fiber_implant1:
            ophys_probes.append(
                OphysProbe.construct(
                    name=ProbeName.PROBE_A,
                    stereotactic_coordinate_ap=self.aind_virus_a_p,
                    stereotactic_coordinate_ml=self.aind_virus_m_l,
                    stereotactic_coordinate_dv=self.aind_fiber_implant1_dv,
                    stereotactic_coordinate_reference=(
                        self.aind_inj1_coordinates_reference
                    ),
                    bregma_to_lambda_distance=self.aind_breg2_lamb,
                    angle=self.aind_inj1_angle_v2,
                )
            )
        if self.aind_fiber_implant2:
            ophys_probes.append(
                OphysProbe.construct(
                    name=ProbeName.PROBE_B,
                    stereotactic_coordinate_ap=self.aind_ap2nd_inj,
                    stereotactic_coordinate_ml=self.aind_ml2nd_inj,
                    stereotactic_coordinate_dv=self.aind_fiber_implant2_dv,
                    stereotactic_coordinate_reference=(
                        self.aind_inj2_coordinates_reference
                    ),
                    bregma_to_lambda_distance=self.aind_breg2_lamb,
                    angle=self.aind_inj2_angle_v2,
                )
            )
        return FiberImplant.construct(
            start_date=self.aind_date_of_surgery,
            end_date=self.aind_date_of_surgery,
            experimenter_full_name=self.aind_experimenter_full_name,
            iacuc_protocol=self.aind_iacuc_protocol,
            animal_weight_prior=self.aind_weight_before_surger,
            animal_weight_post=self.aind_weight_after_surgery,
            probes=ophys_probes,
        )

    def get_basic_subject_procedure(self) -> SubjectProcedure:
        """Get a basic subject procedure"""
        return SubjectProcedure.construct(
            start_date=self.aind_date_of_surgery,
            end_date=self.aind_date_of_surgery,
            experimenter_full_name=self.aind_experimenter_full_name,
            iacuc_protocol=self.aind_iacuc_protocol,
            animal_weight_prior=self.aind_weight_before_surger,
            animal_weight_post=self.aind_weight_after_surgery,
        )

    @property
    def has_injection_procedure(self) -> bool:
        """Return true if injection procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.INJHPC: True,
                self._nsb.procedure.INJWHC_NP: True,
                self._nsb.procedure.HPINJ: True,
                self._nsb.procedure.HP_INJECTION_OPTIC_FIBER: True,
                self._nsb.procedure.STEREOTAXIC_INJECTION: True,
            }.get(self._nsb.procedure, False)

    @property
    def has_craniotomy_procedure(self) -> bool:
        """Return true if craniotomy procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.HPC_CAM: True,
                self._nsb.procedure.HPC_MULTISCOPE: True,
                self._nsb.procedure.HPC_NEUROPIXEL_STYLE: True,
                self._nsb.procedure.INJHPC: True,
                self._nsb.procedure.WHC_NP: True,
                self._nsb.procedure.INJWHC_NP: True,
            }.get(self._nsb.procedure, False)

    @property
    def has_head_frame_procedure(self) -> bool:
        """Return true if headframe procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.HPC_CAM: True,
                self._nsb.procedure.HPC_MULTISCOPE: True,
                self._nsb.procedure.HPC_NEUROPIXEL_STYLE: True,
                self._nsb.procedure.INJHPC: True,
                self._nsb.procedure.HPINJ: True,
                self._nsb.procedure.HP_TRANSCRANIAL_FOR_ISI: True,
                self._nsb.procedure.HP_ONLY: True,
                self._nsb.procedure.HP_INJECTION_OPTIC_FIBER: True,
            }.get(self._nsb.procedure, False)

    @property
    def has_fiber_implant_procedure(self) -> bool:
        """Return true if fiber implant procedure in nsb list"""
        if self._nsb.procedure is None:
            return False
        else:
            return {
                self._nsb.procedure.HP_INJECTION_OPTIC_FIBER: True,
            }.get(self._nsb.procedure, False)

    @property
    def has_unknown_procedures(self) -> bool:
        """Return true if no known procedures are found in nsb model"""
        if self._nsb.procedure is None:
            return True
        else:
            return not (
                self.has_injection_procedure
                or self.has_fiber_implant_procedure
                or self.has_craniotomy_procedure
                or self.has_head_frame_procedure
            )

    def get_procedures(self) -> List[SubjectProcedure]:
        """Get a list of subject procedures"""
        procedures = []
        if self.has_head_frame_procedure:
            procedures.append(self.get_head_frame_procedure())
        if self.has_injection_procedure:
            procedures.append(self.get_first_injection_procedure())
        if self.has_injection_procedure and self.aind_inj2_round is not None:
            procedures.append(self.get_second_injection_procedure())
        if self.has_craniotomy_procedure:
            procedures.append(self.get_craniotomy_procedure())
        if self.has_fiber_implant_procedure:
            procedures.append(self.get_fiber_implant())
        if self.has_unknown_procedures:
            procedures.append(self.get_basic_subject_procedure())
        return procedures
