Repository: WISDEM/ORBIT (language: python)

Issue:
Add gh-pages action



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/gh_pages.yml b/.github/workflows/gh_pages.yml
new file mode 100644
index 00000000..96e087f6
--- /dev/null
+++ b/.github/workflows/gh_pages.yml
@@ -0,0 +1,33 @@
+name: Documentation
+
+on:
+  push:
+    branches: [main, master]
+
+jobs:
+  make-pages:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: select python version
+        uses: actions/setup-python@v2
+        with:
+          python-version: '3.8'
+      - name: install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          python -m pip install .
+          python -m pip install sphinx
+          python -m pip install sphinx_rtd_theme
+          python -m pip install sphinx-click
+      - name: build documentation
+        run: |
+          cd docs
+          make html
+      - name: deploy
+        uses: peaceiris/actions-gh-pages@v3.6.1
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: ./docs/_build/html
+          force_orphan: true
+          full_commit_message: ${{ github.event.head_commit.message }}
diff --git a/.gitignore b/.gitignore
old mode 100755
new mode 100644
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 37f42497..d9667610 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,6 +13,7 @@ repos:
           name: black
           stages: [commit]
           language_version: python3.7
+          exclude: ^ORBIT/api/wisdem
 
 -   repo: https://github.com/pre-commit/pre-commit-hooks
     rev: v2.3.0
diff --git a/MANIFEST.in b/MANIFEST.in
index 7396e8fe..9ef8af42 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,3 @@
 include versioneer.py
 include ORBIT/_version.py
+include *.yaml
diff --git a/ORBIT/__init__.py b/ORBIT/__init__.py
index b7106c0d..c4e8f16a 100644
--- a/ORBIT/__init__.py
+++ b/ORBIT/__init__.py
@@ -6,7 +6,9 @@
 
 
 from .manager import ProjectManager  # isort:skip
+from .config import load_config, save_config
 from ._version import get_versions
+from .parametric import ParametricManager
 
 __version__ = get_versions()["version"]
 del get_versions
diff --git a/ORBIT/api/wisdem.py b/ORBIT/api/wisdem.py
new file mode 100644
index 00000000..a26a6c94
--- /dev/null
+++ b/ORBIT/api/wisdem.py
@@ -0,0 +1,416 @@
+"""WISDEM Monopile API"""
+
+__author__ = ["Jake Nunemaker"]
+__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
+__maintainer__ = "Jake Nunemaker"
+__email__ = "jake.nunemaker@nrel.gov"
+
+
+import openmdao.api as om
+
+from ORBIT import ProjectManager
+
+
+class Orbit(om.Group):
+    def initialize(self):
+        self.options.declare("floating", default=False)
+
+    def setup(self):
+
+        # Define all input variables from all models
+        self.set_input_defaults("wtiv", "example_wtiv")
+        self.set_input_defaults("feeder", "example_feeder")
+        self.set_input_defaults("num_feeders", 1)
+        self.set_input_defaults("num_towing", 1)
+        self.set_input_defaults("num_station_keeping", 3)
+        self.set_input_defaults("oss_install_vessel", "example_heavy_lift_vessel")
+        self.set_input_defaults("site_distance", 40.0, units="km")
+        self.set_input_defaults("site_distance_to_landfall", 40.0, units="km")
+        self.set_input_defaults("interconnection_distance", 40.0, units="km")
+        self.set_input_defaults("plant_turbine_spacing", 7)
+        self.set_input_defaults("plant_row_spacing", 7)
+        self.set_input_defaults("plant_substation_distance", 1, units="km")
+        self.set_input_defaults("num_port_cranes", 1)
+        self.set_input_defaults("num_assembly_lines", 1)
+        self.set_input_defaults("takt_time", 170.0, units="h")
+        self.set_input_defaults("port_cost_per_month", 2e6, units="USD/mo")
+        self.set_input_defaults("commissioning_pct", 0.01)
+        self.set_input_defaults("decommissioning_pct", 0.15)
+        self.set_input_defaults("site_auction_price", 100e6, units="USD")
+        self.set_input_defaults("site_assessment_plan_cost", 1e6, units="USD")
+        self.set_input_defaults("site_assessment_cost", 25e6, units="USD")
+        self.set_input_defaults("construction_operations_plan_cost", 2.5e6, units="USD")
+        self.set_input_defaults("design_install_plan_cost", 2.5e6, units="USD")
+        self.set_input_defaults("boem_review_cost", 0.0, units="USD")
+
+        self.add_subsystem("orbit", OrbitWisdem(floating=self.options["floating"]), promotes=["*"])
+
+
+class OrbitWisdem(om.ExplicitComponent):
+    """ORBIT-WISDEM Fixed Substructure API"""
+
+    def initialize(self):
+        self.options.declare("floating", default=False)
+
+    def setup(self):
+        """"""
+        # Inputs
+        # self.add_discrete_input('weather_file', 'block_island', desc='Weather file to use for installation times.')
+
+        # Vessels
+        self.add_discrete_input(
+            "wtiv", "example_wtiv", desc="Vessel configuration to use for installation of foundations and turbines."
+        )
+        self.add_discrete_input(
+            "feeder", "future_feeder", desc="Vessel configuration to use for (optional) feeder barges."
+        )
+        self.add_discrete_input(
+            "num_feeders", 1, desc="Number of feeder barges to use for installation of foundations and turbines."
+        )
+        self.add_discrete_input(
+            "num_towing",
+            1,
+            desc="Number of towing vessels to use for floating platforms that are assembled at port (with or without the turbine).",
+        )
+        self.add_discrete_input(
+            "num_station_keeping",
+            3,
+            desc="Number of station keeping vessels that attach to floating platforms under tow-out.",
+        )
+        self.add_discrete_input(
+            "oss_install_vessel",
+            "example_heavy_lift_vessel",
+            desc="Vessel configuration to use for installation of offshore substations.",
+        )
+
+        # Site
+        self.add_input("site_depth", 40.0, units="m", desc="Site depth.")
+        self.add_input("site_distance", 40.0, units="km", desc="Distance from site to installation port.")
+        self.add_input(
+            "site_distance_to_landfall", 50.0, units="km", desc="Distance from site to landfall for export cable."
+        )
+        self.add_input("interconnection_distance", 3.0, units="km", desc="Distance from landfall to interconnection.")
+        self.add_input("site_mean_windspeed", 9.0, units="m/s", desc="Mean windspeed of the site.")
+
+        # Plant
+        self.add_discrete_input("number_of_turbines", 60, desc="Number of turbines.")
+        self.add_input("plant_turbine_spacing", 7, desc="Turbine spacing in rotor diameters.")
+        self.add_input("plant_row_spacing", 7, desc="Row spacing in rotor diameters. Not used in ring layouts.")
+        self.add_input(
+            "plant_substation_distance", 1, units="km", desc="Distance from first turbine in string to substation."
+        )
+
+        # Turbine
+        self.add_input("turbine_rating", 8.0, units="MW", desc="Rated capacity of a turbine.")
+        self.add_input("turbine_rated_windspeed", 11.0, units="m/s", desc="Rated windspeed of the turbine.")
+        self.add_input("turbine_capex", 1100, units="USD/kW", desc="Turbine CAPEX")
+        self.add_input("hub_height", 100.0, units="m", desc="Turbine hub height.")
+        self.add_input("turbine_rotor_diameter", 130, units="m", desc="Turbine rotor diameter.")
+        self.add_input("tower_mass", 400.0, units="t", desc="mass of the total tower.")
+        self.add_input("tower_length", 100.0, units="m", desc="Total length of the tower.")
+        self.add_input(
+            "tower_deck_space",
+            0.0,
+            units="m**2",
+            desc="Deck space required to transport the tower. Defaults to 0 in order to not be a constraint on installation.",
+        )
+        self.add_input("nacelle_mass", 500.0, units="t", desc="mass of the rotor nacelle assembly (RNA).")
+        self.add_input(
+            "nacelle_deck_space",
+            0.0,
+            units="m**2",
+            desc="Deck space required to transport the rotor nacelle assembly (RNA). Defaults to 0 in order to not be a constraint on installation.",
+        )
+        self.add_discrete_input("number_of_blades", 3, desc="Number of blades per turbine.")
+        self.add_input("blade_mass", 50.0, units="t", desc="mass of an individual blade.")
+        self.add_input(
+            "blade_deck_space",
+            0.0,
+            units="m**2",
+            desc="Deck space required to transport a blade. Defaults to 0 in order to not be a constraint on installation.",
+        )
+
+        # Mooring
+        self.add_discrete_input("num_mooring_lines", 3, desc="Number of mooring lines per platform.")
+        self.add_input("mooring_line_mass", 1e4, units="kg", desc="Total mass of a mooring line")
+        self.add_input("mooring_line_diameter", 0.1, units="m", desc="Cross-sectional diameter of a mooring line")
+        self.add_input("mooring_line_length", 1e3, units="m", desc="Unstretched mooring line length")
+        self.add_input("anchor_mass", 1e4, units="kg", desc="Total mass of an anchor")
+        self.add_input("mooring_line_cost", 0.5e6, units="USD", desc="Mooring line unit cost.")
+        self.add_input("mooring_anchor_cost", 0.1e6, units="USD", desc="Mooring line unit cost.")
+        self.add_discrete_input("anchor_type", "drag_embedment", desc="Number of mooring lines per platform.")
+
+        # Port
+        self.add_input("port_cost_per_month", 2e6, units="USD/mo", desc="Monthly port costs.")
+        self.add_input(
+            "takt_time", 170.0, units="h", desc="Substructure assembly cycle time when doing assembly at the port."
+        )
+        self.add_discrete_input(
+            "num_assembly_lines", 1, desc="Number of assembly lines used when assembly occurs at the port."
+        )
+        self.add_discrete_input(
+            "num_port_cranes",
+            1,
+            desc="Number of cranes used at the port to load feeders / WTIVS when assembly occurs on-site or assembly cranes when assembling at port.",
+        )
+
+        # Floating Substructures
+        self.add_input("floating_substructure_cost", 10e6, units="USD", desc="Floating substructure unit cost.")
+
+        # Monopile
+        self.add_input("monopile_length", 100.0, units="m", desc="Length of monopile.")
+        self.add_input("monopile_diameter", 7.0, units="m", desc="Diameter of monopile.")
+        self.add_input("monopile_mass", 900.0, units="t", desc="mass of an individual monopile.")
+        self.add_input("monopile_cost", 4e6, units="USD", desc="Monopile unit cost.")
+        self.add_input(
+            "monopile_deck_space",
+            0.0,
+            units="m**2",
+            desc="Deck space required to transport a monopile. Defaults to 0 in order to not be a constraint on installation.",
+        )
+        self.add_input("transition_piece_mass", 250.0, units="t", desc="mass of an individual transition piece.")
+        self.add_input(
+            "transition_piece_deck_space",
+            0.0,
+            units="m**2",
+            desc="Deck space required to transport a transition piece. Defaults to 0 in order to not be a constraint on installation.",
+        )
+        self.add_input("transition_piece_cost", 1.5e6, units="USD", desc="Transition piece unit cost.")
+
+        # Project
+        self.add_input("site_auction_price", 100e6, units="USD", desc="Cost to secure site lease")
+        self.add_input(
+            "site_assessment_plan_cost", 1e6, units="USD", desc="Cost to do engineering plan for site assessment"
+        )
+        self.add_input("site_assessment_cost", 25e6, units="USD", desc="Cost to execute site assessment")
+        self.add_input("construction_operations_plan_cost", 2.5e6, units="USD", desc="Cost to do construction planning")
+        self.add_input(
+            "boem_review_cost",
+            0.0,
+            units="USD",
+            desc="Cost for additional review by U.S. Dept of Interior Bureau of Ocean Energy Management (BOEM)",
+        )
+        self.add_input("design_install_plan_cost", 2.5e6, units="USD", desc="Cost to do installation planning")
+
+        # Other
+        self.add_input("commissioning_pct", 0.01, desc="Commissioning percent.")
+        self.add_input("decommissioning_pct", 0.15, desc="Decommissioning percent.")
+
+        # Outputs
+        # Totals
+        self.add_output(
+            "bos_capex", 0.0, units="USD", desc="Total BOS CAPEX not including commissioning or decommissioning."
+        )
+        self.add_output(
+            "total_capex", 0.0, units="USD", desc="Total BOS CAPEX including commissioning and decommissioning."
+        )
+        self.add_output(
+            "total_capex_kW", 0.0, units="USD/kW", desc="Total BOS CAPEX including commissioning and decommissioning."
+        )
+        self.add_output("installation_time", 0.0, units="h", desc="Total balance of system installation time.")
+        self.add_output("installation_capex", 0.0, units="USD", desc="Total balance of system installation cost.")
+
+    def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_outputs):
+        """"""
+
+        floating = self.options["floating"]
+
+        config = {
+            # Vessels
+            "wtiv": "floating_heavy_lift_vessel" if floating else discrete_inputs["wtiv"],
+            "array_cable_install_vessel": "example_cable_lay_vessel",
+            "array_cable_bury_vessel": "example_cable_lay_vessel",
+            "export_cable_install_vessel": "example_cable_lay_vessel",
+            "export_cable_bury_vessel": "example_cable_lay_vessel",
+            # Site/plant
+            "site": {
+                "depth": float(inputs["site_depth"]),
+                "distance": float(inputs["site_distance"]),
+                "distance_to_landfall": float(inputs["site_distance_to_landfall"]),
+                "mean_windspeed": float(inputs["site_mean_windspeed"]),
+            },
+            "landfall": {
+                "interconnection_distance": float(inputs["interconnection_distance"]),
+            },
+            "plant": {
+                "layout": "grid",
+                "num_turbines": int(discrete_inputs["number_of_turbines"]),
+                "row_spacing": float(inputs["plant_row_spacing"]),
+                "turbine_spacing": float(inputs["plant_turbine_spacing"]),
+                "substation_distance": float(inputs["plant_substation_distance"]),
+            },
+            # Turbine + components
+            "turbine": {
+                "hub_height": float(inputs["hub_height"]),
+                "rotor_diameter": float(inputs["turbine_rotor_diameter"]),
+                "turbine_rating": float(inputs["turbine_rating"]),
+                "rated_windspeed": float(inputs["turbine_rated_windspeed"]),
+                "tower": {
+                    "type": "Tower",
+                    "deck_space": float(inputs["tower_deck_space"]),
+                    "mass": float(inputs["tower_mass"]),
+                    "length": float(inputs["tower_length"]),
+                },
+                "nacelle": {
+                    "type": "Nacelle",
+                    "deck_space": float(inputs["nacelle_deck_space"]),
+                    "mass": float(inputs["nacelle_mass"]),
+                },
+                "blade": {
+                    "type": "Blade",
+                    "number": int(discrete_inputs["number_of_blades"]),
+                    "deck_space": float(inputs["blade_deck_space"]),
+                    "mass": float(inputs["blade_mass"]),
+                },
+            },
+            # Substructure components
+            "transition_piece": {
+                "type": "Transition Piece",
+                "deck_space": float(inputs["transition_piece_deck_space"]),
+                "mass": float(inputs["transition_piece_mass"]),
+                "unit_cost": float(inputs["transition_piece_cost"]),
+            },
+            # Electrical
+            "array_system_design": {
+                "cables": ["XLPE_630mm_66kV", "XLPE_185mm_66kV"],
+            },
+            "export_system_design": {
+                "cables": "XLPE_1000m_220kV",
+                "interconnection_distance": float(inputs["interconnection_distance"]),
+                "percent_added_length": 0.1,
+            },
+            # Phase Specific
+            "OffshoreSubstationInstallation": {
+                "oss_install_vessel": "floating_heavy_lift_vessel" if floating else "example_heavy_lift_vessel",
+                "feeder": "floating_barge" if floating else "future_feeder",
+                "num_feeders": int(discrete_inputs["num_feeders"]),
+            },
+            # Project development costs
+            "project_development": {
+                "site_auction_price": float(inputs["site_auction_price"]),  # 100e6,
+                "site_assessment_plan_cost": float(inputs["site_assessment_plan_cost"]),  # 1e6,
+                "site_assessment_cost": float(inputs["site_assessment_cost"]),  # 25e6,
+                "construction_operations_plan_cost": float(inputs["construction_operations_plan_cost"]),  # 2.5e6,
+                "boem_review_cost": float(inputs["boem_review_cost"]),  # 0,
+                "design_install_plan_cost": float(inputs["design_install_plan_cost"]),  # 2.5e6
+            },
+            # Other
+            "commissioning": float(inputs["commissioning_pct"]),
+            "decomissioning": float(inputs["decommissioning_pct"]),
+            "turbine_capex": float(inputs["turbine_capex"]),
+            # Phases
+            # Putting monopile or semisub here would override the inputs we assume to get from WISDEM
+            "design_phases": [
+                #'MonopileDesign',
+                #'SemiSubmersibleDesign',
+                #'MooringSystemDesign',
+                #'ScourProtectionDesign',
+                "ArraySystemDesign",
+                "ExportSystemDesign",
+                "OffshoreSubstationDesign",
+            ],
+        }
+
+        # Unique design phases
+        if floating:
+            config["install_phases"] = {
+                "ExportCableInstallation": 0,
+                "OffshoreSubstationInstallation": 0,
+                "MooringSystemInstallation": 0,
+                "MooredSubInstallation": ("MooringSystemInstallation", 0.25),
+                "ArrayCableInstallation": ("MooredSubInstallation", 0.25),
+            }
+        else:
+            config["design_phases"] += ["ScourProtectionDesign"]
+            config["install_phases"] = {
+                "ExportCableInstallation": 0,
+                "OffshoreSubstationInstallation": 0,
+                "ArrayCableInstallation": 0,
+                "MonopileInstallation": 0,
+                "ScourProtectionInstallation": 0,
+                "TurbineInstallation": ("MonopileInstallation", 0.25),
+            }
+
+        # Unique vessels
+        if floating:
+            vessels = {
+                "support_vessel": "example_support_vessel",
+                "towing_vessel": "example_towing_vessel",
+                "mooring_install_vessel": "example_support_vessel",
+                "towing_vessel_groups": {
+                    "towing_vessels": int(discrete_inputs["num_towing"]),
+                    "station_keeping_vessels": int(discrete_inputs["num_station_keeping"]),
+                },
+            }
+        else:
+            vessels = {
+                "feeder": discrete_inputs["feeder"],
+                "num_feeders": int(discrete_inputs["num_feeders"]),
+                "spi_vessel": "example_scour_protection_vessel",
+            }
+        config.update(vessels)
+
+        # Unique support structure design/assembly
+        if floating:
+            config["port"] = {
+                "sub_assembly_lines": int(discrete_inputs["num_assembly_lines"]),
+                "turbine_assembly_cranes": int(discrete_inputs["num_port_cranes"]),
+                "monthly_rate": float(inputs["port_cost_per_month"]),
+            }
+
+            config["substructure"] = {
+                "takt_time": float(inputs["takt_time"]),
+                "unit_cost": float(inputs["floating_substructure_cost"]),
+                "towing_speed": 6,  # km/h
+            }
+
+            anchorstr_in = discrete_inputs["anchor_type"].lower()
+            if anchorstr_in.find("drag") >= 0:
+                anchorstr = "Drag Embedment"
+            elif anchorstr_in.find("suction") >= 0:
+                anchorstr = "Suction Pile"
+
+            config["mooring_system"] = {
+                "num_lines": int(discrete_inputs["num_mooring_lines"]),
+                "line_mass": 1e-3 * float(inputs["mooring_line_mass"]),
+                "line_diam": float(inputs["mooring_line_diameter"]),
+                "line_length": float(inputs["mooring_line_length"]),
+                "line_cost": float(inputs["mooring_line_cost"]),
+                "anchor_mass": 1e-3 * float(inputs["anchor_mass"]),
+                "anchor_type": anchorstr,
+                "anchor_cost": float(inputs["mooring_anchor_cost"]),
+            }
+        else:
+            config["port"] = {
+                "num_cranes": int(discrete_inputs["num_port_cranes"]),
+                "monthly_rate": float(inputs["port_cost_per_month"]),
+            }
+
+            config["monopile"] = {
+                "type": "Monopile",
+                "length": float(inputs["monopile_length"]),
+                "diameter": float(inputs["monopile_diameter"]),
+                "deck_space": float(inputs["monopile_deck_space"]),
+                "mass": float(inputs["monopile_mass"]),
+                "unit_cost": float(inputs["monopile_cost"]),
+            }
+
+            config["scour_protection_design"] = {
+                "cost_per_tonne": 20,
+            }
+
+        self._orbit_config = config
+        return config
+
+    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
+
+        config = self.compile_orbit_config_file(inputs, outputs, discrete_inputs, discrete_outputs)
+
+        project = ProjectManager(config)
+        project.run_project()
+
+        outputs["bos_capex"] = project.bos_capex
+        outputs["total_capex"] = project.total_capex
+        outputs["total_capex_kW"] = project.total_capex_per_kw
+        outputs["installation_time"] = project.installation_time
+        outputs["installation_capex"] = project.installation_capex
diff --git a/ORBIT/api/wisdem/__init__.py b/ORBIT/api/wisdem/__init__.py
deleted file mode 100644
index 17bbd1ab..00000000
--- a/ORBIT/api/wisdem/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""WISDEM-ORBIT API's"""
-
-__author__ = ["Jake Nunemaker"]
-__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
-__maintainer__ = "Jake Nunemaker"
-__email__ = "jake.nunemaker@nrel.gov"
-
-
-from .fixed import OrbitWisdemFixed
diff --git a/ORBIT/api/wisdem/fixed.py b/ORBIT/api/wisdem/fixed.py
deleted file mode 100644
index 269e751f..00000000
--- a/ORBIT/api/wisdem/fixed.py
+++ /dev/null
@@ -1,263 +0,0 @@
-"""WISDEM Monopile API"""
-
-__author__ = ["Jake Nunemaker"]
-__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
-__maintainer__ = "Jake Nunemaker"
-__email__ = "jake.nunemaker@nrel.gov"
-
-
-import os
-
-import yaml
-import openmdao.api as om
-
-from ORBIT import ProjectManager
-
-class Orbit(om.Group):
-    def setup(self):
-        
-        # Define all input variables from all models
-        myIndeps = om.IndepVarComp()
-        myIndeps.add_discrete_output('wtiv', 'example_wtiv')
-        myIndeps.add_discrete_output('feeder', 'example_feeder')
-        myIndeps.add_discrete_output('num_feeders', 0)
-        myIndeps.add_discrete_output('oss_install_vessel', 'example_heavy_lift_vessel')
-        myIndeps.add_output('site_distance', 0.0, units='km')
-        myIndeps.add_output('site_distance_to_landfall', 40.0, units='km')
-        myIndeps.add_output('site_distance_to_interconnection', 40.0, units='km')
-        myIndeps.add_output('plant_turbine_spacing', 7)
-        myIndeps.add_output('plant_row_spacing', 7)
-        myIndeps.add_output('plant_substation_distance', 1, units='km')
-        myIndeps.add_output('tower_deck_space', 0., units='m**2')
-        myIndeps.add_output('nacelle_deck_space', 0., units='m**2')
-        myIndeps.add_output('blade_deck_space', 0., units='m**2')
-        myIndeps.add_output('port_cost_per_month', 2e6, units='USD/mo')
-        myIndeps.add_output('monopile_deck_space', 0., units='m**2')
-        myIndeps.add_output('transition_piece_deck_space', 0., units='m**2')
-        myIndeps.add_output('commissioning_pct', 0.01)
-        myIndeps.add_output('decommissioning_pct', 0.15)
-        self.add_subsystem('myIndeps', myIndeps, promotes=['*'])
-
-        self.add_subsystem('orbit', OrbitWisdemFixed(), promotes=['*'])
-        
-
-class OrbitWisdemFixed(om.ExplicitComponent):
-    """ORBIT-WISDEM Fixed Substructure API"""
-
-    def setup(self):
-        """"""
-        # Inputs
-        # self.add_discrete_input('weather_file', 'block_island', desc='Weather file to use for installation times.')
-
-        # Vessels
-        self.add_discrete_input('wtiv', 'example_wtiv', desc='Vessel configuration to use for installation of foundations and turbines.')
-        self.add_discrete_input('feeder', 'future_feeder', desc='Vessel configuration to use for (optional) feeder barges.')
-        self.add_discrete_input('num_feeders', 0, desc='Number of feeder barges to use for installation of foundations and turbines.')
-        self.add_discrete_input('oss_install_vessel', 'example_heavy_lift_vessel', desc='Vessel configuration to use for installation of offshore substations.')
-
-        # Site
-        self.add_input('site_depth', 40., units='m', desc='Site depth.')
-        self.add_input('site_distance', 40., units='km', desc='Distance from site to installation port.')
-        self.add_input('site_distance_to_landfall', 50., units='km', desc='Distance from site to landfall for export cable.')
-        self.add_input('interconnection_distance', 3., units='km', desc='Distance from landfall to interconnection.')
-        self.add_input('site_mean_windspeed', 9., units='m/s', desc='Mean windspeed of the site.')
-
-        # Plant
-        self.add_discrete_input('number_of_turbines', 60, desc='Number of turbines.')
-        self.add_input('plant_turbine_spacing', 7, desc='Turbine spacing in rotor diameters.')
-        self.add_input('plant_row_spacing', 7, desc='Row spacing in rotor diameters. Not used in ring layouts.')
-        self.add_input('plant_substation_distance', 1, units='km', desc='Distance from first turbine in string to substation.')
-
-        # Turbine
-        self.add_input('turbine_rating', 8., units='MW', desc='Rated capacity of a turbine.')
-        self.add_input('turbine_rated_windspeed', 11., units='m/s', desc='Rated windspeed of the turbine.')
-        self.add_input('turbine_capex', 1100, units='USD/kW', desc='Turbine CAPEX')
-        self.add_input('hub_height', 100., units='m', desc='Turbine hub height.')
-        self.add_input('turbine_rotor_diameter', 130, units='m', desc='Turbine rotor diameter.')
-        self.add_input('tower_mass', 400., units='t', desc='mass of the total tower.')
-        self.add_input('tower_length', 100., units='m', desc='Total length of the tower.')
-        self.add_input('tower_deck_space', 0., units='m**2', desc='Deck space required to transport the tower. Defaults to 0 in order to not be a constraint on installation.')
-        self.add_input('nacelle_mass', 500., units='t', desc='mass of the rotor nacelle assembly (RNA).')
-        self.add_input('nacelle_deck_space', 0., units='m**2', desc='Deck space required to transport the rotor nacelle assembly (RNA). Defaults to 0 in order to not be a constraint on installation.')
-        self.add_discrete_input('number_of_blades', 3, desc='Number of blades per turbine.')
-        self.add_input('blade_mass', 50., units='t', desc='mass of an individual blade.')
-        self.add_input('blade_deck_space', 0., units='m**2', desc='Deck space required to transport a blade. Defaults to 0 in order to not be a constraint on installation.')
-
-        # Port
-        self.add_input('port_cost_per_month', 2e6, units='USD/mo', desc='Monthly port costs.')
-
-        # Monopile
-        self.add_input('monopile_length', 100., units='m', desc='Length of monopile.')
-        self.add_input('monopile_diameter', 7., units='m', desc='Diameter of monopile.')
-        self.add_input('monopile_mass', 900., units='t', desc='mass of an individual monopile.')
-        self.add_input('monopile_deck_space', 0., units='m**2', desc='Deck space required to transport a monopile. Defaults to 0 in order to not be a constraint on installation.')
-        self.add_input('transition_piece_mass', 250., units='t', desc='mass of an individual transition piece.')
-        self.add_input('transition_piece_deck_space', 0., units='m**2', desc='Deck space required to transport a transition piece. Defaults to 0 in order to not be a constraint on installation.')
-
-        # Other
-        self.add_input('commissioning_pct', 0.01, desc="Commissioning percent.")
-        self.add_input('decommissioning_pct', 0.15, desc="Decommissioning percent.")
-
-        # Outputs
-        # Totals
-        self.add_output('bos_capex', 0.0, units='USD', desc='Total BOS CAPEX not including commissioning or decommissioning.')
-        self.add_output('total_capex', 0.0, units='USD', desc='Total BOS CAPEX including commissioning and decommissioning.')
-        self.add_output('total_capex_kW', 0.0, units='USD/kW', desc='Total BOS CAPEX including commissioning and decommissioning.')
-        self.add_output('installation_time', 0.0, units='h', desc='Total balance of system installation time.')
-        self.add_output('installation_capex', 0.0, units='USD', desc='Total balance of system installation cost.')
-
-
-    def compile_orbit_config_file(self, inputs, outputs, discrete_inputs, discrete_outputs):
-        """"""
-
-        config = {
-            # Vessels
-            'wtiv': discrete_inputs['wtiv'],
-            'feeder': discrete_inputs['feeder'],
-            'num_feeders': discrete_inputs['num_feeders'],
-            'spi_vessel': 'example_scour_protection_vessel',
-            'array_cable_install_vessel': 'example_cable_lay_vessel',
-            'array_cable_bury_vessel': 'example_cable_lay_vessel',
-            'export_cable_install_vessel': 'example_cable_lay_vessel',
-            'export_cable_bury_vessel': 'example_cable_lay_vessel',
-            
-            # Site/plant
-            'site': {
-                'depth': float(inputs['site_depth']),
-                'distance': float(inputs['site_distance']),
-                'distance_to_landfall': float(inputs['site_distance_to_landfall']),
-                'mean_windspeed': float(inputs['site_mean_windspeed'])
-            },
-
-            'landfall': {
-                'interconnection_distance': float(inputs['interconnection_distance']),
-            },
-            
-            'plant': {
-                'layout': 'grid',
-                'num_turbines': discrete_inputs['number_of_turbines'],
-                'row_spacing': float(inputs['plant_row_spacing']),
-                'turbine_spacing': float(inputs['plant_turbine_spacing']),
-                'substation_distance': float(inputs['plant_substation_distance'])
-            },
-            
-            'port': {
-                'num_cranes': 1,
-                'monthly_rate': float(inputs['port_cost_per_month'])
-            },
-            
-            # Turbine + components
-            'turbine': {
-                'hub_height': float(inputs['hub_height']),
-                'rotor_diameter': float(inputs['turbine_rotor_diameter']),
-                'turbine_rating': float(inputs['turbine_rating']),
-                'rated_windspeed': float(inputs['turbine_rated_windspeed']),
-                'tower': {
-                    'type': 'Tower',
-                    'deck_space': float(inputs['tower_deck_space']),
-                    'mass': float(inputs['tower_mass']),
-                    'length': float(inputs['tower_length'])
-                },
-                
-                'nacelle': {
-                    'type': 'Nacelle',
-                    'deck_space': float(inputs['nacelle_deck_space']),
-                    'mass': float(inputs['nacelle_mass'])
-                },
-                
-                'blade': {
-                    'type': 'Blade',
-                    'number': float(discrete_inputs['number_of_blades']),
-                    'deck_space': float(inputs['blade_deck_space']),
-                    'mass': float(inputs['blade_mass'])
-                }
-            },
-
-            # Substructure components
-            'monopile': {
-                'type': 'Monopile',
-                'length': float(inputs['monopile_length']),
-                'diameter': float(inputs['monopile_diameter']),
-                'deck_space': float(inputs['monopile_deck_space']),
-                'mass': float(inputs['monopile_mass'])
-            },
-            
-            'transition_piece': {
-                'type': 'Transition Piece',
-                'deck_space': float(inputs['transition_piece_deck_space']),
-                'mass': float(inputs['transition_piece_mass'])
-            },
-            
-            'scour_protection_design': {
-                'cost_per_tonne': 20,
-            },
-            
-            # Electrical
-            'array_system_design': {
-                'cables': ['XLPE_400mm_33kV', 'XLPE_630mm_33kV']
-            },
-
-            'export_system_design': {
-                'cables': 'XLPE_500mm_132kV',
-                'percent_added_length': .1
-            },
-            
-            # Phase Specific
-            "OffshoreSubstationInstallation": {
-                "oss_install_vessel": 'example_heavy_lift_vessel',
-                "feeder": "future_feeder",
-                "num_feeders": 1
-            },
-
-            # Other
-            "commissioning": float(inputs["commissioning_pct"]),
-            "decomissioning": float(inputs["decommissioning_pct"]),
-            "turbine_capex": float(inputs["turbine_capex"]),
-            
-            # Phases
-            'design_phases': [
-                "MonopileDesign",
-                "ScourProtectionDesign",
-                "ArraySystemDesign",
-                "ExportSystemDesign",
-                "OffshoreSubstationDesign"
-            ],
-            
-            'install_phases': [
-                'MonopileInstallation',
-                'ScourProtectionInstallation',
-                'TurbineInstallation',
-                'ArrayCableInstallation',
-                'ExportCableInstallation',
-                "OffshoreSubstationInstallation",
-            ]
-        }
-
-        self._orbit_config = config
-        return config
-
-    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):
-        
-        config = self.compile_orbit_config_file(inputs, outputs,
-                                                discrete_inputs,
-                                                discrete_outputs)
-
-        project = ProjectManager(config)
-        project.run_project()
-
-        outputs['bos_capex'] = project.bos_capex
-        outputs['total_capex'] = project.total_capex
-        outputs['total_capex_kW'] = project.total_capex_per_kw
-        outputs['installation_time'] = project.installation_time
-        outputs['installation_capex'] = project.installation_capex
-
-if __name__ == "__main__":
-
-    prob = om.Problem()
-    prob.model = OrbitWisdemFixed()
-    prob.setup()
-
-    prob.run_driver()
-
-    prob.model.list_inputs()
-    prob.model.list_outputs()
diff --git a/ORBIT/config.py b/ORBIT/config.py
new file mode 100644
index 00000000..4a50732d
--- /dev/null
+++ b/ORBIT/config.py
@@ -0,0 +1,54 @@
+__author__ = "Jake Nunemaker"
+__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
+__maintainer__ = "Jake Nunemaker"
+__email__ = "jake.nunemaker@nrel.gov"
+
+
+import os
+
+import yaml
+from yaml import Dumper
+
+from ORBIT.core import loader
+
+
+def load_config(filepath):
+    """
+    Load an ORBIT config at `filepath`.
+
+    Parameters
+    ----------
+    filepath : str
+        Path to yaml config file.
+    """
+
+    with open(filepath, "r") as f:
+        data = yaml.load(f, Loader=loader)
+
+    return data
+
+
+def save_config(config, filepath, overwrite=False):
+    """
+    Save an ORBIT `config` to `filepath`.
+
+    Parameters
+    ----------
+    config : dict
+        ORBIT configuration.
+    filepath : str
+        Location to save config.
+    overwrite : bool (optional)
+        Overwrite file if it already exists. Default: False.
+    """
+
+    dirs = os.path.split(filepath)[0]
+    if dirs and not os.path.isdir(dirs):
+        os.makedirs(dirs)
+
+    if overwrite is False:
+        if os.path.exists(filepath):
+            raise FileExistsError(f"File already exists at '{filepath}'.")
+
+    with open(filepath, "w") as f:
+        yaml.dump(config, f, Dumper=Dumper, default_flow_style=False)
diff --git a/ORBIT/core/__init__.py b/ORBIT/core/__init__.py
index a714c39b..8b6e49c5 100644
--- a/ORBIT/core/__init__.py
+++ b/ORBIT/core/__init__.py
@@ -9,5 +9,6 @@
 from .port import Port, WetStorage
 from .cargo import Cargo
 from .vessel import Vessel
+from .library import loader
 from .components import Crane, JackingSys
 from .environment import OrbitEnvironment as Environment
diff --git a/ORBIT/core/_defaults.py b/ORBIT/core/_defaults.py
deleted file mode 100755
index 40ddd6e8..00000000
--- a/ORBIT/core/_defaults.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""
-Jake Nunemaker
-National Renewable Energy Lab
-07/11/2019
-
-This module contains default vessel process times.
-"""
-
-
-process_times = {
-    # Export Cable Installation
-    "onshore_construction_time": 48,  # hr
-    "trench_dig_speed": 0.1,  # km/hr
-    "pull_winch_speed": 5,  # km/hr
-    "tow_plow_speed": 5,  # km/hr
-    # Array Cable Installation
-    # General Cable Installation
-    "plgr_speed": 1,  # km/hr
-    "cable_load_time": 6,  # hr
-    "cable_prep_time": 1,  # hr
-    "cable_lower_time": 1,  # hr
-    "cable_pull_in_time": 5.5,  # hr
-    "cable_termination_time": 5.5,  # hr
-    "cable_lay_speed": 1,  # km/hr
-    "cable_lay_bury_speed": 0.3,  # km/hr
-    "cable_bury_speed": 0.5,  # km/hr
-    "cable_splice_time": 48,  # hr
-    "cable_raise_time": 0.5,  # hr
-    # Offshore Substation
-    "topside_fasten_time": 12,  # hr
-    "topside_release_time": 2,  # hr
-    "topside_attach_time": 6,  # hr
-    # Monopiles
-    "mono_embed_len": 30,  # m
-    "mono_drive_rate": 20,  # m/hr
-    "mono_fasten_time": 12,  # hr
-    "mono_release_time": 3,  # hr
-    "tp_fasten_time": 8,  # hr
-    "tp_release_time": 2,  # hr
-    "tp_bolt_time": 4,  # hr
-    "grout_cure_time": 24,  # hr
-    "grout_pump_time": 2,  # hr
-    # Scour Protection
-    "drop_rocks_time": 10,  # hr
-    "load_rocks_time": 4,  # hr
-    # Turbines
-    "tower_section_fasten_time": 4,  # hr, applies to all sections
-    "tower_section_release_time": 3,  # hr, applies to all sections
-    "tower_section_attach_time": 6,  # hr, applies to all sections
-    "nacelle_fasten_time": 4,  # hr
-    "nacelle_release_time": 3,  # hr
-    "nacelle_attach_time": 6,  # hr
-    "blade_fasten_time": 1.5,  # hr
-    "blade_release_time": 1,  # hr
-    "blade_attach_time": 3.5,  # hr
-    # Mooring System
-    "mooring_system_load_time": 5,  # hr
-    "mooring_site_survey_time": 4,  # hr
-    "suction_pile_install_time": 11,  # hr
-    "drag_embed_install_time": 5,  # hr
-    # Misc.
-    "site_position_time": 2,  # hr
-    "rov_survey_time": 1,  # hr
-    "crane_reequip_time": 1,  # hr
-}
diff --git a/ORBIT/core/components.py b/ORBIT/core/components.py
index 2152937f..29b0f133 100644
--- a/ORBIT/core/components.py
+++ b/ORBIT/core/components.py
@@ -7,7 +7,7 @@
 
 import simpy
 
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.core.exceptions import (
     ItemNotFound,
     CargoMassExceeded,
@@ -43,10 +43,6 @@ def extract_crane_specs(self, crane_specs):
             Dictionary of crane specifications.
         """
 
-        # Physical Dimensions
-        self.boom_length = crane_specs.get("boom_length", None)
-        self.radius = crane_specs.get("radius", None)
-
         # Operational Parameters
         self.max_lift = crane_specs.get("max_lift", None)
         self.max_hook_height = crane_specs.get("max_hook_height", None)
@@ -80,6 +76,34 @@ def reequip(**kwargs):
         return duration
 
 
+class DynamicPositioning:
+    """Base Dynamic Positioning Class"""
+
+    def __init__(self, dp_specs):
+        """
+        Creates an instance of DynamicPositioning.
+
+        Parameters
+        ----------
+        dp_specs : dict
+            Dictionary containing dynamic positioning specs.
+        """
+
+        self.extract_dp_specs(dp_specs)
+
+    def extract_dp_specs(self, dp_specs):
+        """
+        Extracts and defines jacking system specifications.
+
+        Parameters
+        ----------
+        jacksys_specs : dict
+            Dictionary containing jacking system specifications.
+        """
+
+        self.dp_class = dp_specs.get("class", 1)
+
+
 class JackingSys:
     """Base Jacking System Class"""
 
@@ -106,7 +130,6 @@ def extract_jacksys_specs(self, jacksys_specs):
         """
 
         # Physical Dimensions
-        self.num_legs = jacksys_specs.get("num_legs", None)
         self.leg_length = jacksys_specs.get("leg_length", None)
         self.air_gap = jacksys_specs.get("air_gap", None)
         self.leg_pen = jacksys_specs.get("leg_pen", None)
diff --git a/ORBIT/core/defaults/__init__.py b/ORBIT/core/defaults/__init__.py
new file mode 100644
index 00000000..7df591ec
--- /dev/null
+++ b/ORBIT/core/defaults/__init__.py
@@ -0,0 +1,21 @@
+"""Default inputs used throughout ORBIT."""
+
+__author__ = "Jake Nunemaker"
+__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
+__maintainer__ = "Jake Nunemaker"
+__email__ = "jake.nunemaker@nrel.gov"
+
+import os
+
+import yaml
+
+from ORBIT.core.library import loader
+
+DIR = os.path.split(__file__)[0]
+
+with open(os.path.join(DIR, "process_times.yaml"), "r") as f:
+    process_times = yaml.load(f, Loader=loader)
+
+
+with open(os.path.join(DIR, "common_costs.yaml"), "r") as f:
+    common_costs = yaml.load(f, Loader=loader)
diff --git a/ORBIT/core/defaults/common_costs.yaml b/ORBIT/core/defaults/common_costs.yaml
new file mode 100644
index 00000000..2237a44d
--- /dev/null
+++ b/ORBIT/core/defaults/common_costs.yaml
@@ -0,0 +1,6 @@
+# Material costs
+monopile_steel_cost: 3000                     # USD/t
+tp_steel_cost: 3000                           # USD/t
+
+# Port properties
+port_cost_per_month: 2e6                      # USD/month
diff --git a/ORBIT/core/defaults/process_times.yaml b/ORBIT/core/defaults/process_times.yaml
new file mode 100644
index 00000000..8d3a58e9
--- /dev/null
+++ b/ORBIT/core/defaults/process_times.yaml
@@ -0,0 +1,62 @@
+# Export Cable Installation
+"onshore_construction_time": 48   # hr
+"trench_dig_speed": 0.1           # km/hr
+"pull_winch_speed": 5             # km/hr
+"tow_plow_speed": 5               # km/hr
+
+# Array Cable Installation
+
+# General Cable Installation
+"plgr_speed": 1                   # km/hr
+"cable_load_time": 6              # hr
+"cable_prep_time": 1              # hr
+"cable_lower_time": 1             # hr
+"cable_pull_in_time": 5.5         # hr
+"cable_termination_time": 5.5     # hr
+"cable_lay_speed": 1              # km/hr
+"cable_lay_bury_speed": 0.3       # km/hr
+"cable_bury_speed": 0.5           # km/hr
+"cable_splice_time": 48           # hr
+"cable_raise_time": 0.5           # hr
+
+# Offshore Substation
+"topside_fasten_time": 12         # hr
+"topside_release_time": 2         # hr
+"topside_attach_time": 6          # hr
+
+# Monopiles
+"mono_embed_len": 30              # m
+"mono_drive_rate": 20             # m/hr
+"mono_fasten_time": 12            # hr
+"mono_release_time": 3            # hr
+"tp_fasten_time": 8               # hr
+"tp_release_time": 2              # hr
+"tp_bolt_time": 4                 # hr
+"grout_cure_time": 24             # hr
+"grout_pump_time": 2              # hr
+
+# Scour Protection
+"drop_rocks_time": 10             # hr
+"load_rocks_time": 4              # hr
+
+# Turbines
+"tower_section_fasten_time": 4    # hr, applies to all sections
+"tower_section_release_time": 3   # hr, applies to all sections
+"tower_section_attach_time": 6    # hr, applies to all sections
+"nacelle_fasten_time": 4          # hr
+"nacelle_release_time": 3         # hr
+"nacelle_attach_time": 6          # hr
+"blade_fasten_time": 1.5          # hr
+"blade_release_time": 1           # hr
+"blade_attach_time": 3.5          # hr
+
+# Mooring System
+"mooring_system_load_time": 5     # hr
+"mooring_site_survey_time": 4     # hr
+"suction_pile_install_time": 11   # hr
+"drag_embed_install_time": 5      # hr
+
+# Misc.
+"site_position_time": 2           # hr
+"rov_survey_time": 1              # hr
+"crane_reequip_time": 1           # hr
diff --git a/ORBIT/library.py b/ORBIT/core/library.py
old mode 100755
new mode 100644
similarity index 99%
rename from ORBIT/library.py
rename to ORBIT/core/library.py
index 599c5a0f..a0dc514d
--- a/ORBIT/library.py
+++ b/ORBIT/core/library.py
@@ -41,7 +41,7 @@
 
 from ORBIT.core.exceptions import LibraryItemNotFoundError
 
-ROOT = os.path.abspath(os.path.join(os.path.abspath(__file__), "../.."))
+ROOT = os.path.abspath(os.path.join(os.path.abspath(__file__), "../../.."))
 default_library = os.path.join(ROOT, "library")
 
 # Need a custom loader to read in scientific notation correctly
diff --git a/ORBIT/core/logic/__init__.py b/ORBIT/core/logic/__init__.py
index 340d9403..15644401 100644
--- a/ORBIT/core/logic/__init__.py
+++ b/ORBIT/core/logic/__init__.py
@@ -7,7 +7,9 @@
 
 
 from .vessel_logic import (  # shuttle_items_to_queue
+    stabilize,
     position_onsite,
+    jackdown_if_required,
     shuttle_items_to_queue,
     prep_for_site_operations,
     get_list_of_items_from_port,
diff --git a/ORBIT/core/logic/vessel_logic.py b/ORBIT/core/logic/vessel_logic.py
index 1e7c8c1d..0a45a4bc 100644
--- a/ORBIT/core/logic/vessel_logic.py
+++ b/ORBIT/core/logic/vessel_logic.py
@@ -8,8 +8,12 @@
 
 from marmot import process
 
-from ORBIT.core._defaults import process_times as pt
-from ORBIT.core.exceptions import ItemNotFound, VesselCapacityError
+from ORBIT.core.defaults import process_times as pt
+from ORBIT.core.exceptions import (
+    ItemNotFound,
+    MissingComponent,
+    VesselCapacityError,
+)
 
 
 @process
@@ -31,14 +35,8 @@ def prep_for_site_operations(vessel, survey_required=False, **kwargs):
         List of tasks included in preperation process.
     """
 
-    site_depth = kwargs.get("site_depth", None)
-    extension = kwargs.get("extension", site_depth + 10)
-    jackup_time = vessel.jacksys.jacking_time(extension, site_depth)
-
     yield position_onsite(vessel, **kwargs)
-    yield vessel.task(
-        "Jackup", jackup_time, constraints=vessel.transit_limits, **kwargs
-    )
+    yield stabilize(vessel, **kwargs)
 
     if survey_required:
         survey_time = kwargs.get("rov_survey_time", pt["rov_survey_time"])
@@ -50,6 +48,71 @@ def prep_for_site_operations(vessel, survey_required=False, **kwargs):
         )
 
 
+@process
+def stabilize(vessel, **kwargs):
+    """
+    Task representing time required to stabilize the vessel. If the vessel
+    has a dynamic positioning system, this task does not take any time. If the
+    vessel has a jacking system, the vessel will jackup.
+
+    Parameters
+    ----------
+    vessel : Vessel
+        Performing vessel. Requires configured `dynamic_positioning` or
+        `jacksys`.
+    """
+
+    try:
+        _ = vessel.dynamic_positioning
+        return
+
+    except MissingComponent:
+        pass
+
+    try:
+        jacksys = vessel.jacksys
+        site_depth = kwargs.get("site_depth", 40)
+        extension = kwargs.get("extension", site_depth + 10)
+        jackup_time = jacksys.jacking_time(extension, site_depth)
+        yield vessel.task(
+            "Jackup", jackup_time, constraints=vessel.transit_limits, **kwargs
+        )
+
+    except MissingComponent:
+        raise MissingComponent(
+            vessel, ["Dynamic Positioning", "Jacking System"]
+        )
+
+
+@process
+def jackdown_if_required(vessel, **kwargs):
+    """
+    Task representing time required to jackdown the vessel, if jacking system
+    is configured. If not, this task does not take anytime and the vessel is
+    released.
+
+    Parameters
+    ----------
+    vessel : Vessel
+        Performing vessel.
+    """
+
+    try:
+        jacksys = vessel.jacksys
+        site_depth = kwargs.get("site_depth", 40)
+        extension = kwargs.get("extension", site_depth + 10)
+        jackdown_time = jacksys.jacking_time(extension, site_depth)
+        yield vessel.task(
+            "Jackdown",
+            jackdown_time,
+            constraints=vessel.transit_limits,
+            **kwargs,
+        )
+
+    except MissingComponent:
+        return
+
+
 @process
 def position_onsite(vessel, **kwargs):
     """
@@ -121,6 +184,7 @@ def shuttle_items_to_queue(vessel, port, queue, distance, items, **kwargs):
             yield vessel.task(
                 "Transit", transit_time, constraints=vessel.transit_limits
             )
+            yield stabilize(vessel, **kwargs)
             vessel.at_site = True
 
         if vessel.at_site:
@@ -155,6 +219,7 @@ def shuttle_items_to_queue(vessel, port, queue, distance, items, **kwargs):
 
             # Transit back to port
             vessel.at_site = False
+            yield jackdown_if_required(vessel, **kwargs)
             yield vessel.task(
                 "Transit", transit_time, constraints=vessel.transit_limits
             )
diff --git a/ORBIT/core/vessel.py b/ORBIT/core/vessel.py
index 74c6fdf6..d8ad423c 100644
--- a/ORBIT/core/vessel.py
+++ b/ORBIT/core/vessel.py
@@ -16,6 +16,7 @@
     JackingSys,
     CableCarousel,
     VesselStorage,
+    DynamicPositioning,
     ScourProtectionStorage,
 )
 from ORBIT.core.exceptions import ItemNotFound, MissingComponent
@@ -132,6 +133,15 @@ def jacksys(self):
         except AttributeError:
             raise MissingComponent(self, "Jacking System")
 
+    @property
+    def dynamic_positioning(self):
+        """Returns configured `DynamicPositioning` or raises `MissingComponent`."""
+        try:
+            return self._dp_system
+
+        except AttributeError:
+            raise MissingComponent(self, "Dynamic Positioning")
+
     @property
     def storage(self):
         """Returns configured `VesselStorage` or raises `MissingComponent`."""
@@ -168,6 +178,7 @@ def initialize(self, mobilize=True):
         self._vessel_specs = self.config.get("vessel_specs", {})
         self.extract_transport_specs()
         self.extract_jacksys_specs()
+        self.extract_dp_specs()
         self.extract_crane_specs()
         self.extract_storage_specs()
         self.extract_cable_storage_specs()
@@ -196,6 +207,13 @@ def extract_jacksys_specs(self):
         if self._jacksys_specs:
             self._jacksys = JackingSys(self._jacksys_specs)
 
+    def extract_dp_specs(self):
+        """Extracts dynamic positioning system specifications if found."""
+
+        self._dp_specs = self.config.get("dynamic_positioning_specs", {})
+        if self._dp_specs:
+            self._dp_system = DynamicPositioning(self._dp_specs)
+
     def extract_storage_specs(self):
         """Extracts storage system specifications if found."""
 
diff --git a/ORBIT/manager.py b/ORBIT/manager.py
index 2bf59a55..07011655 100644
--- a/ORBIT/manager.py
+++ b/ORBIT/manager.py
@@ -15,15 +15,18 @@
 import numpy as np
 import pandas as pd
 
-from ORBIT import library
+import ORBIT
 from ORBIT.phases import DesignPhase, InstallPhase
-from ORBIT.library import initialize_library, extract_library_data
+from ORBIT.core.library import (
+    initialize_library,
+    export_library_specs,
+    extract_library_data,
+)
 from ORBIT.phases.design import (
     SparDesign,
     MonopileDesign,
     ArraySystemDesign,
     ExportSystemDesign,
-    ProjectDevelopment,
     MooringSystemDesign,
     ScourProtectionDesign,
     SemiSubmersibleDesign,
@@ -55,7 +58,6 @@ class ProjectManager:
     date_format_long = "%m/%d/%Y %H:%M"
 
     _design_phases = [
-        ProjectDevelopment,
         MonopileDesign,
         ArraySystemDesign,
         CustomArraySystemDesign,
@@ -102,17 +104,20 @@ def __init__(self, config, library_path=None, weather=None):
                 *config.get("install_phases", []),
             ],
         )
+        self._phases = {}
         self.config = self.resolve_project_capacity(config)
         self.weather = self.transform_weather_input(weather)
 
+        self.design_results = {}
+        self.detailed_outputs = {}
+
+        self.system_costs = {}
+        self.installation_costs = {}
+
+        # TODO: Revise:
         self.phase_starts = {}
         self.phase_times = {}
-        self.phase_costs = {}
         self._output_logs = []
-        self._phases = {}
-
-        self.design_results = {}
-        self.detailed_outputs = {}
 
     def run_project(self, **kwargs):
         """
@@ -158,6 +163,12 @@ def phases(self):
 
         return self._phases
 
+    @property
+    def project_params(self):
+        """Returns defined project parameters, if found."""
+
+        return self.config.get("project_parameters", {})
+
     @classmethod
     def compile_input_dict(cls, phases):
         """
@@ -189,17 +200,27 @@ def compile_input_dict(cls, phases):
             config = cls.merge_dicts(config, d.expected_config)
             config = cls.remove_keys(config, d.output_config)
 
-        config["commissioning"] = "float (optional, default: 0.01)"
-        config["decommissioning"] = "float (optional, default: 0.15)"
-
-        config["ncf"] = "float (optional, default: 0.4)"
-        config["offtake_price"] = "$/MWh (optional, default: 80)"
-        config["project_lifetime"] = "yrs (optional, default: 25)"
-        config["discount_rate"] = "yearly (optional, default: .025)"
-        config["opex_rate"] = "$/kW/year (optional, default: 150)"
+        config["project_parameters"] = {
+            "turbine_capex": "$/kW (optional, default: 1300)",
+            "ncf": "float (optional, default: 0.4)",
+            "offtake_price": "$/MWh (optional, default: 80)",
+            "project_lifetime": "yrs (optional, default: 25)",
+            "discount_rate": "yearly (optional, default: .025)",
+            "opex_rate": "$/kW/year (optional, default: 150)",
+            "construction_insurance": "$/kW (optional, default: 44)",
+            "construction_financing": "$/kW (optional, default: 183)",
+            "contingency": "$/kW (optional, default: 316)",
+            "commissioning": "$/kW (optional, default: 44)",
+            "decommissioning": "$/kW (optional, default: 58)",
+            "site_auction_price": "$ (optional, default: 100e6)",
+            "site_assessment_cost": "$ (optional, default: 50e6)",
+            "construction_plan_cost": "$ (optional, default: 1e6)",
+            "installation_plan_cost": "$ (optional, default: 0.25e6)",
+        }
 
         config["design_phases"] = [*design_phases.keys()]
         config["install_phases"] = [*install_phases.keys()]
+        config["orbit_version"] = str(ORBIT.__version__)
 
         return config
 
@@ -391,6 +412,19 @@ def create_config_for_phase(self, phase):
 
         return phase_config
 
+    @property
+    def phase_ends(self):
+
+        ret = {}
+        for k, t in self.phase_times.items():
+            try:
+                ret[k] = self.phase_starts[k] + t
+
+            except KeyError:
+                pass
+
+        return ret
+
     def run_install_phase(self, name, start, **kwargs):
         """
         Compiles the phase specific configuration input dictionary for input
@@ -407,8 +441,6 @@ def run_install_phase(self, name, start, **kwargs):
         -------
         time : int | float
             Total phase time.
-        cost : int | float
-            Total phase cost.
         logs : list
             List of phase logs.
         """
@@ -422,41 +454,43 @@ def run_install_phase(self, name, start, **kwargs):
         _catch = kwargs.get("catch_exceptions", False)
         _class = self.get_phase_class(name)
         _config = self.create_config_for_phase(name)
-
-        kwargs = _config.pop("kwargs", {})
+        processes = _config.pop("processes", {})
 
         if _catch:
             try:
                 phase = _class(
-                    _config, weather=weather, phase_name=name, **kwargs
+                    _config, weather=weather, phase_name=name, **processes
                 )
                 phase.run()
 
             except Exception as e:
                 print(f"\n\t - {name}: {e}")
-                self.phase_costs[name] = e.__class__.__name__
-                self.phase_times[name] = e.__class__.__name__
-
-                return None, None, None
+                return None, None
 
         else:
-            phase = _class(_config, weather=weather, phase_name=name, **kwargs)
+            phase = _class(
+                _config, weather=weather, phase_name=name, **processes
+            )
             phase.run()
 
         self._phases[name] = phase
 
         time = phase.total_phase_time
-        cost = phase.total_phase_cost
         logs = deepcopy(phase.env.logs)
 
         self.phase_starts[name] = start
-        self.phase_costs[name] = cost
         self.phase_times[name] = time
         self.detailed_outputs = self.merge_dicts(
             self.detailed_outputs, phase.detailed_output
         )
 
-        return cost, time, logs
+        if phase.system_capex:
+            self.system_costs[name] = phase.system_capex
+
+        if phase.installation_capex:
+            self.installation_costs[name] = phase.installation_capex
+
+        return time, logs
 
     def get_phase_class(self, phase):
         """
@@ -511,8 +545,6 @@ def run_design_phase(self, name, **kwargs):
 
             except Exception as e:
                 print(f"\n\t - {name}: {e}")
-                self.phase_costs[name] = e.__class__.__name__
-                self.phase_times[name] = e.__class__.__name__
                 return
 
         else:
@@ -521,8 +553,6 @@ def run_design_phase(self, name, **kwargs):
 
         self._phases[name] = phase
 
-        self.phase_costs[name] = phase.total_phase_cost
-        self.phase_times[name] = phase.total_phase_time
         self.design_results = self.merge_dicts(
             self.design_results, phase.design_result, overwrite=False
         )
@@ -547,7 +577,7 @@ def run_multiple_phases_in_serial(self, phase_list, **kwargs):
         start = 0
 
         for name in phase_list:
-            _, time, logs = self.run_install_phase(name, start, **kwargs)
+            time, logs = self.run_install_phase(name, start, **kwargs)
 
             if logs is None:
                 continue
@@ -579,7 +609,7 @@ def run_multiple_phases_overlapping(self, phases, **kwargs):
         # Run defined
         for name, start in defined.items():
 
-            _, _, logs = self.run_install_phase(name, start, **kwargs)
+            _, logs = self.run_install_phase(name, start, **kwargs)
 
             if logs is None:
                 continue
@@ -625,9 +655,7 @@ def run_dependent_phases(self, _phases, zero, **kwargs):
 
                 try:
                     start = self.get_dependency_start_time(target, perc)
-                    cost, time, logs = self.run_install_phase(
-                        name, start, **kwargs
-                    )
+                    _, logs = self.run_install_phase(name, start, **kwargs)
 
                     progress = True
 
@@ -805,13 +833,13 @@ def project_logs(self):
         if not self._output_logs:
             raise Exception("Project hasn't been ran yet.")
 
-        return self._output_logs
+        return sorted(self._output_logs, key=lambda l: l["time"])
 
     @property
     def project_time(self):
         """Returns total project time as the time of the last log."""
 
-        return self.project_logs[-1]["time"]
+        return self.project_actions[-1]["time"]
 
     @property
     def month_bins(self):
@@ -825,7 +853,7 @@ def monthly_expenses(self):
         construction."""
 
         opex = self.monthly_opex
-        lifetime = self.config.get("project_lifetime", 25)
+        lifetime = self.project_params.get("project_lifetime", 25)
 
         _expense_logs = self._filter_logs(keys=["cost", "time"])
         expenses = np.array(
@@ -843,8 +871,8 @@ def monthly_expenses(self):
     def monthly_opex(self):
         """Returns the monthly OpEx expenditures based on project size."""
 
-        rate = self.config.get("opex_rate", 150)
-        lifetime = self.config.get("project_lifetime", 25)
+        rate = self.project_params.get("opex_rate", 150)
+        lifetime = self.project_params.get("project_lifetime", 25)
 
         try:
             times, turbines = self.progress.energize_points
@@ -869,9 +897,9 @@ def monthly_revenue(self):
         """Returns the monthly revenue based on when array system strings can
         be energized, eg. 'self.progress.energize_points'."""
 
-        ncf = self.config.get("ncf", 0.4)
-        price = self.config.get("offtake_price", 80)
-        lifetime = self.config.get("project_lifetime", 25)
+        ncf = self.project_params.get("ncf", 0.4)
+        price = self.project_params.get("offtake_price", 80)
+        lifetime = self.project_params.get("project_lifetime", 25)
 
         times, turbines = self.progress.energize_points
         dig = list(np.digitize(times, self.month_bins))
@@ -909,7 +937,7 @@ def npv(self):
         """Returns the net present value of the project based on
         `self.cash_flow`."""
 
-        dr = self.config.get("discount_rate", 0.025)
+        dr = self.project_params.get("discount_rate", 0.025)
         pr = (1 + dr) ** (1 / 12) - 1
 
         cash_flow = self.cash_flow
@@ -918,7 +946,7 @@ def npv(self):
             for i in range(1, max(cash_flow.keys()) + 1)
         ]
 
-        return self.overnight_capex - sum(_npv)
+        return (self.total_capex - self.installation_capex) - sum(_npv)
 
     @property
     def progress_logs(self):
@@ -1037,41 +1065,31 @@ def _diff_dates_long(self, a, b):
         return abs((a - b).days)
 
     @property
-    def phase_costs_per_kw(self):
+    def overnight_capex_per_kw(self):
         """
-        Returns phase costs in CAPEX/kW.
+        Returns overnight CAPEX/kW.
         """
 
-        _dict = {}
-        for k, capex in self.phase_costs.items():
-
-            try:
-                _dict[k] = capex / (self.capacity * 1000)
+        try:
+            capex = self.overnight_capex / (self.capacity * 1000)
 
-            except TypeError:
-                pass
+        except TypeError:
+            capex = None
 
-        return _dict
+        return capex
 
     @property
-    def overnight_capex(self):
-        """Returns the overnight capital cost of the project."""
-
-        design_phases = [p.__name__ for p in self._design_phases]
-        design_cost = sum(
-            [v for k, v in self.phase_costs.items() if k in design_phases]
-        )
+    def system_capex(self):
+        """Returns total system procurement CapEx."""
 
-        return design_cost + self.turbine_capex
+        return np.nansum([c for _, c in self.system_costs.items()])
 
     @property
-    def overnight_capex_per_kw(self):
-        """
-        Returns overnight CAPEX/kW.
-        """
+    def system_capex_per_kw(self):
+        """Returns system CapEx/kW."""
 
         try:
-            capex = self.overnight_capex / (self.capacity * 1000)
+            capex = self.system_capex / (self.capacity * 1000)
 
         except TypeError:
             capex = None
@@ -1080,24 +1098,13 @@ def overnight_capex_per_kw(self):
 
     @property
     def installation_capex(self):
-        """
-        Returns installation related CAPEX.
-        """
+        """Returns total installation related CapEx."""
 
-        res = sum(
-            [
-                v
-                for k, v in self.phase_costs.items()
-                if k in self.config["install_phases"] and isinstance(v, Number)
-            ]
-        )
-        return res
+        return np.nansum([c for _, c in self.installation_costs.items()])
 
     @property
     def installation_capex_per_kw(self):
-        """
-        Returns installation related CAPEX/kW.
-        """
+        """Returns installation CapEx/kW."""
 
         try:
             capex = self.installation_capex / (self.capacity * 1000)
@@ -1109,17 +1116,13 @@ def installation_capex_per_kw(self):
 
     @property
     def bos_capex(self):
-        """
-        Returns BOS CAPEX not including commissioning and decommissioning.
-        """
+        """Returns total balance of system CapEx."""
 
-        return sum([v for _, v in self.phase_costs.items()])
+        return self.system_capex + self.installation_capex
 
     @property
     def bos_capex_per_kw(self):
-        """
-        Returns BOS CAPEX/kW not including commissioning and decommissioning.
-        """
+        """Returns balance of system CapEx/kW."""
 
         try:
             capex = self.bos_capex / (self.capacity * 1000)
@@ -1130,29 +1133,45 @@ def bos_capex_per_kw(self):
         return capex
 
     @property
-    def commissioning(self):
+    def turbine_capex(self):
         """
-        Returns the cost of commissioning based on the configured phases.
-        Defaults to 1% of total BOS CAPEX.
+        Returns the total turbine CAPEX.
         """
 
-        _comm = self.config.get("commissioning", 0.0)
-        if (_comm < 0.0) or (_comm > 1.0):
-            raise ValueError("'commissioning' must be between 0 and 1")
+        _capex = self.project_params.get("turbine_capex", 1300)
+        try:
+            num_turbines = self.config["plant"]["num_turbines"]
+            rating = self.config["turbine"]["turbine_rating"]
 
-        total = self.bos_capex + self.turbine_capex
+        except KeyError:
+            raise KeyError(
+                f"Total turbine CAPEX can't be calculated. Required "
+                f"parameters 'plant.num_turbines' or 'turbine.turbine_rating' "
+                f"not found."
+            )
 
-        comm = total * _comm
-        return comm
+        capex = _capex * num_turbines * rating * 1000
+        return capex
 
     @property
-    def commissioning_per_kw(self):
-        """
-        Returns the cost of commissioning per kW.
-        """
+    def turbine_capex_per_kw(self):
+        """Returns the turbine CapEx/kW."""
+
+        _capex = self.project_params.get("turbine_capex", None)
+        return _capex
+
+    @property
+    def overnight_capex(self):
+        """Returns the overnight capital cost of the project."""
+
+        return self.system_capex + self.turbine_capex
+
+    @property
+    def soft_capex(self):
+        """Returns total project cost costs."""
 
         try:
-            capex = self.commissioning / (self.capacity * 1000)
+            capex = self.soft_capex_per_kw * self.capacity * 1000
 
         except TypeError:
             capex = None
@@ -1160,87 +1179,68 @@ def commissioning_per_kw(self):
         return capex
 
     @property
-    def decommissioning(self):
+    def soft_capex_per_kw(self):
         """
-        Returns the cost of decommissioning based on the configured
-        installation phases. Defaults to 15% of installation CAPEX.
+        Returns project soft costs per kW. Default numbers are based on the
+        Cost of Energy Review (Stehly and Beiter 2018).
         """
 
-        _decomm = self.config.get("decommissioning", 0.0)
-        if (_decomm < 0.0) or (_decomm > 1.0):
-            raise ValueError("'decommissioning' must be between 0 and 1")
-
-        try:
-            decomm = self.installation_capex * _decomm
-
-        except KeyError:
-            return 0.0
+        insurance = self.project_params.get("construction_insurance", 44)
+        financing = self.project_params.get("construction_financing", 183)
+        contingency = self.project_params.get("contingency", 316)
+        commissioning = self.project_params.get("commissioning", 44)
+        decommissioning = self.project_params.get("decommissioning", 58)
 
-        return decomm
+        return sum(
+            [insurance, financing, contingency, commissioning, decommissioning]
+        )
 
     @property
-    def decommissioning_per_kw(self):
+    def project_capex(self):
         """
-        Returns the cost of decommissioning per kW.
+        Returns project related CapEx line items. To override the defaults,
+        the keys below should be passed to the 'project_parameters' subdict.
         """
 
-        try:
-            capex = self.decommissioning / (self.capacity * 1000)
-
-        except TypeError:
-            capex = None
+        site_auction = self.project_params.get("site_auction_price", 100e6)
+        site_assessment = self.project_params.get("site_assessment_cost", 50e6)
+        construction_plan = self.project_params.get(
+            "construction_plan_cost", 1e6
+        )
+        installation_plan = self.project_params.get(
+            "installation_plan_cost", 0.25e6
+        )
 
-        return capex
+        return sum(
+            [
+                site_auction,
+                site_assessment,
+                construction_plan,
+                installation_plan,
+            ]
+        )
 
     @property
-    def turbine_capex(self):
-        """
-        Returns the total turbine CAPEX.
-        """
+    def project_capex_per_kw(self):
+        """Returns project related CapEx per kW."""
 
-        _capex = self.config.get("turbine_capex", 0.0)
         try:
-            num_turbines = self.config["plant"]["num_turbines"]
-            rating = self.config["turbine"]["turbine_rating"]
+            capex = self.project_capex / (self.capacity * 1000)
 
-        except KeyError:
-            print(
-                f"Turbine CAPEX not included in commissioning. Required "
-                f"parameters 'plant.num_turbines' or 'turbine.turbine_rating' "
-                f"not found."
-            )
-            return 0.0
+        except TypeError:
+            capex = None
 
-        capex = _capex * num_turbines * rating * 1000
         return capex
 
-    @property
-    def turbine_capex_per_kw(self):
-        """
-        Returns the turbine CAPEX/kW.
-        """
-
-        _capex = self.config.get("turbine_capex", None)
-        return _capex
-
     @property
     def total_capex(self):
-        """
-        Returns total project CAPEX including commissioning and decommissioning.
-        """
+        """Returns total project CapEx including soft costs."""
 
-        return (
-            self.bos_capex
-            + self.turbine_capex
-            + self.commissioning
-            + self.decommissioning
-        )
+        return self.bos_capex + self.turbine_capex + self.soft_capex
 
     @property
     def total_capex_per_kw(self):
-        """
-        Returns total BOS CAPEX/kW including commissioning and decommissioning.
-        """
+        """Returns total CapEx/kW."""
 
         try:
             capex = self.total_capex / (self.capacity * 1000)
@@ -1261,7 +1261,7 @@ def export_configuration(self, file_name):
             Name to use for the file.
         """
 
-        library.export_library_specs("config", file_name, self.config)
+        export_library_specs("config", file_name, self.config)
 
 
 class ProjectProgress:
diff --git a/ORBIT/parametric.py b/ORBIT/parametric.py
new file mode 100644
index 00000000..135fd4c0
--- /dev/null
+++ b/ORBIT/parametric.py
@@ -0,0 +1,147 @@
+__author__ = ["Jake Nunemaker"]
+__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
+__maintainer__ = "Jake Nunemaker"
+__email__ = ["jake.nunemaker@nrel.gov"]
+
+
+import time
+from copy import deepcopy
+from random import sample
+from itertools import product
+
+import numpy as np
+import pandas as pd
+from benedict import benedict
+
+from ORBIT import ProjectManager
+
+
+class ParametricManager:
+    """Class for configuring parametric ORBIT runs."""
+
+    def __init__(self, base, params, funcs, weather=None, module=None):
+        """
+        Creates an instance of `ParametricRun`.
+
+        Parameters
+        ----------
+        base : dict
+            Base ORBIT configuration.
+        params : dict
+            Parameters and their values.
+            Format: "subdict.param": [num1, num2, num3]
+        funcs : dict
+            List of functions used to save results.
+            Format: "name": lambda project: project.attr
+        weather : DataFrame
+
+        """
+
+        self.base = benedict(base)
+        self.params = params
+        self.funcs = funcs
+        self.weather = weather
+        self.results = None
+        self.module = module
+
+    def run(self, **kwargs):
+        """Run the configured parametric runs and save any requested results to
+        `self.results`."""
+
+        outputs = []
+        for run in self.run_list:
+            data = self._run_config(run, **kwargs)
+            outputs.append(data)
+
+        self.results = pd.DataFrame(outputs)
+
+    def _run_config(self, run, **kwargs):
+        """Run an individual config."""
+
+        config = deepcopy(self.base)
+        config.merge(run)
+
+        if self.module is not None:
+            project = self.module(config, weather=self.weather, **kwargs)
+            project.run()
+
+        else:
+            project = ProjectManager(config, weather=self.weather, **kwargs)
+            project.run_project()
+
+        results = self.map_funcs(project, self.funcs)
+        data = {**run, **results}
+
+        return data
+
+    @property
+    def run_list(self):
+        """Returns list of configured parametric runs."""
+
+        runs = list(product(*self.params.values()))
+        return [dict(zip(self.params.keys(), run)) for run in runs]
+
+    @property
+    def num_runs(self):
+        return len(self.run_list)
+
+    @staticmethod
+    def map_funcs(obj, funcs):
+        """
+        Map `obj` to list of `funcs`.
+
+        Parameters
+        ----------
+        obj : ProjectManager
+            Project instance to run through functions.
+        funcs : list
+            Functions used to pull results from obj.
+        """
+
+        results = {}
+        for k, f in funcs.items():
+            try:
+                res = f(obj)
+
+            except TypeError:
+                raise TypeError(
+                    f"Result function '{f}' not structured properly. "
+                    f"Correct format: 'lambda project: project.{f}'"
+                )
+
+            except AttributeError:
+                res = np.NaN
+
+            results[k] = res
+
+        return results
+
+    def preview(self, num=10, **kwargs):
+        """
+        Runs a limited set of runs to preview the results and provide an
+        estimate for total run time.
+
+        Parameters
+        ----------
+        num : int
+            Number to run.
+        """
+
+        start = time.time()
+        outputs = []
+        if num > len(self.run_list):
+            to_run = self.run_list
+
+        else:
+            to_run = sample(self.run_list, num)
+
+        for run in to_run:
+            data = self._run_config(run, **kwargs)
+            outputs.append(data)
+
+        elapsed = time.time() - start
+        estimate = (len(self.run_list) / num) * elapsed
+        print(f"{num} runs elapsed time: {elapsed:.2f}s")
+        print(f"{self.num_runs} runs estimated time: {estimate:.2f}s")
+
+        return pd.DataFrame(outputs)
diff --git a/ORBIT/phases/base.py b/ORBIT/phases/base.py
index 15bcc6fa..5b3c35be 100644
--- a/ORBIT/phases/base.py
+++ b/ORBIT/phases/base.py
@@ -9,11 +9,7 @@
 from abc import ABC, abstractmethod
 from copy import deepcopy
 
-from ORBIT.library import (
-    initialize_library,
-    extract_library_data,
-    extract_library_specs,
-)
+from ORBIT.core.library import initialize_library, extract_library_data
 from ORBIT.core.exceptions import MissingInputs
 
 
@@ -25,17 +21,6 @@ class BasePhase(ABC):
     interfaces for all phases defined by subclasses. Many of the methods below
     should be overwritten in subclasses.
 
-    Attributes
-    ----------
-    phase : str
-        Name of the phase that is being used.
-    total_phase_cost : float
-        Calculates the total phase cost. Should be implemented in each subclass.
-    detailed_output : dict
-        Creates the detailed output dictionary. Should be implemented in each
-        subclass.
-    phase_dataframe : pd.DataFrame
-
     Methods
     -------
     run()
@@ -67,13 +52,6 @@ def extract_phase_kwargs(self, **kwargs):
         if phase_name is not None:
             self.phase = phase_name
 
-    def extract_defaults(self):
-        """
-        Extracts the default data from the library.
-        """
-
-        self.defaults = extract_library_specs("defaults", "project")
-
     @classmethod
     def _check_keys(cls, expected, config):
         """
@@ -144,24 +122,3 @@ def run(self):
         """Main run function for phase."""
 
         pass
-
-    @property
-    @abstractmethod
-    def total_phase_cost(self):
-        """Returns total phase cost in $USD."""
-
-        pass
-
-    @property
-    @abstractmethod
-    def total_phase_time(self):
-        """Returns total phase time in hours."""
-
-        pass
-
-    @property
-    @abstractmethod
-    def detailed_output(self):
-        """Returns detailed phase information."""
-
-        pass
diff --git a/ORBIT/phases/design/__init__.py b/ORBIT/phases/design/__init__.py
index c7d85801..d234df4c 100644
--- a/ORBIT/phases/design/__init__.py
+++ b/ORBIT/phases/design/__init__.py
@@ -11,7 +11,6 @@
 from .spar_design import SparDesign
 from .monopile_design import MonopileDesign
 from .array_system_design import ArraySystemDesign, CustomArraySystemDesign
-from .project_development import ProjectDevelopment
 from .export_system_design import ExportSystemDesign
 from .mooring_system_design import MooringSystemDesign
 from .scour_protection_design import ScourProtectionDesign
diff --git a/ORBIT/phases/design/_cables.py b/ORBIT/phases/design/_cables.py
old mode 100755
new mode 100644
index 9611ce6e..638cbd59
--- a/ORBIT/phases/design/_cables.py
+++ b/ORBIT/phases/design/_cables.py
@@ -12,7 +12,7 @@
 import numpy as np
 from scipy.optimize import fsolve
 
-from ORBIT.library import extract_library_specs
+from ORBIT.core.library import extract_library_specs
 from ORBIT.phases.design import DesignPhase
 
 
@@ -435,7 +435,7 @@ def cost_by_type(self):
         return cost
 
     @property
-    def total_phase_cost(self):
+    def total_cost(self):
         """
         Calculates the cost of the array cabling system.
 
@@ -447,15 +447,9 @@ def total_phase_cost(self):
 
         return sum(self.cost_by_type.values())
 
-    @property
-    def total_phase_time(self):
-        return self._design.get("design_time", 0.0)
-
     @property
     def detailed_output(self):
-        """
-        Returns detailed design outputs.
-        """
+        """Returns detailed design outputs."""
 
         _output = {
             "length": self.total_cable_length_by_type,
@@ -488,7 +482,7 @@ def design_result(self):
             raise Exception(f"Has {self.__class__.__name__} been ran?")
 
         system = "_".join((self.cable_type, "system"))
-        output = {system: {"cables": {}}}
+        output = {system: {"cables": {}, "system_cost": self.total_cost}}
         _temp = output[system]["cables"]
 
         for name, cable in self.cables.items():
diff --git a/ORBIT/phases/design/array_system_design.py b/ORBIT/phases/design/array_system_design.py
index c8904ff6..8ec5850a 100644
--- a/ORBIT/phases/design/array_system_design.py
+++ b/ORBIT/phases/design/array_system_design.py
@@ -13,7 +13,7 @@
 import pandas as pd
 import matplotlib.pyplot as plt
 
-from ORBIT.library import export_library_specs, extract_library_specs
+from ORBIT.core.library import export_library_specs, extract_library_specs
 from ORBIT.phases.design._cables import Plant, CableSystem
 
 
@@ -77,14 +77,13 @@ class ArraySystemDesign(CableSystem):
         },
         "turbine": {"rotor_diameter": "m", "turbine_rating": "MW"},
         "array_system_design": {
-            "design_time": "hrs (optional)",
             "cables": "list | str",
             "touchdown_distance": "m (optional, default: 0)",
             "average_exclusion_percent": "float (optional)",
         },
     }
 
-    output_config = {"array_system": {"cables": "dict"}}
+    output_config = {"array_system": {"cables": "dict", "system_cost": "USD"}}
 
     def __init__(self, config, **kwargs):
         """
@@ -534,7 +533,6 @@ class CustomArraySystemDesign(ArraySystemDesign):
         "plant": {"layout": "str", "num_turbines": "int"},
         "turbine": {"turbine_rating": "int | float"},
         "array_system_design": {
-            "design_time": "int | float (optional)",
             "cables": "list | str",
             "location_data": "str",
             "distance": "bool (optional)",
diff --git a/ORBIT/phases/design/export_system_design.py b/ORBIT/phases/design/export_system_design.py
old mode 100755
new mode 100644
index e415cb91..6c6ae0a0
--- a/ORBIT/phases/design/export_system_design.py
+++ b/ORBIT/phases/design/export_system_design.py
@@ -207,7 +207,8 @@ def design_result(self):
 
         output = {
             "export_system": {
-                "interconnection_distance": self._distance_to_interconnection
+                "interconnection_distance": self._distance_to_interconnection,
+                "system_cost": self.total_cost,
             }
         }
 
diff --git a/ORBIT/phases/design/monopile_design.py b/ORBIT/phases/design/monopile_design.py
old mode 100755
new mode 100644
index da27df3e..ab1e5349
--- a/ORBIT/phases/design/monopile_design.py
+++ b/ORBIT/phases/design/monopile_design.py
@@ -10,6 +10,7 @@
 
 from scipy.optimize import fsolve
 
+from ORBIT.core.defaults import common_costs
 from ORBIT.phases.design import DesignPhase
 
 
@@ -39,8 +40,6 @@ class MonopileDesign(DesignPhase):
             "weibull_scale_factor": "float (optional)",
             "weibull_shape_factor": "float (optional)",
             "turb_length_scale": "m (optional)",
-            "design_time": "h (optional)",
-            "design_cost": "USD (optional)",
             "monopile_steel_cost": "USD/t (optional)",
             "tp_steel_cost": "USD/t (optional)",
         },
@@ -55,8 +54,14 @@ class MonopileDesign(DesignPhase):
             "length": "m",
             "mass": "t",
             "deck_space": "m2",
+            "unit_cost": "USD",
+        },
+        "transition_piece": {
+            "length": "m",
+            "mass": "t",
+            "deck_space": "m2",
+            "unit_cost": "USD",
         },
-        "transition_piece": {"length": "m", "mass": "t", "deck_space": "m2"},
     }
 
     def __init__(self, config, **kwargs):
@@ -70,7 +75,6 @@ def __init__(self, config, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
         self._outputs = {}
 
     def run(self):
@@ -125,8 +129,8 @@ def design_monopile(
 
         Returns
         -------
-        sizing : dict
-            Dictionary of monopile sizing.
+        monopile : dict
+            Dictionary of monopile sizing and costs.
 
             - ``diameter`` - Pile diameter (m)
             - ``thickness`` - Pile wall thickness (m)
@@ -158,37 +162,40 @@ def design_monopile(
         )
 
         data = (yield_stress, material_factor, M_50y)
-        sizing = {}
+        monopile = {}
 
         # Monopile sizing
-        sizing["diameter"] = fsolve(self.pile_diam_equation, 10, args=data)[0]
-        sizing["thickness"] = self.pile_thickness(sizing["diameter"])
-        sizing["moment"] = self.pile_moment(
-            sizing["diameter"], sizing["thickness"]
+        monopile["diameter"] = fsolve(self.pile_diam_equation, 10, args=data)[
+            0
+        ]
+        monopile["thickness"] = self.pile_thickness(monopile["diameter"])
+        monopile["moment"] = self.pile_moment(
+            monopile["diameter"], monopile["thickness"]
         )
-        sizing["embedment_length"] = self.pile_embedment_length(
-            sizing["moment"], **kwargs
+        monopile["embedment_length"] = self.pile_embedment_length(
+            monopile["moment"], **kwargs
         )
 
         # Total length
         airgap = kwargs.get("airgap", 10)  # m
-        sizing["length"] = sizing["embedment_length"] + site_depth + airgap
-        sizing["mass"] = self.pile_mass(
-            Dp=sizing["diameter"],
-            tp=sizing["thickness"],
-            Lt=sizing["length"],
+        monopile["length"] = monopile["embedment_length"] + site_depth + airgap
+        monopile["mass"] = self.pile_mass(
+            Dp=monopile["diameter"],
+            tp=monopile["thickness"],
+            Lt=monopile["length"],
             **kwargs,
         )
 
         # Deck space
-        sizing["deck_space"] = sizing["diameter"] ** 2
+        monopile["deck_space"] = monopile["diameter"] ** 2
 
-        self.monopile_sizing = sizing
+        # Costs
+        monopile["unit_cost"] = monopile["mass"] * self.monopile_steel_cost
 
-        return sizing
+        self.monopile_sizing = monopile
+        return monopile
 
-    @staticmethod
-    def design_transition_piece(D_p, t_p, **kwargs):
+    def design_transition_piece(self, D_p, t_p, **kwargs):
         """
         Designs transition piece given the results of the monopile design.
 
@@ -224,15 +231,14 @@ def design_transition_piece(D_p, t_p, **kwargs):
             "mass": m_tp,
             "length": L_tp,
             "deck_space": D_tp ** 2,
+            "unit_cost": m_tp * self.tp_steel_cost,
         }
 
         return tp_design
 
     @property
     def design_result(self):
-        """
-        Returns the results of :py:meth:`.design_monopile`.
-        """
+        """Returns the results of :py:meth:`.design_monopile`."""
 
         if not self._outputs:
             raise Exception("Has MonopileDesign been ran yet?")
@@ -240,22 +246,10 @@ def design_result(self):
         return self._outputs
 
     @property
-    def total_phase_cost(self):
-        """Returns total phase cost in $USD."""
+    def total_cost(self):
+        """Returns total cost of the substructures and transition pieces."""
 
-        _design = self.config.get("monopile_design", {})
-        design_cost = _design.get("design_cost", 0.0)
-        material_cost = sum([v for _, v in self.material_cost.items()])
-
-        return design_cost + material_cost
-
-    @property
-    def total_phase_time(self):
-        """Returns total phase time in hours."""
-
-        _design = self.config.get("monopile_design", {})
-        phase_time = _design.get("design_time", 0.0)
-        return phase_time
+        return sum([v for _, v in self.material_cost.items()])
 
     @property
     def detailed_output(self):
@@ -316,7 +310,7 @@ def monopile_steel_cost(self):
         _key = "monopile_steel_cost"
 
         try:
-            cost = _design.get(_key, self.defaults[_key])
+            cost = _design.get(_key, common_costs[_key])
 
         except KeyError:
             raise Exception("Cost of monopile steel not found.")
@@ -333,7 +327,7 @@ def tp_steel_cost(self):
         _key = "tp_steel_cost"
 
         try:
-            cost = _design.get(_key, self.defaults[_key])
+            cost = _design.get(_key, common_costs[_key])
 
         except KeyError:
             raise Exception("Cost of transition piece steel not found.")
diff --git a/ORBIT/phases/design/mooring_system_design.py b/ORBIT/phases/design/mooring_system_design.py
index f14d0876..269a34e7 100644
--- a/ORBIT/phases/design/mooring_system_design.py
+++ b/ORBIT/phases/design/mooring_system_design.py
@@ -31,9 +31,11 @@ class MooringSystemDesign(DesignPhase):
             "num_lines": "int",
             "line_diam": "m, float",
             "line_mass": "t",
+            "line_cost": "USD",
             "line_length": "m",
             "anchor_mass": "t",
             "anchor_type": "str",
+            "anchor_cost": "USD",
         }
     }
 
@@ -54,7 +56,6 @@ def __init__(self, config, **kwargs):
         self.num_lines = self._design.get("num_lines", 4)
         self.anchor_type = self._design.get("anchor_type", "Suction Pile")
 
-        self.extract_defaults()
         self._outputs = {}
 
     def run(self):
@@ -135,10 +136,15 @@ def calculate_anchor_mass_cost(self):
             self.anchor_mass = 50
             self.anchor_cost = sqrt(self.breaking_load / 9.81 / 1250) * 150000
 
-    def calculate_total_cost(self):
-        """
-        Returns the total cost of the mooring system.
-        """
+    @property
+    def line_cost(self):
+        """Returns cost of one line mooring line."""
+
+        return self.line_length * self.line_cost_rate
+
+    @property
+    def total_cost(self):
+        """Returns the total cost of the mooring system."""
 
         return (
             self.num_lines
@@ -146,37 +152,6 @@ def calculate_total_cost(self):
             * (self.anchor_cost + self.line_length * self.line_cost_rate)
         )
 
-    @property
-    def design_result(self):
-        """Returns the results of the design phase."""
-
-        return {
-            "mooring_system": {
-                "num_lines": self.num_lines,
-                "line_diam": self.line_diam,
-                "line_mass": self.line_mass,
-                "line_length": self.line_length,
-                "anchor_mass": self.anchor_mass,
-                "anchor_type": self.anchor_type,
-            }
-        }
-
-    @property
-    def total_phase_cost(self):
-        """Returns total phase cost in $USD."""
-
-        _design = self.config.get("mooring_system_design", {})
-        design_cost = _design.get("design_cost", 0.0)
-        return self.calculate_total_cost() + design_cost
-
-    @property
-    def total_phase_time(self):
-        """Returns total phase time in hours."""
-
-        _design = self.config.get("mooring_system_design", {})
-        phase_time = _design.get("design_time", 0.0)
-        return phase_time
-
     @property
     def detailed_output(self):
         """Returns detailed phase information."""
@@ -186,8 +161,15 @@ def detailed_output(self):
             "line_diam": self.line_diam,
             "line_mass": self.line_mass,
             "line_length": self.line_length,
+            "line_cost": self.line_cost,
             "anchor_type": self.anchor_type,
             "anchor_mass": self.anchor_mass,
             "anchor_cost": self.anchor_cost,
-            "system_cost": self.calculate_total_cost(),
+            "system_cost": self.total_cost,
         }
+
+    @property
+    def design_result(self):
+        """Returns the results of the design phase."""
+
+        return {"mooring_system": self.detailed_output}
diff --git a/ORBIT/phases/design/oss_design.py b/ORBIT/phases/design/oss_design.py
old mode 100755
new mode 100644
index 24e90332..eb367388
--- a/ORBIT/phases/design/oss_design.py
+++ b/ORBIT/phases/design/oss_design.py
@@ -31,7 +31,6 @@ class OffshoreSubstationDesign(DesignPhase):
             "oss_substructure_cost_rate": "USD/t (optional)",
             "oss_pile_cost_rate": "USD/t (optional)",
             "num_substations": "int (optional)",
-            "design_time": "h (optional)",
         },
     }
 
@@ -69,22 +68,47 @@ def run(self):
         self.calc_ancillary_system_cost()
         self.calc_assembly_cost()
         self.calc_substructure_mass_and_cost()
-        self.calc_substation_cost()
 
         self._outputs["offshore_substation_substructure"] = {
             "type": "Monopile",  # Substation install only supports monopiles
             "deck_space": self.substructure_deck_space,
             "mass": self.substructure_mass,
             "length": self.substructure_length,
+            "unit_cost": self.substructure_cost,
         }
 
         self._outputs["offshore_substation_topside"] = {
             "deck_space": self.topside_deck_space,
             "mass": self.topside_mass,
+            "unit_cost": self.substation_cost,
         }
 
         self._outputs["num_substations"] = self.num_substations
 
+    @property
+    def substation_cost(self):
+        """Returns total procuremet cost of the topside."""
+
+        return (
+            self.mpt_cost
+            + self.topside_cost
+            + self.shunt_reactor_cost
+            + self.switchgear_costs
+            + self.ancillary_system_costs
+            + self.land_assembly_cost
+        )
+
+    @property
+    def total_cost(self):
+        """Returns total procurement cost of the substation(s)."""
+
+        if not self._outputs:
+            raise Exception("Has OffshoreSubstationDesign been ran yet?")
+
+        return (
+            self.substructure_cost + self.substation_cost
+        ) * self.num_substations
+
     def calc_substructure_length(self):
         """
         Calculates substructure length as the site depth + 10m
@@ -265,27 +289,6 @@ def calc_substructure_mass_and_cost(self):
 
         self.substructure_mass = substructure_mass + substructure_pile_mass
 
-    def calc_substation_cost(self):
-        """
-        Calculates the total cost of the substation solution, based on the
-        number of configured substations.
-        """
-
-        self.substation_cost = (
-            sum(
-                [
-                    self.mpt_cost,
-                    self.topside_cost,
-                    self.shunt_reactor_cost,
-                    self.switchgear_costs,
-                    self.ancillary_system_costs,
-                    self.land_assembly_cost,
-                    self.substructure_cost,
-                ]
-            )
-            * self.num_substations
-        )
-
     @property
     def design_result(self):
         """
@@ -297,23 +300,6 @@ def design_result(self):
 
         return self._outputs
 
-    @property
-    def total_phase_cost(self):
-        """Returns total phase cost in $USD."""
-
-        if not self._outputs:
-            raise Exception("Has OffshoreSubstationDesign been ran yet?")
-
-        return self.substation_cost
-
-    @property
-    def total_phase_time(self):
-        """Returns total phase time in hours."""
-
-        _design = self.config.get("substation_design", {})
-        phase_time = _design.get("design_time", 0.0)
-        return phase_time
-
     @property
     def detailed_output(self):
         """Returns detailed phase information."""
diff --git a/ORBIT/phases/design/project_development.py b/ORBIT/phases/design/project_development.py
deleted file mode 100644
index bdb8416f..00000000
--- a/ORBIT/phases/design/project_development.py
+++ /dev/null
@@ -1,245 +0,0 @@
-"""Provides the `ProjectDevelopment` class."""
-
-__author__ = "Jake Nunemaker"
-__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
-__maintainer__ = "Jake Nunemaker"
-__email__ = "jake.nunemaker@nrel.gov"
-
-
-from ORBIT.library import extract_library_specs
-
-from .design_phase import DesignPhase
-
-
-class ProjectDevelopment(DesignPhase):
-    """Project Development Class."""
-
-    expected_config = {
-        "project_development": {
-            "site_auction_duration": "h (optional)",
-            "site_auction_price": "USD(optional)",
-            "site_assessment_plan_duration": "h (optional)",
-            "site_assessment_plan_cost": "USD (optional)",
-            "site_assessment_duration": "h (optional)",
-            "site_assessment_cost": "USD (optional)",
-            "construction_operations_plan_duration": "h (optional)",
-            "construction_operations_plan_cost": "USD (optional)",
-            "boem_review_duration": "h (optional)",
-            "boem_review_cost": "USD (optional)",
-            "design_install_plan_duration": "h (optional)",
-            "design_install_plan_cost": "USD (optional)",
-        }
-    }
-
-    output_config = {}
-
-    def __init__(self, config, **kwargs):
-        """
-        Creates an instance of ProjectDevelopment.
-
-        Parameters
-        ----------
-        config : dict
-        """
-
-        config = self.initialize_library(config, **kwargs)
-        self.config = self.validate_config(config)
-        self._outputs = {}
-
-        self.defaults = extract_library_specs("defaults", "project")
-
-    def run(self):
-        """
-        Main run function. Passes ``self.config['project_development']`` to the
-        following methods:
-
-        - :py:meth:`.site_auction`
-        - :py:meth:`.site_assessment_plan_development`
-        - :py:meth:`.site_assessment`
-        - :py:meth:`.construction_operations_plan_development`
-        - :py:meth:`.boem_review`
-        - :py:meth:`.design_install_plan_development`
-        """
-
-        dev_specs = self.config.get("project_development", {})
-
-        self.site_auction(**dev_specs)
-        self.site_assessment_plan_development(**dev_specs)
-        self.site_assessment(**dev_specs)
-        self.construction_operations_plan_development(**dev_specs)
-        self.boem_review(**dev_specs)
-        self.design_install_plan_development(**dev_specs)
-
-    @property
-    def design_result(self):
-        """
-        Returns design results for ProjectDevelopment. This method currently
-        returns an empty dictionary as ProjectDevelopment does not produce any
-        additional config keys.
-        """
-
-        return {}
-
-    @property
-    def total_phase_cost(self):
-        """Returns total phase cost in $USD."""
-
-        phase_cost = sum([v["cost"] for v in self._outputs.values()])
-        return phase_cost
-
-    @property
-    def total_phase_time(self):
-        """Returns total phase time in hours."""
-
-        phase_time = sum([v["duration"] for v in self._outputs.values()])
-        return phase_time
-
-    @property
-    def detailed_output(self):
-        """Returns detailed phase information."""
-
-        if not self._outputs:
-            raise Exception("Has ProjectDevelopment been ran yet?")
-
-        return self._outputs
-
-    def site_auction(self, **development_specs):
-        """
-        Cost and duration associated with lease area auction.
-
-        Parameters
-        ----------
-        development_specs : dict
-            Dicitonary containing development specifications. Keys:
-
-            - ``site_auction_duration`` - Auction duration in hours.
-            - ``site_auction_cost`` - Auction price.
-        """
-
-        t = "site_auction_duration"
-        c = "site_auction_cost"
-        self._outputs["site_auction"] = {
-            "duration": development_specs.get(t, self.defaults[t]),
-            "cost": development_specs.get(c, self.defaults[c]),
-        }
-
-    def site_assessment_plan_development(self, **development_specs):
-        """
-        Cost and duration associated with developing a site assessment plan.
-
-        Parameters
-        ----------
-        development_specs : dict
-            Dicitonary containing development specifications. Keys:
-
-            - ``site_assessment_plan_duration`` - Site assesment plan
-              development duration in hours.
-            - ``site_assessment_plan_cost`` - Site assessment plan development
-              cost.
-        """
-
-        t = "site_assessment_plan_duration"
-        c = "site_assessment_plan_cost"
-        self._outputs["site_assessment_plan"] = {
-            "duration": development_specs.get(t, self.defaults[t]),
-            "cost": development_specs.get(c, self.defaults[c]),
-        }
-
-    def site_assessment(self, **development_specs):
-        """
-        Cost and duration for conducting site assessments/surveys and
-        obtaining permits.
-
-        Parameters
-        ----------
-        development_specs : dict
-            Dicitonary containing development specifications. Keys:
-
-            - ``site_assessment_duration`` - Site assesment duration in hours.
-            - ``site_assessment_cost`` - Site assessment costs.
-        """
-
-        t = "site_assessment_duration"
-        c = "site_assessment_cost"
-        self._outputs["site_assessment"] = {
-            "duration": development_specs.get(t, self.defaults[t]),
-            "cost": development_specs.get(c, self.defaults[c]),
-        }
-
-    def construction_operations_plan_development(self, **development_specs):
-        """
-        Cost and duration for developing the Construction and Operations Plan
-        (COP). Typically occurs in parallel to Site Assessment.
-
-        Parameters
-        ----------
-        development_specs : dict
-            Dicitonary containing development specifications. Keys:
-
-            - ``construction_operations_plan_duration`` - Construction and
-              operations plan development duration in hours.
-            - ``construction_operations_plan_cost`` - Construction and
-              operations plan development cost.
-        """
-
-        t = "construction_operations_plan_duration"
-        c = "construction_operations_plan_cost"
-        self._outputs["construction_operations_plan"] = {
-            "duration": development_specs.get(t, self.defaults[t]),
-            "cost": development_specs.get(c, self.defaults[c]),
-        }
-
-    def boem_review(self, **development_specs):
-        """
-        Cost and duration for BOEM to review the Construction and Operations
-        Plan.
-
-        Parameters
-        ----------
-        development_specs : dict
-            Dicitonary containing development specifications. Keys:
-
-            - ``boem_review_duration`` - BOEM review duration in hours.
-            - ``boem_review_cost`` - BOEM review cost. Typically not a cost to
-              developers.
-        """
-
-        t = "boem_review_duration"
-        c = "boem_review_cost"
-        self._outputs["boem_review"] = {
-            "duration": development_specs.get(t, self.defaults[t]),
-            "cost": development_specs.get(c, self.defaults[c]),
-        }
-
-    def design_install_plan_development(self, **development_specs):
-        """
-        Cost and duration for developing the Design and Installation Plan.
-        Typically occurs in parallel with BOEM Review.
-
-        Parameters
-        ----------
-        development_specs : dict
-            Dicitonary containing development specifications. Keys:
-
-            - ``design_install_plan_duration`` - Design and installation plan
-              development duration in hours.
-            - ``design_install_plan_cost`` - Design and installation plan
-              development cost.
-        """
-
-        t = "design_install_plan_duration"
-        c = "design_install_plan_cost"
-        self._outputs["design_install_plan"] = {
-            "duration": development_specs.get(t, self.defaults[t]),
-            "cost": development_specs.get(c, self.defaults[c]),
-        }
-
-    @property
-    def design_result(self):
-        """
-        Returns design results for ProjectDevelopment. This method currently
-        returns an empty dictionary as ProjectDevelopment does not produce any
-        additional config keys.
-        """
-
-        return {}
diff --git a/ORBIT/phases/design/scour_protection_design.py b/ORBIT/phases/design/scour_protection_design.py
index 9b55be23..efa66b4f 100644
--- a/ORBIT/phases/design/scour_protection_design.py
+++ b/ORBIT/phases/design/scour_protection_design.py
@@ -55,14 +55,18 @@ class ScourProtectionDesign(DesignPhase):
         "scour_protection_design": {
             "cost_per_tonne": "USD/t",
             "rock_density": "kg/m3 (optional)",
-            "design_time": "h (optional)",
             "soil_friction_angle": "float (optional)",
             "scour_depth_equilibrium": "float (optional)",
             "scour_protection_depth": "m (optional)",
         },
     }
 
-    output_config = {"scour_protection": {"tons_per_substructure": "int"}}
+    output_config = {
+        "scour_protection": {
+            "tonnes_per_substructure": "t",
+            "cost_per_tonne": "USD/t",
+        }
+    }
 
     def __init__(self, config, **kwargs):
         """
@@ -126,10 +130,8 @@ def run(self):
         self.compute_scour_protection_tonnes_to_install()
 
     @property
-    def total_phase_cost(self):
-        """
-        Returns the total cost of the phase in $USD
-        """
+    def total_cost(self):
+        """Returns the total cost of the phase in $USD"""
 
         cost = (
             self._design["cost_per_tonne"]
@@ -138,22 +140,13 @@ def total_phase_cost(self):
         )
         return cost
 
-    @property
-    def total_phase_time(self):
-        """
-        Returns the total time requried for the phase, in hours.
-        """
-
-        return self._design.get("design_time", 0.0)
-
     @property
     def detailed_output(self):
-        """
-        Returns detailed outputs of the design.
-        """
+        """Returns detailed outputs of the design."""
 
         _out = {
-            "scour_protection_per_substructure": self.scour_protection_tonnes
+            "tonnes_per_substructure": self.scour_protection_tonnes,
+            "cost_per_tonne": self._design["cost_per_tonne"],
         }
         return _out
 
@@ -170,9 +163,5 @@ def design_result(self):
                 - ``tonnes_per_substructure`` : `int`
         """
 
-        output = {
-            "scour_protection": {
-                "tons_per_substructure": self.scour_protection_tonnes
-            }
-        }
+        output = {"scour_protection": self.detailed_output}
         return output
diff --git a/ORBIT/phases/design/semi_submersible_design.py b/ORBIT/phases/design/semi_submersible_design.py
index 74392e8e..58404a29 100644
--- a/ORBIT/phases/design/semi_submersible_design.py
+++ b/ORBIT/phases/design/semi_submersible_design.py
@@ -22,12 +22,16 @@ class SemiSubmersibleDesign(DesignPhase):
             "heave_plate_CR": "$/t (optional, default: 6250)",
             "secondary_steel_CR": "$/t (optional, default: 7250)",
             "towing_speed": "km/h (optional, default: 6)",
-            "design_time": "h, (optional, default: 0)",
-            "design_cost": "h, (optional, default: 0)",
         },
     }
 
-    output_config = {}
+    output_config = {
+        "substructure": {
+            "mass": "t",
+            "unit_cost": "USD",
+            "towing_speed": "km/h",
+        }
+    }
 
     def __init__(self, config, **kwargs):
         """
@@ -40,7 +44,6 @@ def __init__(self, config, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
         self._design = self.config.get("semisubmersible_design", {})
 
         self._outputs = {}
@@ -50,11 +53,11 @@ def run(self):
 
         substructure = {
             "mass": self.substructure_mass,
-            "cost": self.substructure_cost,
+            "unit_cost": self.substructure_cost,
             "towing_speed": self._design.get("towing_speed", 6),
         }
 
-        self._outputs["semisubmersible"] = substructure
+        self._outputs["substructure"] = substructure
 
     @property
     def stiffened_column_mass(self):
@@ -173,13 +176,6 @@ def total_substructure_mass(self):
         num = self.config["plant"]["num_turbines"]
         return num * self.substructure_mass
 
-    @property
-    def total_substructure_cost(self):
-        """Retruns cost of all substructures."""
-
-        num = self.config["plant"]["num_turbines"]
-        return num * self.substructure_cost
-
     @property
     def design_result(self):
         """Returns the result of `self.run()`"""
@@ -190,21 +186,11 @@ def design_result(self):
         return self._outputs
 
     @property
-    def total_phase_cost(self):
+    def total_cost(self):
         """Returns total phase cost in $USD."""
 
-        _design = self.config.get("semisubmersible_design", {})
-        design_cost = _design.get("design_cost", 0.0)
-
-        return design_cost + self.total_substructure_cost
-
-    @property
-    def total_phase_time(self):
-        """Returns total phase time in hours."""
-
-        _design = self.config.get("semisubmersible_design", {})
-        phase_time = _design.get("design_time", 0.0)
-        return phase_time
+        num = self.config["plant"]["num_turbines"]
+        return num * self.substructure_cost
 
     @property
     def detailed_output(self):
diff --git a/ORBIT/phases/design/spar_design.py b/ORBIT/phases/design/spar_design.py
index a5531161..224c4a5e 100644
--- a/ORBIT/phases/design/spar_design.py
+++ b/ORBIT/phases/design/spar_design.py
@@ -24,12 +24,17 @@ class SparDesign(DesignPhase):
             "ballast_material_CR": "$/t (optional, default: 100)",
             "secondary_steel_CR": "$/t (optional, default: 7250)",
             "towing_speed": "km/h (optional, default: 6)",
-            "design_time": "h, (optional, default: 0)",
-            "design_cost": "h, (optional, default: 0)",
         },
     }
 
-    output_config = {}
+    output_config = {
+        "substructure": {
+            "mass": "t",
+            "ballasted_mass": "t",
+            "unit_cost": "USD",
+            "towing_speed": "km/h",
+        }
+    }
 
     def __init__(self, config, **kwargs):
         """
@@ -42,7 +47,6 @@ def __init__(self, config, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
         self._design = self.config.get("spar_design", {})
 
         self._outputs = {}
@@ -53,11 +57,11 @@ def run(self):
         substructure = {
             "mass": self.unballasted_mass,
             "ballasted_mass": self.ballasted_mass,
-            "cost": self.substructure_cost,
+            "unit_cost": self.substructure_cost,
             "towing_speed": self._design.get("towing_speed", 6),
         }
 
-        self._outputs["spar"] = substructure
+        self._outputs["substructure"] = substructure
 
     @property
     def stiffened_column_mass(self):
@@ -195,29 +199,12 @@ def detailed_output(self):
         return _outputs
 
     @property
-    def total_substructure_cost(self):
-        """Retruns cost of all substructures."""
+    def total_cost(self):
+        """Returns total phase cost in $USD."""
 
         num = self.config["plant"]["num_turbines"]
         return num * self.substructure_cost
 
-    @property
-    def total_phase_cost(self):
-        """Returns total phase cost in $USD."""
-
-        _design = self.config.get("spar_design", {})
-        design_cost = _design.get("design_cost", 0.0)
-
-        return design_cost + self.total_substructure_cost
-
-    @property
-    def total_phase_time(self):
-        """Returns total phase time in hours."""
-
-        _design = self.config.get("spar_design", {})
-        phase_time = _design.get("design_time", 0.0)
-        return phase_time
-
     @property
     def design_result(self):
         """Returns the result of `self.run()`"""
diff --git a/ORBIT/phases/install/cable_install/array.py b/ORBIT/phases/install/cable_install/array.py
old mode 100755
new mode 100644
index a64f7f46..ad822d0f
--- a/ORBIT/phases/install/cable_install/array.py
+++ b/ORBIT/phases/install/cable_install/array.py
@@ -42,6 +42,7 @@ class ArrayCableInstallation(InstallPhase):
         "array_cable_trench_vessel": "str (optional)",
         "site": {"distance": "km", "depth": "m"},
         "array_system": {
+            "system_cost": "USD",
             "num_strings": "int (optional, default: 10)",
             "free_cable_length": "km (optional, default: 'depth')",
             "cables": {
@@ -71,7 +72,6 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.initialize_port()
         self.setup_simulation(**kwargs)
@@ -109,6 +109,12 @@ def setup_simulation(self, **kwargs):
             **kwargs,
         )
 
+    @property
+    def system_capex(self):
+        """Returns total procurement cost of the array system."""
+
+        return self.config["array_system"]["system_cost"]
+
     def initialize_installation_vessel(self):
         """Creates the array cable installation vessel."""
 
diff --git a/ORBIT/phases/install/cable_install/common.py b/ORBIT/phases/install/cable_install/common.py
old mode 100755
new mode 100644
index 437cdfdd..f22cc8e3
--- a/ORBIT/phases/install/cable_install/common.py
+++ b/ORBIT/phases/install/cable_install/common.py
@@ -9,7 +9,7 @@
 from marmot import process
 
 from ORBIT.core.logic import position_onsite
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.defaults import process_times as pt
 
 
 class SimpleCable:
diff --git a/ORBIT/phases/install/cable_install/export.py b/ORBIT/phases/install/cable_install/export.py
old mode 100755
new mode 100644
index 62dbcf26..c97c10c2
--- a/ORBIT/phases/install/cable_install/export.py
+++ b/ORBIT/phases/install/cable_install/export.py
@@ -71,7 +71,6 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.initialize_port()
         self.extract_distances()
@@ -115,6 +114,12 @@ def setup_simulation(self, **kwargs):
             **kwargs,
         )
 
+    @property
+    def system_capex(self):
+        """Returns total procurement cost of the array system."""
+
+        return self.config["export_system"]["system_cost"]
+
     def extract_distances(self):
         """Extracts distances from input configuration or default values."""
 
diff --git a/ORBIT/phases/install/install_phase.py b/ORBIT/phases/install/install_phase.py
index 3f200dcd..06257b14 100644
--- a/ORBIT/phases/install/install_phase.py
+++ b/ORBIT/phases/install/install_phase.py
@@ -11,9 +11,11 @@
 
 import numpy as np
 import simpy
+import pandas as pd
 
 from ORBIT.core import Port, Environment
 from ORBIT.phases import BasePhase
+from ORBIT.core.defaults import common_costs
 
 
 class InstallPhase(BasePhase):
@@ -25,7 +27,7 @@ def __init__(self, weather, **kwargs):
 
         Parameters
         ----------
-        weather : np.ndarray
+        weather : pd.DataFrame | np.ndarray
             Weather profile at site.
         """
 
@@ -42,6 +44,9 @@ def initialize_environment(self, weather, **kwargs):
             Weather profile at site.
         """
 
+        if isinstance(weather, pd.DataFrame):
+            weather = weather.to_records()
+
         env_name = kwargs.get("env_name", "Environment")
         self.env = Environment(name=env_name, state=weather, **kwargs)
 
@@ -103,22 +108,18 @@ def port_costs(self):
         else:
             key = "port_cost_per_month"
             port_config = self.config.get("port", {})
-            rate = port_config.get("monthly_rate", self.defaults[key])
+            rate = port_config.get("monthly_rate", common_costs[key])
 
             months = self.total_phase_time / (8760 / 12)
             return months * rate
 
     @property
-    def total_phase_cost(self):
-        """Returns total phase cost in $USD."""
-
-        return self.action_costs + self.port_costs
-
-    @property
-    def action_costs(self):
-        """Returns sum cost of all actions."""
+    def installation_capex(self):
+        """Returns sum of all installation costs in `self.env.actions`."""
 
-        return np.nansum([a["cost"] for a in self.env.actions])
+        return (
+            np.nansum([a["cost"] for a in self.env.actions]) + self.port_costs
+        )
 
     @property
     def total_phase_time(self):
diff --git a/ORBIT/phases/install/monopile_install/common.py b/ORBIT/phases/install/monopile_install/common.py
index c6cf7594..65d7df72 100644
--- a/ORBIT/phases/install/monopile_install/common.py
+++ b/ORBIT/phases/install/monopile_install/common.py
@@ -9,7 +9,8 @@
 from marmot import process
 
 from ORBIT.core import Cargo
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.logic import jackdown_if_required
+from ORBIT.core.defaults import process_times as pt
 
 
 class Monopile(Cargo):
@@ -124,7 +125,7 @@ def lower_monopile(vessel, **kwargs):
     depth = kwargs.get("site_depth", None)
     rate = vessel.crane.crane_rate(**kwargs)
 
-    height = (vessel.jacksys.air_gap + vessel.jacksys.leg_pen + depth) / rate
+    height = (depth + 10) / rate  # Assumed 10m deck height added to site depth
     lower_time = height / rate
 
     yield vessel.task(
@@ -184,11 +185,8 @@ def lower_transition_piece(vessel, **kwargs):
     vessel.task representing time to "Lower Transition Piece".
     """
 
-    rate = vessel.crane.crane_rate(**kwargs)
-    lower_time = vessel.jacksys.air_gap / rate
-
     yield vessel.task(
-        "Lower TP", lower_time, constraints=vessel.operational_limits, **kwargs
+        "Lower TP", 1, constraints=vessel.operational_limits, **kwargs
     )
 
 
@@ -306,7 +304,7 @@ def install_transition_piece(vessel, tp, **kwargs):
     - Reequip crane, ``vessel.crane.reequip()``
     - Lower transition piece, ``tasks.lower_transition_piece()``
     - Install connection, see below.
-    - Jackdown, ``vessel.jacksys.jacking_time()``
+    - Jackdown, ``vessel.jacksys.jacking_time()`` (if a jackup vessel)
 
     The transition piece can either be installed with a bolted or a grouted
     connection. By default, ORBIT uses the bolted connection with the following
@@ -330,9 +328,6 @@ def install_transition_piece(vessel, tp, **kwargs):
 
     connection = kwargs.get("tp_connection_type", "bolted")
     reequip_time = vessel.crane.reequip(**kwargs)
-    site_depth = kwargs.get("site_depth", None)
-    extension = kwargs.get("extension", site_depth + 10)
-    jackdown_time = vessel.jacksys.jacking_time(extension, site_depth)
 
     yield vessel.task(
         "Crane Reequip",
@@ -342,10 +337,10 @@ def install_transition_piece(vessel, tp, **kwargs):
     )
     yield lower_transition_piece(vessel, **kwargs)
 
-    if connection is "bolted":
+    if connection == "bolted":
         yield bolt_transition_piece(vessel, **kwargs)
 
-    elif connection is "grouted":
+    elif connection == "grouted":
 
         yield pump_transition_piece_grout(vessel, **kwargs)
         yield cure_transition_piece_grout(vessel)
@@ -356,6 +351,4 @@ def install_transition_piece(vessel, tp, **kwargs):
             "not recognized. Must be 'bolted' or 'grouted'."
         )
 
-    yield vessel.task(
-        "Jackdown", jackdown_time, constraints=vessel.transit_limits, **kwargs
-    )
+    yield jackdown_if_required(vessel, **kwargs)
diff --git a/ORBIT/phases/install/monopile_install/standard.py b/ORBIT/phases/install/monopile_install/standard.py
index 01e438f8..4e411eb1 100644
--- a/ORBIT/phases/install/monopile_install/standard.py
+++ b/ORBIT/phases/install/monopile_install/standard.py
@@ -56,8 +56,13 @@ class MonopileInstallation(InstallPhase):
             "diameter": "m",
             "deck_space": "m2",
             "mass": "t",
+            "unit_cost": "USD",
+        },
+        "transition_piece": {
+            "deck_space": "m2",
+            "mass": "t",
+            "unit_cost": "USD",
         },
-        "transition_piece": {"deck_space": "m2", "mass": "t"},
     }
 
     def __init__(self, config, weather=None, **kwargs):
@@ -77,13 +82,21 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.initialize_port()
         self.initialize_wtiv()
         self.initialize_monopiles()
         self.setup_simulation(**kwargs)
 
+    @property
+    def system_capex(self):
+        """Returns procurement cost of the substructures."""
+
+        return (
+            self.config["monopile"]["unit_cost"]
+            + self.config["transition_piece"]["unit_cost"]
+        ) * self.config["plant"]["num_turbines"]
+
     def setup_simulation(self, **kwargs):
         """
         Sets up simulation infrastructure, routing to specific methods dependent
diff --git a/ORBIT/phases/install/mooring_install/mooring.py b/ORBIT/phases/install/mooring_install/mooring.py
index e0d3f4f0..de14d4b6 100644
--- a/ORBIT/phases/install/mooring_install/mooring.py
+++ b/ORBIT/phases/install/mooring_install/mooring.py
@@ -10,7 +10,7 @@
 
 from ORBIT.core import Cargo, Vessel
 from ORBIT.core.logic import position_onsite, get_list_of_items_from_port
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import InstallPhase
 from ORBIT.core.exceptions import ItemNotFound
 
@@ -28,7 +28,9 @@ class MooringSystemInstallation(InstallPhase):
         "mooring_system": {
             "num_lines": "int",
             "line_mass": "t",
+            "line_cost": "USD",
             "anchor_mass": "t",
+            "anchor_cost": "USD",
             "anchor_type": "str (optional, default: 'Suction Pile')",
         },
     }
@@ -49,7 +51,6 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.setup_simulation(**kwargs)
 
@@ -68,13 +69,22 @@ def setup_simulation(self, **kwargs):
         depth = self.config["site"]["depth"]
         distance = self.config["site"]["distance"]
 
+        self.num_lines = self.config["mooring_system"]["num_lines"]
+        self.line_cost = self.config["mooring_system"]["line_cost"]
+        self.anchor_cost = self.config["mooring_system"]["anchor_cost"]
+
         install_mooring_systems(
-            self.vessel,
-            self.port,
-            distance,
-            depth,
-            self.number_systems,
-            **kwargs,
+            self.vessel, self.port, distance, depth, self.num_systems, **kwargs
+        )
+
+    @property
+    def system_capex(self):
+        """Returns total procurement cost of all mooring systems."""
+
+        return (
+            self.num_systems
+            * self.num_lines
+            * (self.line_cost + self.anchor_cost)
         )
 
     def initialize_installation_vessel(self):
@@ -95,9 +105,9 @@ def initialize_components(self):
         """Initializes the Cargo components at port."""
 
         system = MooringSystem(**self.config["mooring_system"])
-        self.number_systems = self.config["plant"]["num_turbines"]
+        self.num_systems = self.config["plant"]["num_turbines"]
 
-        for _ in range(self.number_systems):
+        for _ in range(self.num_systems):
             self.port.put(system)
 
     @property
diff --git a/ORBIT/phases/install/oss_install/common.py b/ORBIT/phases/install/oss_install/common.py
index adc964e5..9b0c5212 100644
--- a/ORBIT/phases/install/oss_install/common.py
+++ b/ORBIT/phases/install/oss_install/common.py
@@ -9,7 +9,8 @@
 from marmot import process
 
 from ORBIT.core import Cargo
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.logic import stabilize, jackdown_if_required
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install.monopile_install.common import (
     bolt_transition_piece,
     cure_transition_piece_grout,
@@ -124,9 +125,6 @@ def install_topside(vessel, topside, **kwargs):
 
     connection = kwargs.get("topside_connection_type", "bolted")
     reequip_time = vessel.crane.reequip(**kwargs)
-    site_depth = kwargs.get("site_depth", None)
-    extension = kwargs.get("extension", site_depth + 10)
-    jackdown_time = vessel.jacksys.jacking_time(extension, site_depth)
 
     yield vessel.task(
         "Crane Reequip",
@@ -137,10 +135,10 @@ def install_topside(vessel, topside, **kwargs):
     yield lift_topside(vessel)
     yield attach_topside(vessel)
 
-    if connection is "bolted":
+    if connection == "bolted":
         yield bolt_transition_piece(vessel, **kwargs)
 
-    elif connection is "grouted":
+    elif connection == "grouted":
 
         yield pump_transition_piece_grout(vessel, **kwargs)
         yield cure_transition_piece_grout(vessel, **kwargs)
@@ -151,6 +149,4 @@ def install_topside(vessel, topside, **kwargs):
             "not recognized. Must be 'bolted' or 'grouted'."
         )
 
-    yield vessel.task(
-        "Jackdown", jackdown_time, constraints=vessel.transit_limits, **kwargs
-    )
+    yield jackdown_if_required(vessel, **kwargs)
diff --git a/ORBIT/phases/install/oss_install/standard.py b/ORBIT/phases/install/oss_install/standard.py
index 07907229..8f82b978 100644
--- a/ORBIT/phases/install/oss_install/standard.py
+++ b/ORBIT/phases/install/oss_install/standard.py
@@ -33,7 +33,7 @@ class OffshoreSubstationInstallation(InstallPhase):
     expected_config = {
         "num_substations": "int",
         "oss_install_vessel": "dict | str",
-        "num_feeders": "int",
+        "num_feeders": "int (optional, default: 1)",
         "feeder": "dict | str",
         "site": {"distance": "km", "depth": "m"},
         "port": {
@@ -41,12 +41,17 @@ class OffshoreSubstationInstallation(InstallPhase):
             "monthly_rate": "USD/mo (optional)",
             "name": "str (optional)",
         },
-        "offshore_substation_topside": {"deck_space": "m2", "mass": "t"},
+        "offshore_substation_topside": {
+            "deck_space": "m2",
+            "mass": "t",
+            "unit_cost": "USD",
+        },
         "offshore_substation_substructure": {
             "type": "Monopile",
             "deck_space": "m2",
             "mass": "t",
             "length": "m",
+            "unit_cost": "USD",
         },
     }
 
@@ -66,11 +71,19 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.initialize_port()
         self.setup_simulation(**kwargs)
 
+    @property
+    def system_capex(self):
+        """Returns procurement CapEx of the offshore substations."""
+
+        return self.config["num_substations"] * (
+            self.config["offshore_substation_topside"]["unit_cost"]
+            + self.config["offshore_substation_substructure"]["unit_cost"]
+        )
+
     def setup_simulation(self, **kwargs):
         """
         Initializes required objects for simulation.
@@ -143,7 +156,7 @@ def initialize_feeders(self):
         Initializes feeder barge objects.
         """
 
-        number = self.config.get("num_feeders", None)
+        number = self.config.get("num_feeders", 1)
         feeder_specs = self.config.get("feeder", None)
 
         self.feeders = []
diff --git a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py
index c2398a81..767d084a 100644
--- a/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py
+++ b/ORBIT/phases/install/quayside_assembly_tow/gravity_base.py
@@ -35,6 +35,7 @@ class GravityBasedInstallation(InstallPhase):
         "substructure": {
             "takt_time": "int | float (optional, default: 0)",
             "towing_speed": "int | float (optional, default: 6 km/h)",
+            "unit_cost": "USD",
         },
         "site": {"depth": "m", "distance": "km"},
         "plant": {"num_turbines": "int"},
@@ -65,7 +66,6 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.setup_simulation(**kwargs)
 
@@ -86,6 +86,12 @@ def setup_simulation(self, **kwargs):
         self.initialize_towing_groups()
         self.initialize_support_vessel()
 
+    @property
+    def system_capex(self):
+        """Returns total procurement cost of the substructures."""
+
+        return self.num_turbines * self.config["substructure"]["unit_cost"]
+
     def initialize_substructure_production(self):
         """
         Initializes the production of substructures at port. The number of
diff --git a/ORBIT/phases/install/quayside_assembly_tow/moored.py b/ORBIT/phases/install/quayside_assembly_tow/moored.py
index 94260787..12876c5d 100644
--- a/ORBIT/phases/install/quayside_assembly_tow/moored.py
+++ b/ORBIT/phases/install/quayside_assembly_tow/moored.py
@@ -35,6 +35,7 @@ class MooredSubInstallation(InstallPhase):
         "substructure": {
             "takt_time": "int | float (optional, default: 0)",
             "towing_speed": "int | float (optional, default: 6 km/h)",
+            "unit_cost": "USD",
         },
         "site": {"depth": "m", "distance": "km"},
         "plant": {"num_turbines": "int"},
@@ -65,7 +66,6 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.setup_simulation(**kwargs)
 
@@ -86,6 +86,12 @@ def setup_simulation(self, **kwargs):
         self.initialize_towing_groups()
         self.initialize_support_vessel()
 
+    @property
+    def system_capex(self):
+        """Returns total procurement cost of the substructures."""
+
+        return self.num_turbines * self.config["substructure"]["unit_cost"]
+
     def initialize_substructure_production(self):
         """
         Initializes the production of substructures at port. The number of
diff --git a/ORBIT/phases/install/scour_protection_install/standard.py b/ORBIT/phases/install/scour_protection_install/standard.py
index 91cb0444..e75fe366 100644
--- a/ORBIT/phases/install/scour_protection_install/standard.py
+++ b/ORBIT/phases/install/scour_protection_install/standard.py
@@ -12,7 +12,7 @@
 from marmot import process
 
 from ORBIT.core import Vessel
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import InstallPhase
 from ORBIT.core.exceptions import CargoMassExceeded, InsufficientAmount
 
@@ -34,7 +34,10 @@ class ScourProtectionInstallation(InstallPhase):
             "monthly_rate": "USD/mo (optional)",
             "name": "str (optional)",
         },
-        "scour_protection": {"tons_per_substructure": "float"},
+        "scour_protection": {
+            "tonnes_per_substructure": "t",
+            "cost_per_tonne": "USD/t",
+        },
     }
 
     phase = "Scour Protection Installation"
@@ -55,7 +58,6 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.setup_simulation(**kwargs)
 
@@ -82,20 +84,32 @@ def setup_simulation(self, **kwargs):
                 / 1000.0
             )
 
-        self.tons_per_substructure = ceil(
-            self.config["scour_protection"]["tons_per_substructure"]
+        self.tonnes_per_substructure = ceil(
+            self.config["scour_protection"]["tonnes_per_substructure"]
         )
 
+        self.cost_per_tonne = self.config["scour_protection"]["cost_per_tonne"]
+
         install_scour_protection(
             self.spi_vessel,
             port=self.port,
             site_distance=site_distance,
             turbines=self.num_turbines,
             turbine_distance=turbine_distance,
-            tons_per_substructure=self.tons_per_substructure,
+            tonnes_per_substructure=self.tonnes_per_substructure,
             **kwargs,
         )
 
+    @property
+    def system_capex(self):
+        """Returns total procurement cost of scour protection material."""
+
+        return (
+            self.num_turbines
+            * self.tonnes_per_substructure
+            * self.cost_per_tonne
+        )
+
     def initialize_port(self):
         """
         Initializes a Port object with a simpy.Container of scour protection
@@ -136,7 +150,7 @@ def install_scour_protection(
     site_distance,
     turbines,
     turbine_distance,
-    tons_per_substructure,
+    tonnes_per_substructure,
     **kwargs,
 ):
     """
@@ -155,8 +169,8 @@ def install_scour_protection(
         For now this assumes it traverses an edge and not a diagonal.
     turbines_to_install : int
         Number of turbines where scouring protection must be installed.
-    tons_per_substructure : int
-        Number of tons required to be installed at each substation
+    tonnes_per_substructure : int
+        Number of tonnes required to be installed at each substation
     """
 
     while turbines > 0:
@@ -172,14 +186,14 @@ def install_scour_protection(
             vessel.at_site = True
 
         elif vessel.at_site:
-            if vessel.rock_storage.level >= tons_per_substructure:
+            if vessel.rock_storage.level >= tonnes_per_substructure:
                 # Drop scour protection material
-                yield drop_material(vessel, tons_per_substructure, **kwargs)
+                yield drop_material(vessel, tonnes_per_substructure, **kwargs)
                 turbines -= 1
 
                 # Transit to another turbine
                 if (
-                    vessel.rock_storage.level >= tons_per_substructure
+                    vessel.rock_storage.level >= tonnes_per_substructure
                     and turbines > 0
                 ):
                     yield vessel.transit(turbine_distance)
diff --git a/ORBIT/phases/install/turbine_install/common.py b/ORBIT/phases/install/turbine_install/common.py
index a2c7a8ca..7a9d2a93 100644
--- a/ORBIT/phases/install/turbine_install/common.py
+++ b/ORBIT/phases/install/turbine_install/common.py
@@ -9,7 +9,7 @@
 from marmot import process
 
 from ORBIT.core import Cargo
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.defaults import process_times as pt
 
 
 class TowerSection(Cargo):
diff --git a/ORBIT/phases/install/turbine_install/standard.py b/ORBIT/phases/install/turbine_install/standard.py
index d4683cbb..f00caf72 100644
--- a/ORBIT/phases/install/turbine_install/standard.py
+++ b/ORBIT/phases/install/turbine_install/standard.py
@@ -15,6 +15,7 @@
 
 from ORBIT.core import Vessel
 from ORBIT.core.logic import (
+    jackdown_if_required,
     shuttle_items_to_queue,
     prep_for_site_operations,
     get_list_of_items_from_port,
@@ -84,13 +85,18 @@ def __init__(self, config, weather=None, **kwargs):
 
         config = self.initialize_library(config, **kwargs)
         self.config = self.validate_config(config)
-        self.extract_defaults()
 
         self.initialize_port()
         self.initialize_wtiv()
         self.initialize_turbines()
         self.setup_simulation(**kwargs)
 
+    @property
+    def system_capex(self):
+        """Returns 0 as turbine capex is handled at in ProjectManager."""
+
+        return 0
+
     def setup_simulation(self, **kwargs):
         """
         Sets up simulation infrastructure, routing to specific methods dependent
@@ -352,21 +358,8 @@ def solo_install_turbines(
 
                     yield install_turbine_blade(vessel, blade, **kwargs)
 
-                # Jack-down
-                site_depth = kwargs.get("site_depth", None)
-                extension = kwargs.get("extension", site_depth + 10)
-                jackdown_time = vessel.jacksys.jacking_time(
-                    extension, site_depth
-                )
-
-                yield vessel.task(
-                    "Jackdown",
-                    jackdown_time,
-                    constraints=vessel.transit_limits,
-                )
-
+                yield jackdown_if_required(vessel, **kwargs)
                 vessel.submit_debug_log(progress="Turbine")
-
                 n += 1
 
             else:
@@ -456,19 +449,8 @@ def install_turbine_components_from_queue(
 
                     yield install_turbine_blade(wtiv, blade, **kwargs)
 
-                # Jack-down
-                site_depth = kwargs.get("site_depth", None)
-                extension = kwargs.get("extension", site_depth + 10)
-                jackdown_time = wtiv.jacksys.jacking_time(
-                    extension, site_depth
-                )
-
-                yield wtiv.task(
-                    "Jackdown", jackdown_time, constraints=wtiv.transit_limits
-                )
-
+                yield jackdown_if_required(wtiv, **kwargs)
                 wtiv.submit_debug_log(progress="Turbine")
-
                 n += 1
 
             else:
diff --git a/README.rst b/README.rst
index b3c7c4a4..fb210847 100644
--- a/README.rst
+++ b/README.rst
@@ -4,7 +4,7 @@ ORBIT
 Offshore Renewables Balance of system and Installation Tool
 
 
-:Version: 0.5.0
+:Version: 0.5.2
 :Authors: `Jake Nunemaker <https://www.linkedin.com/in/jake-nunemaker/>`_, `Matt Shields <https://www.linkedin.com/in/matt-shields-834a6b66/>`_, `Rob Hammond <https://www.linkedin.com/in/rob-hammond-33583756/>`_
 :Documentation: `ORBIT Read the Docs <https://orbit-nrel.readthedocs.io/en/latest/>`_
 
@@ -38,7 +38,7 @@ Dependencies
 - NumPy
 - SciPy
 - Matplotlib
-- OpenMDAO (>=2.0)
+- OpenMDAO (>=3.2)
 
 Development
 ~~~~~~~~~~~
diff --git a/docs/source/api.rst b/docs/source/api.rst
index 662d9712..2f01ee1b 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -7,5 +7,6 @@ API Reference
    :maxdepth: 3
 
    api_ProjectManager
+   api_ParametricManager
    api_DesignPhase
    api_InstallPhase
diff --git a/docs/source/api_DesignPhase.rst b/docs/source/api_DesignPhase.rst
index b668a8a1..63f1f5d3 100644
--- a/docs/source/api_DesignPhase.rst
+++ b/docs/source/api_DesignPhase.rst
@@ -10,7 +10,6 @@ trends but are not intended to be used for actual designs.
 .. toctree::
    :maxdepth: 2
 
-   phases/design/api_ProjectDevelopment
    phases/design/api_MonopileDesign
    phases/design/api_ScourProtectionDesign
    phases/design/api_ArraySystemDesign
diff --git a/docs/source/api_ParametricManager.rst b/docs/source/api_ParametricManager.rst
new file mode 100644
index 00000000..ca4b1909
--- /dev/null
+++ b/docs/source/api_ParametricManager.rst
@@ -0,0 +1,7 @@
+.. _manager:
+
+Parametric Configurations - ``ORBIT.ParametricManager``
+=======================================================
+
+.. autoclass:: ORBIT.ParametricManager
+   :members:
diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst
index 245081e2..07f078ce 100644
--- a/docs/source/changelog.rst
+++ b/docs/source/changelog.rst
@@ -3,6 +3,15 @@
 ORBIT Changelog
 ===============
 
+0.5.1
+-----
+
+- Process time kwargs should now be passed through ``ProjectManager`` in a
+  dictionary named ``processes`` in the config.
+- Revised ``prep_for_site_operations`` and related processes to allow for
+  dynamically positioned vessels.
+- Updated WISDEM API to include floating functionality.
+
 0.5.0
 -----
 
diff --git a/docs/source/phases/design/api_ProjectDevelopment.rst b/docs/source/phases/design/api_ProjectDevelopment.rst
deleted file mode 100644
index b3b7da97..00000000
--- a/docs/source/phases/design/api_ProjectDevelopment.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Project Development API
-=======================
-
-.. autoclass:: ORBIT.phases.design.ProjectDevelopment
-   :members:
diff --git a/docs/source/tutorial/parametric_manager.rst b/docs/source/tutorial/parametric_manager.rst
new file mode 100644
index 00000000..b5cf2788
--- /dev/null
+++ b/docs/source/tutorial/parametric_manager.rst
@@ -0,0 +1,6 @@
+.. _parametric:
+
+ParametricManager
+=================
+
+Coming soon!
diff --git a/library/cables/XLPE_1000m_220kV.yaml b/library/cables/XLPE_1000m_220kV.yaml
old mode 100755
new mode 100644
diff --git a/library/cables/XLPE_185mm_66kV.yaml b/library/cables/XLPE_185mm_66kV.yaml
old mode 100755
new mode 100644
diff --git a/library/cables/XLPE_630mm_66kV.yaml b/library/cables/XLPE_630mm_66kV.yaml
old mode 100755
new mode 100644
diff --git a/library/cables/__init__.py b/library/cables/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/library/defaults/__init__.py b/library/defaults/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/library/defaults/project.yaml b/library/defaults/project.yaml
deleted file mode 100644
index 18d382ff..00000000
--- a/library/defaults/project.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-# Development costs
-site_auction_cost: 100e6                      # USD
-site_auction_duration: 0                      # hrs
-site_assessment_plan_cost: 0.5e6              # USD
-site_assessment_plan_duration: 8760           # hrs
-site_assessment_cost: 50e6                    # USD
-site_assessment_duration: 43800               # hrs
-construction_operations_plan_cost: 1e6        # USD
-construction_operations_plan_duration: 43800  # hrs
-boem_review_cost: 0                           # No cost to developer
-boem_review_duration: 8760                    # hrs
-design_install_plan_cost: 0.25e6              # USD
-design_install_plan_duration: 8760            # hrs
-
-# Phase designs
-substructure_design_time: 1000                # hrs
-array_system_design_time: 1000                # hrs
-export_system_design_time: 1000               # hrs
-oss_design_time: 1000                         # hrs
-onshore_trans_design_time: 1000               # hrs
-
-# Substructure costs
-monopile_steel_cost: 3000                     # USD/t
-tp_steel_cost: 3000                           # USD/t
-
-# Port properties
-port_cost_per_month: 2e6                      # USD/month
-
-# Export System Installation
-onshore_construction_rate: 50000              # USD/day
-
-# Vessel parameters
-wtiv_day_rate: 180000                         # USD/day
-feeder_day_rate: 75000                        # USD/day
-scour_day_rate: 120000                        # USD/day
-trench_day_rate: 50000                        # USD/day
-heavy_lift_vessel_day_rate: 500000            # USD/day
-array_cable_install_day_rate: 125000          # USD/day
-export_cable_install_day_rate: 165000         # USD/day
diff --git a/library/project/__init__.py b/library/project/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/library/project/config/__init__.py b/library/project/config/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/library/turbines/12MW_generic.yaml b/library/turbines/12MW_generic.yaml
old mode 100755
new mode 100644
diff --git a/library/turbines/__init__.py b/library/turbines/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/library/vessels/__init__.py b/library/vessels/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/library/vessels/example_cable_lay_vessel.yaml b/library/vessels/example_cable_lay_vessel.yaml
index 28563fc3..728ac348 100644
--- a/library/vessels/example_cable_lay_vessel.yaml
+++ b/library/vessels/example_cable_lay_vessel.yaml
@@ -3,7 +3,6 @@ transport_specs:
   max_windspeed: 25     # m/s
   transit_speed: 11.5   # km/hr
 vessel_specs:
-  beam_length: 30.0     # m
   day_rate: 120000      # USD/day, cost of operating vessel with crew
   min_draft: 4.8        # m
   overall_length: 99.0  # m
diff --git a/library/vessels/example_feeder.yaml b/library/vessels/example_feeder.yaml
index a1054da5..4979d4ba 100644
--- a/library/vessels/example_feeder.yaml
+++ b/library/vessels/example_feeder.yaml
@@ -1,13 +1,9 @@
 crane_specs:
   max_lift: 500             # t
-  radius: 15                # m
 jacksys_specs:
-  air_gap: 10               # m, distance from MSL to jacked up height
   leg_length: 85            # m
-  leg_pen: 5                # m
   max_depth: 40             # m
   max_extension: 60         # m
-  num_legs: 4
   speed_above_depth: 0.5    # m/min
   speed_below_depth: 0.5    # m/min
 storage_specs:
@@ -19,8 +15,4 @@ transport_specs:
   max_windspeed: 20         # m/s
   transit_speed: 6          # km/h
 vessel_specs:
-  beam_length: 35           # m
   day_rate: 75000           # USD/day
-  max_draft: 5              # m
-  min_draft: 4              # m
-  overall_length: 60        # m
diff --git a/library/vessels/example_heavy_feeder.yaml b/library/vessels/example_heavy_feeder.yaml
index 4f8fb567..fb318b9d 100644
--- a/library/vessels/example_heavy_feeder.yaml
+++ b/library/vessels/example_heavy_feeder.yaml
@@ -1,13 +1,9 @@
 crane_specs:
   max_lift: 500             # t
-  radius: 15                # m
 jacksys_specs:
-  air_gap: 10               # m, distance from MSL to jacked up height
-  leg_length: 85            # m
-  leg_pen: 5                # m
+  leg_length: 85            # mm
   max_depth: 40             # m
   max_extension: 60         # m
-  num_legs: 4
   speed_above_depth: 0.5    # m/min
   speed_below_depth: 0.5    # m/min
 storage_specs:
@@ -19,8 +15,4 @@ transport_specs:
   max_windspeed: 20         # m/s
   transit_speed: 6          # km/h
 vessel_specs:
-  beam_length: 35           # m
   day_rate: 120000          # USD/day
-  max_draft: 5              # m
-  min_draft: 4              # m
-  overall_length: 60        # m
diff --git a/library/vessels/example_heavy_lift_vessel.yaml b/library/vessels/example_heavy_lift_vessel.yaml
index a63057d9..e3c841e2 100644
--- a/library/vessels/example_heavy_lift_vessel.yaml
+++ b/library/vessels/example_heavy_lift_vessel.yaml
@@ -1,16 +1,11 @@
 crane_specs:
-  boom_length: 100          # m
   max_hook_height: 72       # m
   max_lift: 5500            # t
   max_windspeed: 15         # m/s
-  radius: 30                # m
 jacksys_specs:
-  air_gap: 10               # m, distance from MSL to jacked up height
   leg_length: 110           # m
-  leg_pen: 5                # m
   max_depth: 75             # m
   max_extension: 85         # m
-  num_legs: 6
   speed_above_depth: 0.5    # m/min
   speed_below_depth: 0.5    # m/min
 storage_specs:
@@ -23,5 +18,3 @@ transport_specs:
   transit_speed: 7          # km/h
 vessel_specs:
   day_rate: 500000          # USD/day
-  max_draft: 4.5            # m
-  overall_length: 102.75    # m
diff --git a/library/vessels/example_scour_protection_vessel.yaml b/library/vessels/example_scour_protection_vessel.yaml
index 6c0edf39..ba498eab 100644
--- a/library/vessels/example_scour_protection_vessel.yaml
+++ b/library/vessels/example_scour_protection_vessel.yaml
@@ -7,8 +7,4 @@ transport_specs:
   max_windspeed: 20     # m/s
   transit_speed: 6      # km/hr
 vessel_specs:
-  beam_length: 35       # m
   day_rate: 120000      # USD/day
-  max_draft: 5          # m
-  min_draft: 4          # m
-  overall_length: 60    # m
diff --git a/library/vessels/example_towing_vessel.yaml b/library/vessels/example_towing_vessel.yaml
index b1de857d..5aa4bf58 100644
--- a/library/vessels/example_towing_vessel.yaml
+++ b/library/vessels/example_towing_vessel.yaml
@@ -3,8 +3,4 @@ transport_specs:
   max_windspeed: 20         # m/s
   transit_speed: 6          # km/h
 vessel_specs:
-  beam_length: 35           # m
   day_rate: 30000           # USD/day
-  max_draft: 5              # m
-  min_draft: 4              # m
-  overall_length: 60        # m
diff --git a/library/vessels/example_wtiv.yaml b/library/vessels/example_wtiv.yaml
index 22f0e6ad..f57430a3 100644
--- a/library/vessels/example_wtiv.yaml
+++ b/library/vessels/example_wtiv.yaml
@@ -1,16 +1,11 @@
 crane_specs:
-  boom_length: 100        # m
   max_hook_height: 100    # m
   max_lift: 1200          # t
   max_windspeed: 15       # m/s
-  radius: 30              # m
 jacksys_specs:
-  air_gap: 10             # m, distance from MSL to jacked up height
   leg_length: 110         # m
-  leg_pen: 5              # m
   max_depth: 75           # m
   max_extension: 85       # m
-  num_legs: 6
   speed_above_depth: 1    # m/min
   speed_below_depth: 2.5  # m/min
 storage_specs:
@@ -22,10 +17,6 @@ transport_specs:
   max_windspeed: 20       # m/s
   transit_speed: 10       # km/h
 vessel_specs:
-  beam_length: 50         # m
   day_rate: 180000        # USD/day
-  max_draft: 6            # m
-  min_draft: 5            # m
-  overall_length: 150     # m
   mobilization_days: 7    # days
   mobilization_mult: 1  # Mobilization multiplier applied to 'day_rate'
diff --git a/library/vessels/floating_barge.yaml b/library/vessels/floating_barge.yaml
index 0dd7212b..1fcd8ef8 100644
--- a/library/vessels/floating_barge.yaml
+++ b/library/vessels/floating_barge.yaml
@@ -1,14 +1,7 @@
 crane_specs:
   max_lift: 500             # t
-  radius: 15                # m
-jacksys_specs:              # TEMPORARY UNTIL FLOATING FUNCTIONALY ADDED
-  air_gap: 10               # m, distance from MSL to jacked up height
-  leg_length: 85            # m
-  leg_pen: 5                # m
-  max_depth: 1000            # m
-  max_extension: 1000        # m
-  speed_above_depth: 10000    # m/min
-  speed_below_depth: 10000   # m/min
+dynamic_positioning_specs:
+  class: 2                  # 1, 2 or 3
 storage_specs:
   max_cargo: 8000           # t
   max_deck_load: 8          # t/m^2
@@ -18,8 +11,4 @@ transport_specs:
   max_windspeed: 20         # m/s
   transit_speed: 6          # km/h
 vessel_specs:
-  beam_length: 35           # m
   day_rate: 120000          # USD/day
-  max_draft: 5              # m
-  min_draft: 4              # m
-  overall_length: 60        # m
diff --git a/library/vessels/floating_heavy_lift_vessel.yaml b/library/vessels/floating_heavy_lift_vessel.yaml
index fea7e5da..44b9f7e9 100644
--- a/library/vessels/floating_heavy_lift_vessel.yaml
+++ b/library/vessels/floating_heavy_lift_vessel.yaml
@@ -1,17 +1,9 @@
 crane_specs:
-  boom_length: 100          # m
   max_hook_height: 72       # m
   max_lift: 5500            # t
   max_windspeed: 15         # m/s
-  radius: 30                # m
-jacksys_specs:              # TEMPORARY UNTIL FLOATING FUNCTIONALY ADDED
-  air_gap: 10               # m, distance from MSL to jacked up height
-  leg_length: 110           # m
-  leg_pen: 5                # m
-  max_depth: 1000            # m
-  max_extension: 1000        # m
-  speed_above_depth: 10000    # m/min
-  speed_below_depth: 10000    # m/min
+dynamic_positioning_specs:
+  class: 2                  # 1, 2 or 3
 storage_specs:
   max_cargo: 8000           # t
   max_deck_load: 15         # t/m^2
@@ -22,5 +14,3 @@ transport_specs:
   transit_speed: 7          # km/h
 vessel_specs:
   day_rate: 500000          # USD/day
-  max_draft: 4.5            # m
-  overall_length: 102.75    # m
diff --git a/library/vessels/future_feeder.yaml b/library/vessels/future_feeder.yaml
index be26f700..e0c26f8f 100644
--- a/library/vessels/future_feeder.yaml
+++ b/library/vessels/future_feeder.yaml
@@ -1,13 +1,9 @@
 crane_specs:
   max_lift: 500
-  radius: 15
 jacksys_specs:
-  air_gap: 10
   leg_length: 85
-  leg_pen: 5
   max_depth: 40
   max_extension: 60
-  num_legs: 4
   speed_above_depth: 0.5
   speed_below_depth: 0.5
 storage_specs:
@@ -19,8 +15,4 @@ transport_specs:
   max_windspeed: 20
   transit_speed: 6
 vessel_specs:
-  beam_length: 35
   day_rate: 100000
-  max_draft: 5
-  min_draft: 4
-  overall_length: 60
diff --git a/setup.py b/setup.py
index 2448bfe2..5bc75c6f 100644
--- a/setup.py
+++ b/setup.py
@@ -12,10 +12,12 @@
     long_description = fh.read()
 
 setup(
-    name="ORBIT",
+    name="orbit-nrel",
+    author="Jake Nunemaker",
     version=versioneer.get_version(),
     cmdclass=versioneer.get_cmdclass(),
     description="Offshore Renewables Balance of system and Installation Tool",
+    long_description=long_description,
     classifiers=[
         "Development Status :: 4 - Beta",
         "Programming Language :: Python :: 3.7",
@@ -23,6 +25,7 @@
     packages=find_packages(
         exclude=["*.tests", "*.tests.*", "tests.*", "tests"]
     ),
+    package_data={"": ["*.yaml"]},
     install_requires=[
         "numpy",
         "matplotlib",
@@ -31,7 +34,8 @@
         "scipy",
         "pandas",
         "pyyaml",
-        "openmdao>= 2.0",
+        "openmdao>=3.2",
+        "python-benedict",
     ],
     extras_require={
         "dev": [


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..bf1b9868
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,33 @@
+
+name: tests
+
+on:
+  pull_request:
+    types: [opened, edited, synchronize]
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      max-parallel: 3
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+        python-version: [3.7, 3.8]
+
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        ref: ${{ github.event.pull_request.head.ref }}
+        fetch-depth: 1
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install pytest
+        python -m pip install .
+    - name: Run pytest
+      run: |
+        python -m pytest -v --disable-warnings
diff --git a/tests/api/wisdem/test_fixed_wisdem_api.py b/tests/api/test_wisdem_api.py
similarity index 56%
rename from tests/api/wisdem/test_fixed_wisdem_api.py
rename to tests/api/test_wisdem_api.py
index 81a05c8b..188f9500 100644
--- a/tests/api/wisdem/test_fixed_wisdem_api.py
+++ b/tests/api/test_wisdem_api.py
@@ -8,16 +8,28 @@
 
 import openmdao.api as om
 
-from ORBIT.api.wisdem import OrbitWisdemFixed
+from ORBIT.api.wisdem import Orbit
 
 
 def test_wisdem_monopile_api_default():
 
     prob = om.Problem()
-    prob.model = OrbitWisdemFixed()
+    prob.model = Orbit(floating=False)
     prob.setup()
 
-    prob.run_driver()
+    prob.run_model()
+
+    prob.model.list_inputs()
+    prob.model.list_outputs()
+
+
+def test_wisdem_floating_api_default():
+
+    prob = om.Problem()
+    prob.model = Orbit(floating=True)
+    prob.setup()
+
+    prob.run_model()
 
     prob.model.list_inputs()
     prob.model.list_outputs()
diff --git a/tests/conftest.py b/tests/conftest.py
index 9c3948fc..a480e04e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,7 +8,7 @@
 
 from ORBIT.core import Vessel
 from tests.data import test_weather
-from ORBIT.library import initialize_library, extract_library_specs
+from ORBIT.core.library import initialize_library, extract_library_specs
 from ORBIT.phases.install.cable_install import SimpleCable
 
 
@@ -71,3 +71,10 @@ def spi_vessel():
 def simple_cable():
 
     return SimpleCable(linear_density=50.0)
+
+
+@pytest.fixture(scope="function")
+def tmp_yaml_del():
+
+    yield
+    os.remove("tmp.yaml")
diff --git a/tests/test_library.py b/tests/core/test_library.py
similarity index 97%
rename from tests/test_library.py
rename to tests/core/test_library.py
index 48ce6153..7320a9f6 100644
--- a/tests/test_library.py
+++ b/tests/core/test_library.py
@@ -10,7 +10,8 @@
 
 import pytest
 
-from ORBIT import ProjectManager, library
+from ORBIT import ProjectManager
+from ORBIT.core import library
 from ORBIT.core.exceptions import LibraryItemNotFoundError
 
 config = {
diff --git a/tests/data/library/defaults/project.yaml b/tests/data/library/defaults/project.yaml
deleted file mode 100644
index 18d382ff..00000000
--- a/tests/data/library/defaults/project.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-# Development costs
-site_auction_cost: 100e6                      # USD
-site_auction_duration: 0                      # hrs
-site_assessment_plan_cost: 0.5e6              # USD
-site_assessment_plan_duration: 8760           # hrs
-site_assessment_cost: 50e6                    # USD
-site_assessment_duration: 43800               # hrs
-construction_operations_plan_cost: 1e6        # USD
-construction_operations_plan_duration: 43800  # hrs
-boem_review_cost: 0                           # No cost to developer
-boem_review_duration: 8760                    # hrs
-design_install_plan_cost: 0.25e6              # USD
-design_install_plan_duration: 8760            # hrs
-
-# Phase designs
-substructure_design_time: 1000                # hrs
-array_system_design_time: 1000                # hrs
-export_system_design_time: 1000               # hrs
-oss_design_time: 1000                         # hrs
-onshore_trans_design_time: 1000               # hrs
-
-# Substructure costs
-monopile_steel_cost: 3000                     # USD/t
-tp_steel_cost: 3000                           # USD/t
-
-# Port properties
-port_cost_per_month: 2e6                      # USD/month
-
-# Export System Installation
-onshore_construction_rate: 50000              # USD/day
-
-# Vessel parameters
-wtiv_day_rate: 180000                         # USD/day
-feeder_day_rate: 75000                        # USD/day
-scour_day_rate: 120000                        # USD/day
-trench_day_rate: 50000                        # USD/day
-heavy_lift_vessel_day_rate: 500000            # USD/day
-array_cable_install_day_rate: 125000          # USD/day
-export_cable_install_day_rate: 165000         # USD/day
diff --git a/tests/data/library/project/config/array_cable_install.yaml b/tests/data/library/project/config/array_cable_install.yaml
index 3e61d45d..c5c8282e 100644
--- a/tests/data/library/project/config/array_cable_install.yaml
+++ b/tests/data/library/project/config/array_cable_install.yaml
@@ -18,6 +18,7 @@ array_system:
       - - 1.4
         - 2
       linear_density: 42.5
+  system_cost: 100e6
 plant:
   layout: grid
   num_turbines: 40
diff --git a/tests/data/library/project/config/complete_floating_project.yaml b/tests/data/library/project/config/complete_floating_project.yaml
new file mode 100644
index 00000000..45fbc3ec
--- /dev/null
+++ b/tests/data/library/project/config/complete_floating_project.yaml
@@ -0,0 +1,50 @@
+OffshoreSubstationInstallation:
+  feeder: test_floating_barge
+array_cable_install_vessel: test_cable_lay_vessel
+array_system:
+  free_cable_length: 0.5
+array_system_design:
+  cables:
+  - XLPE_300mm_33kV
+design_phases:
+- ArraySystemDesign
+- ExportSystemDesign
+- MooringSystemDesign
+- OffshoreSubstationDesign
+- SemiSubmersibleDesign
+export_cable_install_vessel: test_cable_lay_vessel
+export_system_design:
+  cables:
+  - XLPE_300mm_33kV
+install_phases:
+  ArrayCableInstallation: 0
+  ExportCableInstallation: 0
+  MooredSubInstallation: 0
+  MooringSystemInstallation: 0
+  OffshoreSubstationInstallation: 0
+  TurbineInstallation: 0
+mooring_install_vessel: test_support_vessel
+oss_install_vessel: test_floating_heavy_lift_vessel
+plant:
+  layout: ring
+  num_turbines: 50
+  row_spacing: 7
+  substation_distance: 1
+  turbine_spacing: 7
+port:
+  monthly_rate: 2000000.0
+  sub_assembly_lines: 1
+  turbine_assembly_cranes: 1
+site:
+  depth: 900
+  distance: 100
+  distance_to_landfall: 100
+substructure:
+  takt_time: 168
+support_vessel: test_support_vessel
+towing_vessel: test_towing_vessel
+towing_vessel_groups:
+  station_keeping_vessels: 2
+  towing_vessels: 3
+turbine: 12MW_generic
+wtiv: test_floating_heavy_lift_vessel
diff --git a/tests/data/library/project/config/export_cable_install.yaml b/tests/data/library/project/config/export_cable_install.yaml
index 4fe13a01..beccf930 100644
--- a/tests/data/library/project/config/export_cable_install.yaml
+++ b/tests/data/library/project/config/export_cable_install.yaml
@@ -7,6 +7,7 @@ export_system:
       - - 20
         - 0.5
     linear_density: 50.0
+  system_cost: 200e6
 plant:
   layout: grid
   num_turbines: 40
diff --git a/tests/data/library/project/config/floating_oss_install.yaml b/tests/data/library/project/config/floating_oss_install.yaml
new file mode 100644
index 00000000..d9dab2b1
--- /dev/null
+++ b/tests/data/library/project/config/floating_oss_install.yaml
@@ -0,0 +1,20 @@
+feeder: test_floating_barge
+num_feeders: 1
+num_substations: 1
+offshore_substation_substructure:
+  deck_space: 200
+  length: 50
+  type: Monopile
+  mass: 400
+  unit_cost: 5e6
+offshore_substation_topside:
+  deck_space: 200
+  mass: 400
+  unit_cost: 100e6
+oss_install_vessel: test_floating_heavy_lift_vessel
+port:
+  monthly_rate: 100000
+  num_cranes: 1
+site:
+  depth: 500
+  distance: 40
diff --git a/tests/data/library/project/config/floating_turbine_install_feeder.yaml b/tests/data/library/project/config/floating_turbine_install_feeder.yaml
new file mode 100644
index 00000000..5780036a
--- /dev/null
+++ b/tests/data/library/project/config/floating_turbine_install_feeder.yaml
@@ -0,0 +1,24 @@
+feeder: test_floating_barge
+num_feeders: 1
+plant:
+  num_turbines: 20
+port:
+  monthly_rate: 100000
+  name: Test Port
+  num_cranes: 1
+site:
+  depth: 500
+  distance: 50
+turbine:
+  blade:
+    deck_space: 100
+    mass: 100
+  hub_height: 100
+  nacelle:
+    deck_space: 200
+    mass: 400
+  tower:
+    deck_space: 100
+    mass: 400
+    length: 100
+wtiv: test_floating_heavy_lift_vessel
diff --git a/tests/data/library/project/config/moored_install.yaml b/tests/data/library/project/config/moored_install.yaml
index 90b0b5ff..47cd8bb0 100644
--- a/tests/data/library/project/config/moored_install.yaml
+++ b/tests/data/library/project/config/moored_install.yaml
@@ -11,6 +11,7 @@ site:
 substructure:
   takt_time: 168
   towing_speed: 6
+  unit_cost: 12e6
 support_vessel: test_support_vessel
 towing_vessel: test_towing_vessel
 towing_vessel_groups:
diff --git a/tests/data/library/project/config/moored_install_no_supply.yaml b/tests/data/library/project/config/moored_install_no_supply.yaml
index 7a0782a2..249abd85 100644
--- a/tests/data/library/project/config/moored_install_no_supply.yaml
+++ b/tests/data/library/project/config/moored_install_no_supply.yaml
@@ -9,6 +9,7 @@ site:
 substructure:
   takt_time: 0
   towing_speed: 6
+  unit_cost: 12e6
 support_vessel: test_support_vessel
 towing_vessel: test_towing_vessel
 towing_vessel_groups:
diff --git a/tests/data/library/project/config/mooring_system_install.yaml b/tests/data/library/project/config/mooring_system_install.yaml
index a9e34a60..7d32e74b 100644
--- a/tests/data/library/project/config/mooring_system_install.yaml
+++ b/tests/data/library/project/config/mooring_system_install.yaml
@@ -8,3 +8,5 @@ mooring_system:
   num_lines: 3
   line_mass: 500
   anchor_mass: 100
+  anchor_cost: 5e5
+  line_cost: 1.5e6
diff --git a/tests/data/library/project/config/multi_wtiv_mono_install.yaml b/tests/data/library/project/config/multi_wtiv_mono_install.yaml
index 7812e2e0..74f22bfd 100644
--- a/tests/data/library/project/config/multi_wtiv_mono_install.yaml
+++ b/tests/data/library/project/config/multi_wtiv_mono_install.yaml
@@ -4,6 +4,7 @@ monopile:
   diameter: 10
   length: 50
   mass: 350
+  unit_cost: 5e6
 num_feeders: 1
 plant:
   num_turbines: 20
@@ -17,6 +18,7 @@ site:
 transition_piece:
   deck_space: 250
   mass: 350
+  unit_cost: 3e6
 turbine:
   hub_height: 100
 wtiv: test_wtiv
diff --git a/tests/data/library/project/config/oss_install.yaml b/tests/data/library/project/config/oss_install.yaml
index 7c6f1945..a7b00dd0 100644
--- a/tests/data/library/project/config/oss_install.yaml
+++ b/tests/data/library/project/config/oss_install.yaml
@@ -6,9 +6,11 @@ offshore_substation_substructure:
   length: 50
   type: Monopile
   mass: 400
+  unit_cost: 5e6
 offshore_substation_topside:
   deck_space: 200
   mass: 400
+  unit_cost: 100e6
 oss_install_vessel: test_heavy_lift_vessel
 port:
   monthly_rate: 100000
diff --git a/tests/data/library/project/config/oss_multi_feeder_substation_install.yaml b/tests/data/library/project/config/oss_multi_feeder_substation_install.yaml
index 6f325b41..6167b0b9 100644
--- a/tests/data/library/project/config/oss_multi_feeder_substation_install.yaml
+++ b/tests/data/library/project/config/oss_multi_feeder_substation_install.yaml
@@ -6,9 +6,11 @@ offshore_substation_substructure:
   length: 50
   type: Monopile
   mass: 400
+  unit_cost: 5e6
 offshore_substation_topside:
   deck_space: 200
   mass: 400
+  unit_cost: 100e6
 oss_install_vessel: test_heavy_lift_vessel
 port:
   monthly_rate: 100000
diff --git a/tests/data/library/project/config/project_manager.yaml b/tests/data/library/project/config/project_manager.yaml
index 47187b96..46ce05e3 100644
--- a/tests/data/library/project/config/project_manager.yaml
+++ b/tests/data/library/project/config/project_manager.yaml
@@ -10,6 +10,7 @@ monopile:
   diameter: 10
   length: 50
   mass: 350
+  unit_cost: 5e6
 plant:
   num_turbines: 10
   turbine_spacing: 7
@@ -21,6 +22,7 @@ site:
 transition_piece:
   deck_space: 250
   mass: 350
+  unit_cost: 2e6
 turbine:
   blade:
     deck_space: 100
diff --git a/tests/data/library/project/config/scour_protection_install.yaml b/tests/data/library/project/config/scour_protection_install.yaml
index fd23223c..f4600895 100644
--- a/tests/data/library/project/config/scour_protection_install.yaml
+++ b/tests/data/library/project/config/scour_protection_install.yaml
@@ -4,7 +4,8 @@ plant:
 port:
   monthly_rate: 100000
 scour_protection:
-  tons_per_substructure: 2000
+  tonnes_per_substructure: 2000
+  cost_per_tonne: 45
 spi_vessel: test_scour_protection_vessel
 site:
   depth: 40
diff --git a/tests/data/library/project/config/single_wtiv_mono_install.yaml b/tests/data/library/project/config/single_wtiv_mono_install.yaml
index 68adaccf..9e4c65ff 100644
--- a/tests/data/library/project/config/single_wtiv_mono_install.yaml
+++ b/tests/data/library/project/config/single_wtiv_mono_install.yaml
@@ -3,6 +3,7 @@ monopile:
   diameter: 10
   length: 50
   mass: 350
+  unit_cost: 5e6
 plant:
   num_turbines: 20
 port:
@@ -14,6 +15,7 @@ site:
 transition_piece:
   deck_space: 250
   mass: 350
+  unit_cost: 3e6
 turbine:
   hub_height: 100
 wtiv: test_wtiv
diff --git a/tests/data/library/turbines/12MW_generic.yaml b/tests/data/library/turbines/12MW_generic.yaml
old mode 100755
new mode 100644
diff --git a/tests/data/library/vessels/test_cable_lay_vessel.yaml b/tests/data/library/vessels/test_cable_lay_vessel.yaml
index c9df9a72..73bfb1a8 100644
--- a/tests/data/library/vessels/test_cable_lay_vessel.yaml
+++ b/tests/data/library/vessels/test_cable_lay_vessel.yaml
@@ -3,7 +3,6 @@ transport_specs:
   max_windspeed: 25     # m/s
   transit_speed: 11.5   # km/hr
 vessel_specs:
-  beam_length: 30.0     # m
   day_rate: 50000       # USD/day, cost of operating vessel with crew
   min_draft: 4.8        # m
   overall_length: 99.0  # m
diff --git a/tests/data/library/vessels/test_feeder.yaml b/tests/data/library/vessels/test_feeder.yaml
index 807db042..c2785b02 100644
--- a/tests/data/library/vessels/test_feeder.yaml
+++ b/tests/data/library/vessels/test_feeder.yaml
@@ -5,7 +5,6 @@ jacksys_specs:
   leg_pen: 5                # m
   max_depth: 40             # m
   max_extension: 60         # m
-  num_legs: 4
   speed_above_depth: 0.5    # m/min
   speed_below_depth: 0.5    # m/min
 storage_specs:
@@ -17,8 +16,4 @@ transport_specs:
   max_windspeed: 20         # m/s
   transit_speed: 6          # km/h
 vessel_specs:
-  beam_length: 35           # m
   day_rate: 50000           # USD/day
-  max_draft: 5              # m
-  min_draft: 4              # m
-  overall_length: 60        # m
diff --git a/tests/data/library/vessels/test_floating_barge.yaml b/tests/data/library/vessels/test_floating_barge.yaml
new file mode 100644
index 00000000..1fcd8ef8
--- /dev/null
+++ b/tests/data/library/vessels/test_floating_barge.yaml
@@ -0,0 +1,14 @@
+crane_specs:
+  max_lift: 500             # t
+dynamic_positioning_specs:
+  class: 2                  # 1, 2 or 3
+storage_specs:
+  max_cargo: 8000           # t
+  max_deck_load: 8          # t/m^2
+  max_deck_space: 1000      # m^2
+transport_specs:
+  max_waveheight: 2.5       # m
+  max_windspeed: 20         # m/s
+  transit_speed: 6          # km/h
+vessel_specs:
+  day_rate: 120000          # USD/day
diff --git a/tests/data/library/vessels/test_floating_heavy_lift_vessel.yaml b/tests/data/library/vessels/test_floating_heavy_lift_vessel.yaml
new file mode 100644
index 00000000..44b9f7e9
--- /dev/null
+++ b/tests/data/library/vessels/test_floating_heavy_lift_vessel.yaml
@@ -0,0 +1,16 @@
+crane_specs:
+  max_hook_height: 72       # m
+  max_lift: 5500            # t
+  max_windspeed: 15         # m/s
+dynamic_positioning_specs:
+  class: 2                  # 1, 2 or 3
+storage_specs:
+  max_cargo: 8000           # t
+  max_deck_load: 15         # t/m^2
+  max_deck_space: 4000      # m^2
+transport_specs:
+  max_waveheight: 2.5       # m
+  max_windspeed: 20         # m/s
+  transit_speed: 7          # km/h
+vessel_specs:
+  day_rate: 500000          # USD/day
diff --git a/tests/data/library/vessels/test_heavy_lift_vessel.yaml b/tests/data/library/vessels/test_heavy_lift_vessel.yaml
index a63057d9..9eddae7c 100644
--- a/tests/data/library/vessels/test_heavy_lift_vessel.yaml
+++ b/tests/data/library/vessels/test_heavy_lift_vessel.yaml
@@ -1,16 +1,13 @@
 crane_specs:
-  boom_length: 100          # m
   max_hook_height: 72       # m
   max_lift: 5500            # t
   max_windspeed: 15         # m/s
-  radius: 30                # m
 jacksys_specs:
   air_gap: 10               # m, distance from MSL to jacked up height
   leg_length: 110           # m
   leg_pen: 5                # m
   max_depth: 75             # m
   max_extension: 85         # m
-  num_legs: 6
   speed_above_depth: 0.5    # m/min
   speed_below_depth: 0.5    # m/min
 storage_specs:
@@ -23,5 +20,3 @@ transport_specs:
   transit_speed: 7          # km/h
 vessel_specs:
   day_rate: 500000          # USD/day
-  max_draft: 4.5            # m
-  overall_length: 102.75    # m
diff --git a/tests/data/library/vessels/test_phase_specific_wtiv.yaml b/tests/data/library/vessels/test_phase_specific_wtiv.yaml
index 8ae38312..b382722c 100644
--- a/tests/data/library/vessels/test_phase_specific_wtiv.yaml
+++ b/tests/data/library/vessels/test_phase_specific_wtiv.yaml
@@ -1,17 +1,14 @@
 name: Phase Specific WTIV
 crane_specs:
-  boom_length: 100        # m
   max_hook_height: 100    # m
   max_lift: 1200          # t
   max_windspeed: 15       # m/s
-  radius: 30              # m
 jacksys_specs:
   air_gap: 10             # m, distance from MSL to jacked up height
   leg_length: 110         # m
   leg_pen: 5              # m
   max_depth: 75           # m
   max_extension: 85       # m
-  num_legs: 6
   speed_above_depth: 1    # m/min
   speed_below_depth: 2.5  # m/min
 storage_specs:
@@ -23,8 +20,4 @@ transport_specs:
   max_windspeed: 20       # m/s
   transit_speed: 10       # km/h
 vessel_specs:
-  beam_length: 50         # m
   day_rate: 250000        # USD/day
-  max_draft: 6            # m
-  min_draft: 5            # m
-  overall_length: 150     # m
diff --git a/tests/data/library/vessels/test_scour_protection_vessel.yaml b/tests/data/library/vessels/test_scour_protection_vessel.yaml
index 83d0b794..bb2fe5cf 100644
--- a/tests/data/library/vessels/test_scour_protection_vessel.yaml
+++ b/tests/data/library/vessels/test_scour_protection_vessel.yaml
@@ -8,8 +8,4 @@ transport_specs:
   max_windspeed: 25     # m/s
   transit_speed: 11.5   # km/hr
 vessel_specs:
-  beam_length: 35       # m
   day_rate: 50000       # USD/day
-  max_draft: 5          # m
-  min_draft: 4          # m
-  overall_length: 60    # m
diff --git a/tests/data/library/vessels/test_towing_vessel.yaml b/tests/data/library/vessels/test_towing_vessel.yaml
index b1de857d..5aa4bf58 100644
--- a/tests/data/library/vessels/test_towing_vessel.yaml
+++ b/tests/data/library/vessels/test_towing_vessel.yaml
@@ -3,8 +3,4 @@ transport_specs:
   max_windspeed: 20         # m/s
   transit_speed: 6          # km/h
 vessel_specs:
-  beam_length: 35           # m
   day_rate: 30000           # USD/day
-  max_draft: 5              # m
-  min_draft: 4              # m
-  overall_length: 60        # m
diff --git a/tests/data/library/vessels/test_wtiv.yaml b/tests/data/library/vessels/test_wtiv.yaml
index 899be7a7..41b70505 100644
--- a/tests/data/library/vessels/test_wtiv.yaml
+++ b/tests/data/library/vessels/test_wtiv.yaml
@@ -1,17 +1,14 @@
 name: Example WTIV
 crane_specs:
-  boom_length: 100        # m
   max_hook_height: 100    # m
   max_lift: 1200          # t
   max_windspeed: 15       # m/s
-  radius: 30              # m
 jacksys_specs:
   air_gap: 10             # m, distance from MSL to jacked up height
   leg_length: 110         # m
   leg_pen: 5              # m
   max_depth: 75           # m
   max_extension: 85       # m
-  num_legs: 6
   speed_above_depth: 1    # m/min
   speed_below_depth: 2.5  # m/min
 storage_specs:
@@ -23,8 +20,4 @@ transport_specs:
   max_windspeed: 20       # m/s
   transit_speed: 10       # km/h
 vessel_specs:
-  beam_length: 50         # m
   day_rate: 250000        # USD/day
-  max_draft: 6            # m
-  min_draft: 5            # m
-  overall_length: 150     # m
diff --git a/tests/data/library/vessels/test_wtiv_mobilize.yaml b/tests/data/library/vessels/test_wtiv_mobilize.yaml
index 58034bb9..bc6b8bff 100644
--- a/tests/data/library/vessels/test_wtiv_mobilize.yaml
+++ b/tests/data/library/vessels/test_wtiv_mobilize.yaml
@@ -1,17 +1,14 @@
 name: Example WTIV
 crane_specs:
-  boom_length: 100        # m
   max_hook_height: 100    # m
   max_lift: 1200          # t
   max_windspeed: 15       # m/s
-  radius: 30              # m
 jacksys_specs:
   air_gap: 10             # m, distance from MSL to jacked up height
   leg_length: 110         # m
   leg_pen: 5              # m
   max_depth: 75           # m
   max_extension: 85       # m
-  num_legs: 6
   speed_above_depth: 1    # m/min
   speed_below_depth: 2.5  # m/min
 storage_specs:
@@ -23,10 +20,6 @@ transport_specs:
   max_windspeed: 20       # m/s
   transit_speed: 10       # km/h
 vessel_specs:
-  beam_length: 50         # m
   day_rate: 250000        # USD/day
-  max_draft: 6            # m
-  min_draft: 5            # m
-  overall_length: 150     # m
   mobilization_days: 14   # days
   mobilization_mult: 1    #
diff --git a/tests/phases/design/test_array_system_design.py b/tests/phases/design/test_array_system_design.py
index e464c5e6..cd1ad5cd 100644
--- a/tests/phases/design/test_array_system_design.py
+++ b/tests/phases/design/test_array_system_design.py
@@ -11,7 +11,7 @@
 import numpy as np
 import pytest
 
-from ORBIT.library import extract_library_specs
+from ORBIT.core.library import extract_library_specs
 from ORBIT.phases.design import ArraySystemDesign, CustomArraySystemDesign
 from ORBIT.core.exceptions import LibraryItemNotFoundError
 
diff --git a/tests/phases/design/test_export_system_design.py b/tests/phases/design/test_export_system_design.py
index 76ffcae0..9db78fed 100644
--- a/tests/phases/design/test_export_system_design.py
+++ b/tests/phases/design/test_export_system_design.py
@@ -9,7 +9,7 @@
 
 import pytest
 
-from ORBIT.library import extract_library_specs
+from ORBIT.core.library import extract_library_specs
 from ORBIT.phases.design import ExportSystemDesign
 
 config = extract_library_specs("config", "export_design")
diff --git a/tests/phases/design/test_mooring_system_design.py b/tests/phases/design/test_mooring_system_design.py
index 39e2cb9c..d6007018 100644
--- a/tests/phases/design/test_mooring_system_design.py
+++ b/tests/phases/design/test_mooring_system_design.py
@@ -29,7 +29,7 @@ def test_depth_sweep(depth):
     m.run()
 
     assert m.design_result
-    assert m.total_phase_cost
+    assert m.total_cost
 
 
 @pytest.mark.parametrize("rating", range(3, 15, 1))
@@ -42,7 +42,7 @@ def test_rating_sweeip(rating):
     m.run()
 
     assert m.design_result
-    assert m.total_phase_cost
+    assert m.total_cost
 
 
 def test_drag_embedment_fixed_length():
diff --git a/tests/phases/design/test_oss_design.py b/tests/phases/design/test_oss_design.py
index 33da8cc7..b2dd6316 100644
--- a/tests/phases/design/test_oss_design.py
+++ b/tests/phases/design/test_oss_design.py
@@ -47,7 +47,7 @@ def test_parameter_sweep(depth, num_turbines, turbine_rating):
     assert 200 <= o._outputs["offshore_substation_topside"]["mass"] <= 5000
 
     # Check valid substation cost
-    assert 1e6 <= o.total_phase_cost <= 300e6
+    assert 1e6 <= o.total_cost <= 300e6
 
 
 def test_oss_kwargs():
@@ -69,7 +69,7 @@ def test_oss_kwargs():
 
     o = OffshoreSubstationDesign(base)
     o.run()
-    base_cost = o.total_phase_cost
+    base_cost = o.total_cost
 
     for k, v in test_kwargs.items():
 
@@ -79,6 +79,6 @@ def test_oss_kwargs():
 
         o = OffshoreSubstationDesign(config)
         o.run()
-        cost = o.total_phase_cost
+        cost = o.total_cost
 
         assert cost != base_cost
diff --git a/tests/phases/design/test_project_development.py b/tests/phases/design/test_project_development.py
deleted file mode 100644
index ab21d96d..00000000
--- a/tests/phases/design/test_project_development.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""Tests for the `ProjectDevelopment` class."""
-
-__author__ = "Jake Nunemaker"
-__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
-__maintainer__ = "Jake Nunemaker"
-__email__ = "jake.nunemaker@nrel.gov"
-
-import os
-from copy import deepcopy
-
-import pytest
-
-from ORBIT.library import extract_library_specs
-from ORBIT.phases.design import ProjectDevelopment
-
-base = {
-    "project_development": {
-        "site_auction_cost": 100e6,  # USD
-        "site_auction_duration": 0,  # hrs
-        "site_assessment_plan_cost": 0.5e6,  # USD
-        "site_assessment_plan_duration": 8760,  # hrs
-        "site_assessment_cost": 50e6,  # USD
-        "site_assessment_duration": 43800,  # hrs
-        "construction_operations_plan_cost": 1e6,  # USD
-        "construction_operations_plan_duration": 43800,  # hrs
-        "boem_review_cost": 0,  # No cost to developer
-        "boem_review_duration": 8760,  # hrs
-        "design_install_plan_cost": 0.25e6,  # USD
-        "design_install_plan_duration": 8760,  # hrs
-    }
-}
-
-
-def test_run():
-
-    dev = ProjectDevelopment(base)
-    dev.run()
-
-
-def test_defaults():
-
-    for k, _ in base["project_development"].items():
-
-        _config = deepcopy(base)
-        _config["project_development"].pop(k)
-
-        dev = ProjectDevelopment(_config, library_path=pytest.library)
-        dev.run()
-
-        defaults = extract_library_specs("defaults", "project")
-
-        _split = k.split("_")
-        n = "_".join(_split[:-1])
-        t = _split[-1]
-        val = dev._outputs[n][t]
-        assert val == defaults[k]
diff --git a/tests/phases/design/test_scour_protection_design.py b/tests/phases/design/test_scour_protection_design.py
index 52e3df88..301e5894 100644
--- a/tests/phases/design/test_scour_protection_design.py
+++ b/tests/phases/design/test_scour_protection_design.py
@@ -40,7 +40,6 @@ def test_default_setup():
     assert scour.phi == 33.5
     assert scour.equilibrium == 1.3
     assert scour.rock_density == 2600
-    assert scour.total_phase_time == 0.0
 
 
 def test_fully_defined_setup():
@@ -52,7 +51,6 @@ def test_fully_defined_setup():
     assert scour.phi == design["soil_friction_angle"]
     assert scour.equilibrium == design["scour_depth_equilibrium"]
     assert scour.rock_density == design["rock_density"]
-    assert scour.total_phase_time == design["design_time"]
 
 
 @pytest.mark.parametrize(
@@ -81,15 +79,4 @@ def test_total_cost(config):
         * config["scour_protection_design"]["cost_per_tonne"]
         * scour.scour_protection_tonnes
     )
-    assert scour.total_phase_cost == pytest.approx(cost, rel=1e-8)
-
-
-def test_design_result():
-    scour = ScourProtectionDesign(config_min_defined)
-    scour.run()
-
-    assert scour.design_result == {
-        "scour_protection": {
-            "tons_per_substructure": scour.scour_protection_tonnes
-        }
-    }
+    assert scour.total_cost == pytest.approx(cost, rel=1e-8)
diff --git a/tests/phases/design/test_semisubmersible_design.py b/tests/phases/design/test_semisubmersible_design.py
index de4315e2..7c710fb9 100644
--- a/tests/phases/design/test_semisubmersible_design.py
+++ b/tests/phases/design/test_semisubmersible_design.py
@@ -51,7 +51,7 @@ def test_design_kwargs():
 
     s = SemiSubmersibleDesign(base)
     s.run()
-    base_cost = s.total_phase_cost
+    base_cost = s.total_cost
 
     for k, v in test_kwargs.items():
 
@@ -61,6 +61,6 @@ def test_design_kwargs():
 
         s = SemiSubmersibleDesign(config)
         s.run()
-        cost = s.total_phase_cost
+        cost = s.total_cost
 
         assert cost != base_cost
diff --git a/tests/phases/design/test_spar_design.py b/tests/phases/design/test_spar_design.py
index f21bd2a2..393cf7c1 100644
--- a/tests/phases/design/test_spar_design.py
+++ b/tests/phases/design/test_spar_design.py
@@ -51,7 +51,7 @@ def test_design_kwargs():
 
     s = SparDesign(base)
     s.run()
-    base_cost = s.total_phase_cost
+    base_cost = s.total_cost
 
     for k, v in test_kwargs.items():
 
@@ -61,6 +61,6 @@ def test_design_kwargs():
 
         s = SparDesign(config)
         s.run()
-        cost = s.total_phase_cost
+        cost = s.total_cost
 
         assert cost != base_cost
diff --git a/tests/phases/install/cable_install/test_array_install.py b/tests/phases/install/cable_install/test_array_install.py
index b52be728..3641a07b 100644
--- a/tests/phases/install/cable_install/test_array_install.py
+++ b/tests/phases/install/cable_install/test_array_install.py
@@ -13,9 +13,10 @@
 import pandas as pd
 import pytest
 
+from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.library import extract_library_specs
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import ArrayCableInstallation
 
 base_config = extract_library_specs("config", "array_cable_install")
@@ -112,7 +113,7 @@ def test_separate_speed_kwargs():
     assert new_bury > base_bury
 
 
-def test_kwargs_for_export_install():
+def test_kwargs_for_array_install():
 
     sim = ArrayCableInstallation(base_config)
     sim.run()
@@ -159,3 +160,58 @@ def test_kwargs_for_export_install():
 
     else:
         assert True
+
+
+def test_kwargs_for_array_install_in_ProjectManager():
+
+    base = deepcopy(base_config)
+    base["install_phases"] = ["ArrayCableInstallation"]
+
+    project = ProjectManager(base)
+    project.run_project()
+    baseline = project.phase_times["ArrayCableInstallation"]
+
+    keywords = [
+        "cable_load_time",
+        "site_position_time",
+        "cable_prep_time",
+        "cable_lower_time",
+        "cable_pull_in_time",
+        "cable_termination_time",
+    ]
+
+    failed = []
+
+    for kw in keywords:
+
+        default = pt[kw]
+
+        if "speed" in kw:
+            _new = default - 0.05
+
+            if _new <= 0:
+                raise Exception(f"'{kw}' is less than 0.")
+
+            processes = {kw: _new}
+
+        else:
+            processes = {kw: default + 2}
+
+        new_config = deepcopy(base)
+        new_config["processes"] = processes
+
+        new_project = ProjectManager(new_config)
+        new_project.run_project()
+        new_time = new_project.phase_times["ArrayCableInstallation"]
+
+        if new_time > baseline:
+            pass
+
+        else:
+            failed.append(kw)
+
+    if failed:
+        raise Exception(f"ExpInstall: '{failed}' not affecting results.")
+
+    else:
+        assert True
diff --git a/tests/phases/install/cable_install/test_export_install.py b/tests/phases/install/cable_install/test_export_install.py
index 6da9258f..7d8d55e4 100644
--- a/tests/phases/install/cable_install/test_export_install.py
+++ b/tests/phases/install/cable_install/test_export_install.py
@@ -13,9 +13,10 @@
 import pandas as pd
 import pytest
 
+from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.library import extract_library_specs
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import ExportCableInstallation
 
 base_config = extract_library_specs("config", "export_cable_install")
@@ -178,3 +179,70 @@ def test_kwargs_for_export_install():
 
     else:
         assert True
+
+
+def test_kwargs_for_export_install_in_ProjectManager():
+
+    new_export_system = {
+        "cable": {"linear_density": 50.0, "sections": [1000], "number": 1},
+        "system_cost": 200e6,
+    }
+    new_site = {"distance": 50, "depth": 20}
+    base = deepcopy(base_config)
+    base["export_system"] = new_export_system
+    base["site"] = new_site
+    base["install_phases"] = ["ExportCableInstallation"]
+
+    project = ProjectManager(base)
+    project.run_project()
+    baseline = project.phase_times["ExportCableInstallation"]
+
+    keywords = [
+        "onshore_construction_time",
+        "cable_load_time",
+        "site_position_time",
+        "cable_prep_time",
+        "cable_lower_time",
+        "cable_pull_in_time",
+        "cable_termination_time",
+        "cable_splice_time",
+        "tow_plow_speed",
+        "pull_winch_speed",
+        "cable_raise_time",
+    ]
+
+    failed = []
+
+    for kw in keywords:
+
+        default = pt[kw]
+
+        if "speed" in kw:
+            _new = default - 0.05
+
+            if _new <= 0:
+                raise Exception(f"'{kw}' is less than 0.")
+
+            processes = {kw: _new}
+
+        else:
+            processes = {kw: default + 2}
+
+        new_config = deepcopy(base)
+        new_config["processes"] = processes
+
+        new_project = ProjectManager(new_config)
+        new_project.run_project()
+        new_time = new_project.phase_times["ExportCableInstallation"]
+
+        if new_time > baseline:
+            pass
+
+        else:
+            failed.append(kw)
+
+    if failed:
+        raise Exception(f"ExpInstall: '{failed}' not affecting results.")
+
+    else:
+        assert True
diff --git a/tests/phases/install/monopile_install/test_monopile_install.py b/tests/phases/install/monopile_install/test_monopile_install.py
index 9e184c66..4e52ea9c 100644
--- a/tests/phases/install/monopile_install/test_monopile_install.py
+++ b/tests/phases/install/monopile_install/test_monopile_install.py
@@ -11,9 +11,10 @@
 import pandas as pd
 import pytest
 
+from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.library import extract_library_specs
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import MonopileInstallation
 
 config_wtiv = extract_library_specs("config", "single_wtiv_mono_install")
@@ -143,6 +144,64 @@ def test_kwargs():
         assert True
 
 
+def test_kwargs_in_ProjectManager():
+
+    base = deepcopy(config_wtiv)
+    base["install_phases"] = ["MonopileInstallation"]
+    project = ProjectManager(base)
+    project.run_project()
+    baseline = project.phase_times["MonopileInstallation"]
+
+    keywords = [
+        "mono_embed_len",
+        "mono_drive_rate",
+        "mono_fasten_time",
+        "mono_release_time",
+        "tp_fasten_time",
+        "tp_release_time",
+        "tp_bolt_time",
+        "site_position_time",
+        "crane_reequip_time",
+        "rov_survey_time",
+    ]
+
+    failed = []
+
+    for kw in keywords:
+
+        default = pt[kw]
+
+        if kw == "mono_drive_rate":
+            _new = default - 2
+
+            if _new <= 0:
+                raise Exception(f"'{kw}' is less than 0.")
+
+            processes = {kw: _new}
+
+        else:
+            processes = {kw: default + 2}
+
+        new_config = deepcopy(base)
+        new_config["processes"] = processes
+
+        new_project = ProjectManager(new_config)
+        new_project.run_project()
+        new_time = new_project.phase_times["MonopileInstallation"]
+
+        if new_time > baseline:
+            pass
+
+        else:
+            failed.append(kw)
+
+    if failed:
+        raise Exception(f"'{failed}' not affecting results.")
+
+    else:
+        assert True
+
+
 def test_grout_kwargs():
 
     sim = MonopileInstallation(config_wtiv)
diff --git a/tests/phases/install/monopile_install/test_monopile_tasks.py b/tests/phases/install/monopile_install/test_monopile_tasks.py
index 0e420e61..bff023f5 100644
--- a/tests/phases/install/monopile_install/test_monopile_tasks.py
+++ b/tests/phases/install/monopile_install/test_monopile_tasks.py
@@ -52,7 +52,6 @@ def test_task(env, wtiv, task, log, args):
         (upend_monopile, "Upend Monopile", [100]),
         (lower_monopile, "Lower Monopile", []),
         (drive_monopile, "Drive Monopile", []),
-        (lower_transition_piece, "Lower TP", []),
     ],
 )
 def test_task_fails(env, feeder, task, log, args):
diff --git a/tests/phases/install/mooring_install/test_mooring_install.py b/tests/phases/install/mooring_install/test_mooring_install.py
index 48570cfc..0d885954 100644
--- a/tests/phases/install/mooring_install/test_mooring_install.py
+++ b/tests/phases/install/mooring_install/test_mooring_install.py
@@ -13,9 +13,10 @@
 import pandas as pd
 import pytest
 
+from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.library import extract_library_specs
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import MooringSystemInstallation
 
 config = extract_library_specs("config", "mooring_system_install")
@@ -28,7 +29,7 @@ def test_simulation_creation():
     assert sim.env
     assert sim.port
     assert sim.vessel
-    assert sim.number_systems
+    assert sim.num_systems
 
 
 @pytest.mark.parametrize(
@@ -92,3 +93,48 @@ def test_kwargs(anchor, key):
 
     else:
         assert True
+
+
+@pytest.mark.parametrize(
+    "anchor, key",
+    [
+        ("Suction Pile", "suction_pile_install_time"),
+        ("Drag Embedment", "drag_embed_install_time"),
+    ],
+)
+def test_kwargs_in_ProjectManager(anchor, key):
+
+    base = deepcopy(config)
+    base["mooring_system"]["anchor_type"] = anchor
+    base["install_phases"] = ["MooringSystemInstallation"]
+
+    project = ProjectManager(base)
+    project.run_project()
+    baseline = project.phase_times["MooringSystemInstallation"]
+
+    keywords = ["mooring_system_load_time", "mooring_site_survey_time", key]
+
+    failed = []
+
+    for kw in keywords:
+
+        default = pt[kw]
+        processes = {kw: default + 2}
+        new_config = deepcopy(base)
+        new_config["processes"] = processes
+
+        new_project = ProjectManager(new_config)
+        new_project.run_project()
+        new_time = new_project.phase_times["MooringSystemInstallation"]
+
+        if new_time > baseline:
+            pass
+
+        else:
+            failed.append(kw)
+
+    if failed:
+        raise Exception(f"'{failed}' not affecting results.")
+
+    else:
+        assert True
diff --git a/tests/phases/install/oss_install/test_oss_install.py b/tests/phases/install/oss_install/test_oss_install.py
index 5a540191..42550327 100644
--- a/tests/phases/install/oss_install/test_oss_install.py
+++ b/tests/phases/install/oss_install/test_oss_install.py
@@ -6,23 +6,28 @@
 __email__ = "jake.nunemaker@nrel.gov"
 
 
+from copy import deepcopy
+
 import pandas as pd
 import pytest
 
+from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.library import extract_library_specs
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import OffshoreSubstationInstallation
+from ORBIT.core.exceptions import MissingComponent
 
 config_single = extract_library_specs("config", "oss_install")
+config_floating = extract_library_specs("config", "floating_oss_install")
 config_multi = extract_library_specs("config", "oss_install")
 config_multi["num_feeders"] = 2
 
 
 @pytest.mark.parametrize(
     "config",
-    (config_single, config_multi),
-    ids=["single_feeder", "multi_feeder"],
+    (config_single, config_multi, config_floating),
+    ids=["single_feeder", "multi_feeder", "floating"],
 )
 def test_simulation_setup(config):
 
@@ -38,25 +43,29 @@ def test_simulation_setup(config):
 
 @pytest.mark.parametrize(
     "config",
-    (config_single, config_multi),
-    ids=["single_feeder", "multi_feeder"],
+    (config_single, config_multi, config_floating),
+    ids=["single_feeder", "multi_feeder", "floating"],
 )
 def test_vessel_initialization(config):
 
     sim = OffshoreSubstationInstallation(config)
     assert sim.oss_vessel
-    assert sim.oss_vessel.jacksys
     assert sim.oss_vessel.crane
 
+    js = sim.oss_vessel._jacksys_specs
+    dp = sim.oss_vessel._dp_specs
+
+    if not any([js, dp]):
+        assert False
+
     for feeder in sim.feeders:
-        assert feeder.jacksys
         assert feeder.storage
 
 
 @pytest.mark.parametrize(
     "config",
-    (config_single, config_multi),
-    ids=["single_feeder", "multi_feeder"],
+    (config_single, config_multi, config_floating),
+    ids=["single_feeder", "multi_feeder", "floating"],
 )
 @pytest.mark.parametrize(
     "weather", (None, test_weather), ids=["no_weather", "test_weather"]
@@ -131,3 +140,62 @@ def test_kwargs():
 
     else:
         assert True
+
+
+def test_kwargs_in_ProjectManager():
+
+    base = deepcopy(config_single)
+    base["install_phases"] = ["OffshoreSubstationInstallation"]
+
+    project = ProjectManager(base)
+    project.run_project()
+    baseline = project.phase_times["OffshoreSubstationInstallation"]
+
+    keywords = [
+        "mono_embed_len",
+        "mono_drive_rate",
+        "mono_fasten_time",
+        "mono_release_time",
+        "tp_bolt_time",
+        "site_position_time",
+        "crane_reequip_time",
+        "rov_survey_time",
+        "topside_fasten_time",
+        "topside_release_time",
+    ]
+
+    failed = []
+
+    for kw in keywords:
+
+        default = pt[kw]
+
+        if kw == "mono_drive_rate":
+            _new = default - 2
+
+            if _new <= 0:
+                raise Exception(f"'{kw}' is less than 0.")
+
+            processes = {kw: _new}
+
+        else:
+            processes = {kw: default + 2}
+
+        new_config = deepcopy(base)
+        new_config["processes"] = processes
+
+        new_project = ProjectManager(new_config)
+        new_project.run_project()
+        new_time = new_project.phase_times["OffshoreSubstationInstallation"]
+
+        if new_time > baseline:
+            pass
+
+        else:
+            failed.append(kw)
+
+    if failed:
+        raise Exception(f"'{failed}' not affecting results.")
+
+    else:
+        assert True
diff --git a/tests/phases/install/quayside_assembly_tow/test_gravity_based.py b/tests/phases/install/quayside_assembly_tow/test_gravity_based.py
index 0d0eb67c..dafad84b 100644
--- a/tests/phases/install/quayside_assembly_tow/test_gravity_based.py
+++ b/tests/phases/install/quayside_assembly_tow/test_gravity_based.py
@@ -10,7 +10,7 @@
 import pytest
 
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
+from ORBIT.core.library import extract_library_specs
 from ORBIT.phases.install import GravityBasedInstallation
 
 config = extract_library_specs("config", "moored_install")
diff --git a/tests/phases/install/quayside_assembly_tow/test_moored.py b/tests/phases/install/quayside_assembly_tow/test_moored.py
index e87ddb3b..e1fc0af7 100644
--- a/tests/phases/install/quayside_assembly_tow/test_moored.py
+++ b/tests/phases/install/quayside_assembly_tow/test_moored.py
@@ -10,7 +10,7 @@
 import pytest
 
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
+from ORBIT.core.library import extract_library_specs
 from ORBIT.phases.install import MooredSubInstallation
 
 config = extract_library_specs("config", "moored_install")
diff --git a/tests/phases/install/scour_protection_install/test_scour_protection.py b/tests/phases/install/scour_protection_install/test_scour_protection.py
index ebec8699..7061bc16 100644
--- a/tests/phases/install/scour_protection_install/test_scour_protection.py
+++ b/tests/phases/install/scour_protection_install/test_scour_protection.py
@@ -8,12 +8,15 @@
 __email__ = "Jake.Nunemaker@nrel.gov"
 
 
+from copy import deepcopy
+
 import pandas as pd
 import pytest
 
+from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.library import extract_library_specs
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import ScourProtectionInstallation
 
 config = extract_library_specs("config", "scour_protection_install")
@@ -27,7 +30,7 @@ def test_simulation_creation():
     assert sim.port
     assert sim.spi_vessel
     assert sim.num_turbines
-    assert sim.tons_per_substructure
+    assert sim.tonnes_per_substructure
 
 
 @pytest.mark.parametrize(
@@ -77,3 +80,41 @@ def test_kwargs():
 
     else:
         assert True
+
+
+def test_kwargs_in_ProjectManager():
+
+    base = deepcopy(config)
+    base["install_phases"] = ["ScourProtectionInstallation"]
+
+    project = ProjectManager(base)
+    project.run_project()
+    baseline = project.phase_times["ScourProtectionInstallation"]
+
+    keywords = ["drop_rocks_time"]
+
+    failed = []
+
+    for kw in keywords:
+
+        default = pt[kw]
+        processes = {kw: default + 2}
+
+        new_config = deepcopy(base)
+        new_config["processes"] = processes
+
+        new_project = ProjectManager(new_config)
+        new_project.run_project()
+        new_time = new_project.phase_times["ScourProtectionInstallation"]
+
+        if new_time > baseline:
+            pass
+
+        else:
+            failed.append(kw)
+
+    if failed:
+        raise Exception(f"'{failed}' not affecting results.")
+
+    else:
+        assert True
diff --git a/tests/phases/install/turbine_install/test_turbine_install.py b/tests/phases/install/turbine_install/test_turbine_install.py
index d9b82d42..d8225258 100644
--- a/tests/phases/install/turbine_install/test_turbine_install.py
+++ b/tests/phases/install/turbine_install/test_turbine_install.py
@@ -11,9 +11,10 @@
 import pandas as pd
 import pytest
 
+from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
-from ORBIT.core._defaults import process_times as pt
+from ORBIT.core.library import extract_library_specs
+from ORBIT.core.defaults import process_times as pt
 from ORBIT.phases.install import TurbineInstallation
 
 config_wtiv = extract_library_specs("config", "turbine_install_wtiv")
@@ -23,12 +24,13 @@
 config_wtiv_feeder = extract_library_specs("config", "turbine_install_feeder")
 config_wtiv_multi_feeder = deepcopy(config_wtiv_feeder)
 config_wtiv_multi_feeder["num_feeders"] = 2
+floating = extract_library_specs("config", "floating_turbine_install_feeder")
 
 
 @pytest.mark.parametrize(
     "config",
-    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder),
-    ids=["wtiv_only", "single_feeder", "multi_feeder"],
+    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating),
+    ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"],
 )
 def test_simulation_setup(config):
 
@@ -50,22 +52,27 @@ def test_simulation_setup(config):
 
 @pytest.mark.parametrize(
     "config",
-    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder),
-    ids=["wtiv_only", "single_feeder", "multi_feeder"],
+    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating),
+    ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"],
 )
 def test_vessel_creation(config):
 
     sim = TurbineInstallation(config)
     assert sim.wtiv
-    assert sim.wtiv.jacksys
     assert sim.wtiv.crane
     assert sim.wtiv.storage
 
+    js = sim.wtiv._jacksys_specs
+    dp = sim.wtiv._dp_specs
+
+    if not any([js, dp]):
+        assert False
+
     if config.get("feeder", None) is not None:
         assert len(sim.feeders) == config["num_feeders"]
 
         for feeder in sim.feeders:
-            assert feeder.jacksys
+            # assert feeder.jacksys
             assert feeder.storage
 
 
@@ -83,8 +90,8 @@ def test_vessel_mobilize(config, expected):
 
 @pytest.mark.parametrize(
     "config",
-    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder),
-    ids=["wtiv_only", "single_feeder", "multi_feeder"],
+    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating),
+    ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"],
 )
 @pytest.mark.parametrize(
     "weather", (None, test_weather), ids=["no_weather", "test_weather"]
@@ -109,8 +116,8 @@ def test_for_complete_logging(weather, config):
 
 @pytest.mark.parametrize(
     "config",
-    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder),
-    ids=["wtiv_only", "single_feeder", "multi_feeder"],
+    (config_wtiv, config_wtiv_feeder, config_wtiv_multi_feeder, floating),
+    ids=["wtiv_only", "single_feeder", "multi_feeder", "floating"],
 )
 def test_for_complete_installation(config):
 
@@ -167,6 +174,56 @@ def test_kwargs():
         assert True
 
 
+def test_kwargs_in_ProjectManager():
+
+    base = deepcopy(config_wtiv)
+    base["install_phases"] = ["TurbineInstallation"]
+
+    project = ProjectManager(base)
+    project.run_project()
+    baseline = project.phase_times["TurbineInstallation"]
+
+    keywords = [
+        "tower_section_fasten_time",
+        "tower_section_release_time",
+        "tower_section_attach_time",
+        "nacelle_fasten_time",
+        "nacelle_release_time",
+        "nacelle_attach_time",
+        "blade_fasten_time",
+        "blade_release_time",
+        "blade_attach_time",
+        "site_position_time",
+        "crane_reequip_time",
+    ]
+
+    failed = []
+
+    for kw in keywords:
+
+        default = pt[kw]
+        processes = {kw: default + 2}
+
+        new_config = deepcopy(base)
+        new_config["processes"] = processes
+
+        new_project = ProjectManager(new_config)
+        new_project.run_project()
+        new_time = new_project.phase_times["TurbineInstallation"]
+
+        if new_time > baseline:
+            pass
+
+        else:
+            failed.append(kw)
+
+    if failed:
+        raise Exception(f"'{failed}' not affecting results.")
+
+    else:
+        assert True
+
+
 def test_multiple_tower_sections():
 
     sim = TurbineInstallation(config_wtiv)
diff --git a/tests/test_config_management.py b/tests/test_config_management.py
new file mode 100644
index 00000000..f635f271
--- /dev/null
+++ b/tests/test_config_management.py
@@ -0,0 +1,30 @@
+__author__ = "Jake Nunemaker"
+__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
+__maintainer__ = "Jake Nunemaker"
+__email__ = "jake.nunemaker@nrel.gov"
+
+
+import os
+
+import pytest
+
+from ORBIT import ProjectManager, load_config, save_config
+from ORBIT.core.library import extract_library_specs
+
+complete_project = extract_library_specs("config", "complete_project")
+
+
+def test_save_and_load_equality(tmp_yaml_del):
+
+    save_config(complete_project, "tmp.yaml", overwrite=True)
+    new = load_config("tmp.yaml")
+
+    assert new == complete_project
+
+
+def test_orbit_version_ProjectManager():
+
+    config = ProjectManager.compile_input_dict(
+        ["MonopileDesign", "MonopileInstallation"]
+    )
+    assert "orbit_version" in config.keys()
diff --git a/tests/test_design_install_phase_interactions.py b/tests/test_design_install_phase_interactions.py
index 98a0cc95..0e845c3f 100644
--- a/tests/test_design_install_phase_interactions.py
+++ b/tests/test_design_install_phase_interactions.py
@@ -6,93 +6,84 @@
 
 from copy import deepcopy
 
-from ORBIT import ProjectManager
+from numpy.testing import assert_almost_equal
 
-config = {
-    "wtiv": "test_wtiv",
-    "site": {"depth": 20, "distance": 20, "mean_windspeed": 9},
-    "plant": {"num_turbines": 20},
-    "turbine": {
-        "hub_height": 130,
-        "rotor_diameter": 154,
-        "rated_windspeed": 11,
-    },
-    "port": {"num_cranes": 1, "monthly_rate": 2e6},
-    "monopile": {
-        "type": "Monopile",
-        "length": 60,
-        "diameter": 8,
-        "deck_space": 0,
-        "mass": 600,
-    },
-    "transition_piece": {
-        "type": "Transition Piece",
-        "deck_space": 0,
-        "mass": 500,
-    },
-    "monopile_design": {},
-    "design_phases": ["MonopileDesign"],
-    "install_phases": ["MonopileInstallation"],
-}
-
-
-def test_monopile_definition():
-
-    test_config = deepcopy(config)
-    _ = test_config.pop("transition_piece")
-
-    project = ProjectManager(test_config)
-    project.run_project()
+from ORBIT import ProjectManager
+from ORBIT.core.library import extract_library_specs
 
-    for key, value in config["monopile"].items():
-        if key == "type":
-            continue
+fixed = extract_library_specs("config", "complete_project")
+floating = extract_library_specs("config", "complete_floating_project")
 
-        assert project.config["monopile"][key] == value
 
-    for key, value in config["transition_piece"].items():
-        if key == "type":
-            continue
+def test_fixed_phase_cost_passing():
 
-        assert project.config["transition_piece"][key] != value
+    project = ProjectManager(fixed)
+    project.run_project()
 
+    assert_almost_equal(
+        project.phases["MonopileDesign"].total_cost,
+        project.phases["MonopileInstallation"].system_capex,
+    )
 
-def test_transition_piece_definition():
+    assert_almost_equal(
+        project.phases["ScourProtectionDesign"].total_cost,
+        project.phases["ScourProtectionInstallation"].system_capex,
+    )
 
-    test_config = deepcopy(config)
-    _ = test_config.pop("monopile")
+    assert_almost_equal(
+        project.phases["ArraySystemDesign"].total_cost,
+        project.phases["ArrayCableInstallation"].system_capex,
+    )
 
-    project = ProjectManager(test_config)
-    project.run_project()
+    assert_almost_equal(
+        project.phases["ExportSystemDesign"].total_cost,
+        project.phases["ExportCableInstallation"].system_capex,
+    )
 
-    for key, value in config["monopile"].items():
-        if key == "type":
-            continue
+    assert_almost_equal(
+        project.phases["OffshoreSubstationDesign"].total_cost,
+        project.phases["OffshoreSubstationInstallation"].system_capex,
+    )
 
-        assert project.config["monopile"][key] != value
 
-    for key, value in config["transition_piece"].items():
-        if key == "type":
-            continue
+def test_floating_phase_cost_passing():
 
-        assert project.config["transition_piece"][key] == value
+    project = ProjectManager(floating)
+    project.run_project()
 
+    assert_almost_equal(
+        project.phases["MooringSystemDesign"].total_cost,
+        project.phases["MooringSystemInstallation"].system_capex,
+    )
 
-def test_mono_and_tp_definition():
+    assert_almost_equal(
+        project.phases["SemiSubmersibleDesign"].total_cost,
+        project.phases["MooredSubInstallation"].system_capex,
+    )
 
-    test_config = deepcopy(config)
+    assert_almost_equal(
+        project.phases["ArraySystemDesign"].total_cost,
+        project.phases["ArrayCableInstallation"].system_capex,
+    )
 
-    project = ProjectManager(test_config)
-    project.run_project()
+    assert_almost_equal(
+        project.phases["ExportSystemDesign"].total_cost,
+        project.phases["ExportCableInstallation"].system_capex,
+    )
 
-    for key, value in config["monopile"].items():
-        if key == "type":
-            continue
+    assert_almost_equal(
+        project.phases["OffshoreSubstationDesign"].total_cost,
+        project.phases["OffshoreSubstationInstallation"].system_capex,
+    )
 
-        assert project.config["monopile"][key] == value
+    spar = deepcopy(floating)
+    spar["design_phases"].remove("SemiSubmersibleDesign")
+    spar["design_phases"].append("SparDesign")
 
-    for key, value in config["transition_piece"].items():
-        if key == "type":
-            continue
+    project = ProjectManager(spar)
+    project.run_project()
 
-        assert project.config["transition_piece"][key] == value
+    assert_almost_equal(
+        project.phases["SparDesign"].total_cost,
+        project.phases["MooredSubInstallation"].system_capex,
+    )
diff --git a/tests/test_parametric.py b/tests/test_parametric.py
new file mode 100644
index 00000000..0a58de68
--- /dev/null
+++ b/tests/test_parametric.py
@@ -0,0 +1,90 @@
+__author__ = "Jake Nunemaker"
+__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
+__maintainer__ = "Jake Nunemaker"
+__email__ = "jake.nunemaker@nrel.gov"
+
+from copy import deepcopy
+
+import pandas as pd
+import pytest
+from benedict import benedict
+
+from ORBIT import ProjectManager, ParametricManager
+from tests.data import test_weather
+from ORBIT.core.library import extract_library_specs
+from ORBIT.phases.install import TurbineInstallation
+
+complete_project = extract_library_specs("config", "complete_project")
+weather_df = pd.DataFrame(test_weather).set_index("datetime")
+
+
+params = {"site.distance": [20, 40, 60]}
+
+funcs = {"bos_capex": lambda project: project.bos_capex}
+
+
+def test_for_equal_results():
+
+    config = benedict(deepcopy(complete_project))
+    config["site.distance"] = 20
+    project = ProjectManager(config)
+    project.run_project()
+
+    parametric = ParametricManager(config, params, funcs)
+    parametric.run()
+    df = parametric.results.set_index("site.distance")
+    assert df.loc[20]["bos_capex"] == project.bos_capex
+
+
+def test_weather():
+
+    without = ParametricManager(complete_project, params, funcs)
+    without.run()
+
+    weathered = ParametricManager(
+        complete_project, params, funcs, weather=weather_df
+    )
+    weathered.run()
+
+    assert all(weathered.results["bos_capex"] > without.results["bos_capex"])
+
+
+def test_individual_phase():
+
+    config = benedict(deepcopy(complete_project))
+    config["site.distance"] = 20
+    phase = TurbineInstallation(config)
+    phase.run()
+
+    funcs = {"time": lambda phase: phase.total_phase_time}
+
+    parametric = ParametricManager(
+        complete_project, params, funcs, module=TurbineInstallation
+    )
+    parametric.run()
+    df = parametric.results.set_index("site.distance")
+    assert df.loc[20]["time"] == phase.total_phase_time
+
+
+def test_bad_result_attribute():
+
+    funcs = {"result": lambda phase: phase.nonexistent_result}
+
+    parametric = ParametricManager(
+        complete_project, params, funcs, module=TurbineInstallation
+    )
+    parametric.run()
+    df = parametric.results
+    assert df["result"].isnull().all()
+
+
+def test_bad_result_structure():
+
+    funcs = {"result": "bos_capex"}
+
+    parametric = ParametricManager(
+        complete_project, params, funcs, module=TurbineInstallation
+    )
+
+    with pytest.raises(TypeError):
+        parametric.run()
diff --git a/tests/test_project_manager.py b/tests/test_project_manager.py
index c4110ae8..8716f6a7 100644
--- a/tests/test_project_manager.py
+++ b/tests/test_project_manager.py
@@ -11,8 +11,8 @@
 
 from ORBIT import ProjectManager
 from tests.data import test_weather
-from ORBIT.library import extract_library_specs
 from ORBIT.manager import ProjectProgress
+from ORBIT.core.library import extract_library_specs
 from ORBIT.core.exceptions import (
     MissingInputs,
     PhaseNotFound,
@@ -171,7 +171,10 @@ def test_chained_dependencies():
 
     config_chained = deepcopy(config)
     config_chained["spi_vessel"] = "test_scour_protection_vessel"
-    config_chained["scour_protection"] = {"tons_per_substructure": 200}
+    config_chained["scour_protection"] = {
+        "tonnes_per_substructure": 200,
+        "cost_per_tonne": 45,
+    }
     config_chained["install_phases"] = {
         "ScourProtectionInstallation": 0,
         "MonopileInstallation": ("ScourProtectionInstallation", 0.1),
@@ -426,7 +429,10 @@ def test_circular_dependencies():
 
     circular_deps = deepcopy(config)
     circular_deps["spi_vessel"] = "test_scour_protection_vessel"
-    circular_deps["scour_protection"] = {"tons_per_substructure": 200}
+    circular_deps["scour_protection"] = {
+        "tonnes_per_substructure": 200,
+        "cost_per_tonne": 45,
+    }
     circular_deps["install_phases"] = {
         "ScourProtectionInstallation": 0,
         "MonopileInstallation": ("TurbineInstallation", 0.1),
@@ -442,7 +448,10 @@ def test_dependent_phase_ordering():
 
     wrong_order = deepcopy(config)
     wrong_order["spi_vessel"] = "test_scour_protection_vessel"
-    wrong_order["scour_protection"] = {"tons_per_substructure": 200}
+    wrong_order["scour_protection"] = {
+        "tonnes_per_substructure": 200,
+        "cost_per_tonne": 45,
+    }
     wrong_order["install_phases"] = {
         "ScourProtectionInstallation": ("TurbineInstallation", 0.1),
         "TurbineInstallation": ("MonopileInstallation", 0.1),
@@ -594,31 +603,93 @@ def test_npv():
     baseline = project.npv
 
     config = deepcopy(complete_project)
-    config["ncf"] = 0.35
+    config["project_parameters"] = {"ncf": 0.35}
     project = ProjectManager(config)
     project.run_project()
     assert project.npv != baseline
 
     config = deepcopy(complete_project)
-    config["offtake_price"] = 70
+    config["project_parameters"] = {"offtake_price": 70}
     project = ProjectManager(config)
     project.run_project()
     assert project.npv != baseline
 
     config = deepcopy(complete_project)
-    config["project_lifetime"] = 30
+    config["project_parameters"] = {"project_lifetime": 30}
     project = ProjectManager(config)
     project.run_project()
     assert project.npv != baseline
 
     config = deepcopy(complete_project)
-    config["discount_rate"] = 0.03
+    config["project_parameters"] = {"discount_rate": 0.03}
     project = ProjectManager(config)
     project.run_project()
     assert project.npv != baseline
 
     config = deepcopy(complete_project)
-    config["opex_rate"] = 120
+    config["project_parameters"] = {"opex_rate": 120}
     project = ProjectManager(config)
     project.run_project()
     assert project.npv != baseline
+
+
+def test_soft_costs():
+
+    project = ProjectManager(complete_project)
+    baseline = project.soft_capex
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"construction_insurance": 50}
+    project = ProjectManager(config)
+    assert project.soft_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"construction_financing": 190}
+    project = ProjectManager(config)
+    assert project.soft_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"contingency": 320}
+    project = ProjectManager(config)
+    assert project.soft_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"contingency": 320}
+    project = ProjectManager(config)
+    assert project.soft_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"commissioning": 50}
+    project = ProjectManager(config)
+    assert project.soft_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"decommissioning": 50}
+    project = ProjectManager(config)
+    assert project.soft_capex != baseline
+
+
+def test_project_costs():
+
+    project = ProjectManager(complete_project)
+    baseline = project.project_capex
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"site_auction_price": 50e6}
+    project = ProjectManager(config)
+    assert project.project_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"site_assessment_cost": 25e6}
+    project = ProjectManager(config)
+    assert project.project_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"construction_plan_cost": 25e6}
+    project = ProjectManager(config)
+    assert project.project_capex != baseline
+
+    config = deepcopy(complete_project)
+    config["project_parameters"] = {"installation_plan_cost": 25e6}
+    project = ProjectManager(config)
+    assert project.project_capex != baseline