Repository: IN-CORE/pyincore (language: python)

Issue:
fix heatmap filter bug

remove deprecated methods from pyIncore
remove deprecated methods (@deprecated) that are annotated in pyIncore.

 

remove the following scripts:

fragilitycurve.py

conditionalstandardfragilitycurve.py

customexpressionfragilitycurve.py

parametricfragilitycurve.py

periodbuildingfragilitycurve.py

periodstandardfragilitycurve.py

standardfragilitycurve.py

 

rename fragilitycurverefactored.py to fragilitycurve.py

 

rename fragilitycurveset.py's method calculate_limit_state_refactored_w_conversion to calculate_limit_state
remove deprecated methods from pyIncore
remove deprecated methods (@deprecated) that are annotated in pyIncore.

 

remove the following scripts:

fragilitycurve.py

conditionalstandardfragilitycurve.py

customexpressionfragilitycurve.py

parametricfragilitycurve.py

periodbuildingfragilitycurve.py

periodstandardfragilitycurve.py

standardfragilitycurve.py

 

rename fragilitycurverefactored.py to fragilitycurve.py

 

rename fragilitycurveset.py's method calculate_limit_state_refactored_w_conversion to calculate_limit_state
remove deprecated methods from pyIncore
remove deprecated methods (@deprecated) that are annotated in pyIncore.

 

remove the following scripts:

fragilitycurve.py

conditionalstandardfragilitycurve.py

customexpressionfragilitycurve.py

parametricfragilitycurve.py

periodbuildingfragilitycurve.py

periodstandardfragilitycurve.py

standardfragilitycurve.py

 

rename fragilitycurverefactored.py to fragilitycurve.py

 

rename fragilitycurveset.py's method calculate_limit_state_refactored_w_conversion to calculate_limit_state
pyincore util to convert population dislocation output for heatmap

 show the api response messages that the services return
When pyincore receives an error message from api, it just prints the error code (500 etc.) and ignores the message included. Below is an example of what pyincore shows to user

ERROR - client.py:return_http_response() - HTTPError: The server returned a 500 failure response code. You can find information about HTTP response status codes here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
--
raise self._exception
requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: https://incore-tst.ncsa.illinois.edu/hazard/api/earthquakes/5dfa4058b9219c934b64d495/values

Add github action to run unit tests
Here is the pytest line from the old build server:

pytest --cache-clear -s pyincore/test_client.py pyincore/test_dataservice.py pyincore/test_dfr3service.py pyincore/test_spaceservice.py pyincore/test_hazardservice.py --junitxml=test-reports/results.xml

A couple of things here. 

1. We need to probably setup a conda environment because requirements.txt has a reference to ipopt
2. We need to use the test user to connect to the services to run these tests
Add Joplin building functionality recovery time model
Implement only the original, empirical part
need to pass the dataset type as a parameter to from_dataframe method
@classmethod
def from_dataframe(cls, dataframe, name):
"""Get Dataset from Panda's DataFrame.

Args:
dataframe (obj): Panda's DataFrame.
name (str): filename.

Returns:
obj: Dataset from file.

"""
dataframe.to_csv(name, index=False)
return Dataset.from_file(name, "csv")


convert hua and pd outputs to json
Lisa's notebook does not run pyincore analysis. There is a csv file with retrofits Wangoutput_2020-05-28_sheet1.csv which is merged with IN-CORE_2ev3_SetupJoplin_FourInventories_2019-08-06_HUinventory.csv. There are two lines named Population dislocation which is compared to HUA dislocation. That might be the only code to be run in pyIncore.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/doc.yaml b/.github/workflows/doc.yaml
new file mode 100644
index 000000000..d96b6eaa3
--- /dev/null
+++ b/.github/workflows/doc.yaml
@@ -0,0 +1,87 @@
+name: DOC
+
+on:
+  push:
+    branches:
+      - master
+      - develop
+      - 'release/*'
+
+    tags:
+      - '*'
+
+  pull_request:
+    branches:
+      - master
+      - develop
+      - 'release/*'
+
+env:
+  MAIN_REPO: IN-CORE/pyincore
+
+jobs:
+
+  # ----------------------------------------------------------------------
+  # DOCKER BUILD
+  # ----------------------------------------------------------------------
+  docker:
+    runs-on: ubuntu-latest
+
+    steps:
+      # checkout source code
+      - uses: actions/checkout@v2
+
+      # calculate some variables that are used later
+      - name: version information
+        run: |
+          if [ "${{ github.event.release.target_commitish }}" != "" ]; then
+            BRANCH="${{ github.event.release.target_commitish }}"
+          elif [[ $GITHUB_REF =~ pull ]]; then
+            BRANCH="$(echo $GITHUB_REF | sed 's#refs/pull/\([0-9]*\)/merge#PR-\1#')"
+          else
+            BRANCH=${GITHUB_REF##*/}
+          fi
+          echo "GITHUB_BRANCH=${BRANCH}" >> $GITHUB_ENV
+          if [ "$BRANCH" == "master" ]; then
+            version=$(awk -F= '/^release/ { print $2}' docs/source/conf.py | sed "s/[ ']//g")
+            tags="latest"
+            oldversion=""
+            while [ "${oldversion}" != "${version}" ]; do
+              oldversion="${version}"
+              tags="${tags},${version}"
+              version=${version%.*}
+            done
+            echo "VERSION=${version}" >> $GITHUB_ENV
+            echo "TAGS=${tags}" >> $GITHUB_ENV
+          elif [ "$BRANCH" == "develop" ]; then
+            echo "VERSION=develop" >> $GITHUB_ENV
+            echo "TAGS=develop" >> $GITHUB_ENV
+          else
+            echo "VERSION=testing" >> $GITHUB_ENV
+            echo "TAGS=${BRANCH}" >> $GITHUB_ENV
+          fi
+
+      # build image
+      - name: Build image
+        uses: elgohr/Publish-Docker-Github-Action@3.04
+        with:
+          name: incore/doc/pyincore
+          no_push: true
+
+      # this will publish to NCSA
+      - name: Publish to NCSA Hub
+        #if: github.event_name != 'pull_request' && github.repository == env.MAIN_REPO
+        if: github.repository == env.MAIN_REPO
+        uses: elgohr/Publish-Docker-Github-Action@3.04
+        env:
+          BRANCH: ${{ env.GITHUB_BRANCH }}
+          VERSION: ${{ env.VERSION }}
+          BUILDNUMBER: ${{ github.run_number }}
+          GITSHA1: ${{ github.sha  }}
+        with:
+          registry: hub.ncsa.illinois.edu
+          name: incore/doc/pyincore
+          username: ${{ secrets.HUB_USERNAME }}
+          password: ${{ secrets.HUB_PASSWORD }}
+          tags: "${{ env.TAGS }}"
+          buildargs: BRANCH,VERSION,BUILDNUMBER,GITSHA1
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e7053b62..f7df4847b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
+## [1.1.0] - 2021-10-27
+
+### Added
+- Convert HUA and PD outputs to JSON [#9](https://github.com/IN-CORE/pyincore/issues/9)
+- Convert population dislocation output to heatmap [#3](https://github.com/IN-CORE/pyincore/issues/3)
+- Joplin empirical restoration analysis [#28](https://github.com/IN-CORE/pyincore/issues/28)
+- GitHub action to run unit tests [#26](https://github.com/IN-CORE/pyincore/issues/26)
+- GitHub action to build documentation [#23](https://github.com/IN-CORE/pyincore/issues/23)
+- Conda recipe [#17](https://github.com/IN-CORE/pyincore/issues/17)
+
+### Changed
+- Percent change in utils converting CGE output to JSON [#34](https://github.com/IN-CORE/pyincore/issues/34)
+- Show API response messages that services return [#6](https://github.com/IN-CORE/pyincore/issues/6)
+- Removed deprecated methods [#7](https://github.com/IN-CORE/pyincore/issues/7)
+
+### Fixed
+- Pass dataset type as parameter to from_dataframe method [#8](https://github.com/IN-CORE/pyincore/issues/8)
+- PEP8 styling issues [#20](https://github.com/IN-CORE/pyincore/issues/20)
+- Corrections to residential building recovery [#25](https://github.com/IN-CORE/pyincore/issues/25)
+
 ## [1.0.0] - 2021-08-31
 ### Changed
 - Improve runtime efficiency of residential recovery analysis [INCORE1-1339](https://opensource.ncsa.illinois.edu/jira/browse/INCORE1-1339)
diff --git a/Dockerfile.docs b/Dockerfile
similarity index 100%
rename from Dockerfile.docs
rename to Dockerfile
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 0e4adc1a9..ec04374f7 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -33,9 +33,9 @@
 author = ''
 
 # The short X.Y version
-version = '1.0'
+version = '1.1'
 # The full version, including alpha/beta/rc tags
-release = '1.0.0'
+release = '1.1.0'
 
 # -- General configuration ---------------------------------------------------
 
diff --git a/docs/source/modules.rst b/docs/source/modules.rst
index de975a5a4..dc3f3e4b9 100644
--- a/docs/source/modules.rst
+++ b/docs/source/modules.rst
@@ -59,7 +59,7 @@ analyses/example
     :members:
 
 analyses/housingrecoverysequential
-==============================
+==================================
 ..  autoclass:: housingrecoverysequential.housingrecoverysequential.HousingRecoverySequential
     :members:
 
@@ -89,6 +89,13 @@ analyses/joplincge
 ..  autofunction:: joplincge.outputfunctions.get_diff
     :members:
 
+analyses/joplinempiricalrestoration
+===================================
+..  autoclass:: joplinempiricalrestoration.joplinempiricalrestoration.JoplinEmpiricalRestoration
+    :members:
+..  autoclass:: joplinempiricalrestoration.joplinempirrestor_util.JoplinEmpirRestorUtil
+    :members:
+
 analyses/meandamage
 ===================
 ..  autoclass:: meandamage.meandamage.MeanDamage
@@ -126,7 +133,7 @@ analyses/populationdislocation
     :members:
 
 analyses/residentialbuildingrecovery
-============================
+====================================
 ..  autoclass:: residentialbuildingrecovery.residentialbuildingrecovery.ResidentialBuildingRecovery
     :members:
 
@@ -252,6 +259,11 @@ utils/analysisutil
 ..  autoclass:: utils.analysisutil.AnalysisUtil
     :members:
 
+utils/cgeoutputprocess
+======================
+..  autoclass:: utils.cgeoutputprocess.CGEOutputProcess
+    :members:
+
 utils/dataprocessutil
 =====================
 ..  autoclass:: utils.dataprocessutil.DataProcessUtil
@@ -272,6 +284,11 @@ utils/geoutil
 ..  autoclass:: utils.geoutil.GeoUtil
     :members:
 
+utils/popdisloutputprocess.py
+=============================
+..  autoclass:: utils.popdisloutputprocess.PopDislOutputProcess
+    :members:
+
 utils/networkutil
 =================
 ..  autoclass:: utils.networkutil.NetworkUtil
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 000000000..1e69a59b7
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,27 @@
+name: base
+channels:
+  - conda-forge
+  - defaults
+dependencies:
+  - boto3
+  - deprecated
+  - fiona>=1.8.4
+  - geopandas>=0.6.1
+  - ipopt>=3.11
+  - jsonpickle>=1.1
+  - networkx>=2.2
+  - numpy>=1.16.6,<2.0a0
+  - owslib>=0.17.1
+  - pandas>=0.24.1
+  - pycodestyle>=2.6.0
+  - pyomo>=5.6
+  - pyproj>=1.9.6
+  - pytest>=3.9.0
+  - python-jose>=3.0
+  - pyyaml>=3.13
+  - rasterio>=1.0.18
+  - requests>=2.21.0
+  - rtree>=0.8.3
+  - scipy>=1.2.0
+  - shapely>=1.6.4.post1
+  - wntr>=0.1.6
diff --git a/pyincore/__init__.py b/pyincore/__init__.py
index 7ca599fef..d4303cf68 100644
--- a/pyincore/__init__.py
+++ b/pyincore/__init__.py
@@ -20,15 +20,11 @@
 from pyincore.restorationservice import RestorationService
 from pyincore.spaceservice import SpaceService
 from pyincore.utils.analysisutil import AnalysisUtil
+from pyincore.utils.popdisloutputprocess import PopDislOutputProcess
+from pyincore.utils.cgeoutputprocess import CGEOutputProcess
 from pyincore.dataset import Dataset, InventoryDataset, DamageRatioDataset
 from pyincore.models.fragilitycurveset import FragilityCurveSet
-from pyincore.models.standardfragilitycurve import StandardFragilityCurve
-from pyincore.models.periodbuildingfragilitycurve import PeriodBuildingFragilityCurve
-from pyincore.models.periodstandardfragilitycurve import PeriodStandardFragilityCurve
-from pyincore.models.customexpressionfragilitycurve import CustomExpressionFragilityCurve
-from pyincore.models.conditionalstandardfragilitycurve import ConditionalStandardFragilityCurve
-from pyincore.models.parametricfragilitycurve import ParametricFragilityCurve
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 from pyincore.models.mappingset import MappingSet
 from pyincore.models.mapping import Mapping
 from pyincore.networkdata import NetworkData
diff --git a/pyincore/analyses/bridgedamage/bridgedamage.py b/pyincore/analyses/bridgedamage/bridgedamage.py
index b9f08d045..65249803f 100644
--- a/pyincore/analyses/bridgedamage/bridgedamage.py
+++ b/pyincore/analyses/bridgedamage/bridgedamage.py
@@ -13,7 +13,7 @@
 from pyincore import AnalysisUtil, GeoUtil
 from pyincore import BaseAnalysis, HazardService, FragilityService
 from pyincore.analyses.bridgedamage.bridgeutil import BridgeUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class BridgeDamage(BaseAnalysis):
@@ -175,7 +175,7 @@ def bridge_damage_analysis_bulk_input(self, bridges, hazard_type,
             dmg_intervals = dict()
             selected_fragility_set = fragility_set[bridge["id"]]
 
-            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurve):
                 # Supports multiple demand types in same fragility
                 hazard_val = AnalysisUtil.update_precision_of_lists(hazard_vals[i]["hazardValues"])
                 input_demand_types = hazard_vals[i]["demands"]
@@ -190,9 +190,9 @@ def bridge_damage_analysis_bulk_input(self, bridges, hazard_type,
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_vals[i]["hazardValues"]):
                     bridge_args = selected_fragility_set.construct_expression_args_from_inventory(bridge)
                     dmg_probability = \
-                        selected_fragility_set.calculate_limit_state_refactored_w_conversion(hval_dict,
-                                                                                             inventory_type="bridge",
-                                                                                             **bridge_args)
+                        selected_fragility_set.calculate_limit_state(hval_dict,
+                                                                     inventory_type="bridge",
+                                                                     **bridge_args)
                     dmg_intervals = selected_fragility_set.calculate_damage_interval(dmg_probability,
                                                                                      hazard_type=hazard_type,
                                                                                      inventory_type="bridge")
diff --git a/pyincore/analyses/buildingdamage/buildingdamage.py b/pyincore/analyses/buildingdamage/buildingdamage.py
index e129c96bb..36db97f7b 100755
--- a/pyincore/analyses/buildingdamage/buildingdamage.py
+++ b/pyincore/analyses/buildingdamage/buildingdamage.py
@@ -11,7 +11,7 @@
 from pyincore import BaseAnalysis, HazardService, \
     FragilityService, AnalysisUtil, GeoUtil
 from pyincore.analyses.buildingdamage.buildingutil import BuildingUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class BuildingDamage(BaseAnalysis):
@@ -174,7 +174,7 @@ def building_damage_analysis_bulk_input(self, buildings, retrofit_strategy, haza
             building_period = selected_fragility_set.fragility_curves[0].get_building_period(num_stories)
 
             # TODO: Once all fragilities are migrated to new format, we can remove this condition
-            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurve):
                 # Supports multiple demand types in same fragility
                 b_haz_vals = AnalysisUtil.update_precision_of_lists(hazard_vals[i]["hazardValues"])
                 b_demands = hazard_vals[i]["demands"]
@@ -192,7 +192,7 @@ def building_damage_analysis_bulk_input(self, buildings, retrofit_strategy, haza
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_vals[i]["hazardValues"]):
                     building_args = selected_fragility_set.construct_expression_args_from_inventory(b)
 
-                    dmg_probability = selected_fragility_set.calculate_limit_state_refactored_w_conversion(
+                    dmg_probability = selected_fragility_set.calculate_limit_state(
                         hval_dict, **building_args, period=building_period)
                     dmg_interval = selected_fragility_set.calculate_damage_interval(
                         dmg_probability, hazard_type=hazard_type, inventory_type="building")
diff --git a/pyincore/analyses/cumulativebuildingdamage/cumulativebuildingdamage.py b/pyincore/analyses/cumulativebuildingdamage/cumulativebuildingdamage.py
index a6a1102f0..5f4be6738 100644
--- a/pyincore/analyses/cumulativebuildingdamage/cumulativebuildingdamage.py
+++ b/pyincore/analyses/cumulativebuildingdamage/cumulativebuildingdamage.py
@@ -127,20 +127,21 @@ def cumulative_building_damage(self, eq_building_damage,
 
                 limit_states = collections.OrderedDict()
 
-                limit_states["LS_0"] = eq_limit_states["LS_0"] + tsunami_limit_states["LS_0"] \
-                                       - eq_limit_states["LS_0"] * tsunami_limit_states["LS_0"]
-
-                limit_states["LS_1"] = eq_limit_states["LS_1"] + tsunami_limit_states["LS_1"] \
-                                       - eq_limit_states["LS_1"] * tsunami_limit_states["LS_1"] \
-                                       + ((eq_limit_states["LS_0"] - eq_limit_states["LS_1"]) * (
-                            tsunami_limit_states["LS_0"] -
-                            tsunami_limit_states["LS_1"]))
-
-                limit_states["LS_2"] = eq_limit_states["LS_2"] + tsunami_limit_states["LS_2"] \
-                                       - eq_limit_states["LS_2"] * tsunami_limit_states["LS_2"] \
-                                       + ((eq_limit_states["LS_1"] - eq_limit_states["LS_2"]) * (
-                            tsunami_limit_states["LS_1"] -
-                            tsunami_limit_states["LS_2"]))
+                limit_states["LS_0"] = \
+                    eq_limit_states["LS_0"] + tsunami_limit_states["LS_0"] \
+                    - eq_limit_states["LS_0"] * tsunami_limit_states["LS_0"]
+
+                limit_states["LS_1"] = \
+                    eq_limit_states["LS_1"] + tsunami_limit_states["LS_1"] \
+                    - eq_limit_states["LS_1"] * tsunami_limit_states["LS_1"] \
+                    + ((eq_limit_states["LS_0"]
+                        - eq_limit_states["LS_1"]) * (tsunami_limit_states["LS_0"] - tsunami_limit_states["LS_1"]))
+
+                limit_states["LS_2"] = \
+                    eq_limit_states["LS_2"] + tsunami_limit_states["LS_2"] \
+                    - eq_limit_states["LS_2"] * tsunami_limit_states["LS_2"] \
+                    + ((eq_limit_states["LS_1"]
+                        - eq_limit_states["LS_2"]) * (tsunami_limit_states["LS_1"] - tsunami_limit_states["LS_2"]))
 
                 damage_state = FragilityCurveSet._3ls_to_4ds(limit_states)
 
diff --git a/pyincore/analyses/epfdamage/epfdamage.py b/pyincore/analyses/epfdamage/epfdamage.py
index 1eb1a0c40..12ad9ef21 100644
--- a/pyincore/analyses/epfdamage/epfdamage.py
+++ b/pyincore/analyses/epfdamage/epfdamage.py
@@ -10,7 +10,7 @@
 
 from pyincore import AnalysisUtil, GeoUtil
 from pyincore import BaseAnalysis, HazardService, FragilityService
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class EpfDamage(BaseAnalysis):
@@ -81,8 +81,8 @@ def run(self):
                                                                          repeat(liq_geology_dataset_id))
 
         self.set_result_csv_data("result", ds_results, name=self.get_parameter("result_name"))
-        self.set_result_json_data("metadata", damage_results, name=self.get_parameter("result_name") +
-                                                                   "_additional_info")
+        self.set_result_json_data("metadata", damage_results,
+                                  name=self.get_parameter("result_name") + "_additional_info")
 
         return True
 
@@ -172,7 +172,7 @@ def epf_damage_analysis_bulk_input(self, epfs, hazard_type, hazard_dataset_id,
             damage_result = dict()
             selected_fragility_set = fragility_set[epf["id"]]
 
-            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurve):
                 hazard_val = AnalysisUtil.update_precision_of_lists(hazard_vals[i]["hazardValues"])
                 input_demand_types = hazard_vals[i]["demands"]
                 input_demand_units = hazard_vals[i]["units"]
@@ -184,9 +184,9 @@ def epf_damage_analysis_bulk_input(self, epfs, hazard_type, hazard_dataset_id,
                     j += 1
 
                 epf_args = selected_fragility_set.construct_expression_args_from_inventory(epf)
-                limit_states = selected_fragility_set.calculate_limit_state_refactored_w_conversion(hval_dict,
-                                                                                                    inventory_type='electric_facility',
-                                                                                                    **epf_args)
+                limit_states = selected_fragility_set.calculate_limit_state(hval_dict,
+                                                                            inventory_type='electric_facility',
+                                                                            **epf_args)
             else:
                 raise ValueError("One of the fragilities is in deprecated format. This should not happen. If you are "
                                  "seeing this please report the issue.")
@@ -256,7 +256,8 @@ def epf_damage_analysis_bulk_input(self, epfs, hazard_type, hazard_dataset_id,
                 liquefaction_prob = liquefaction_vals[i]['liqProbability']
 
                 selected_liq_fragility = liq_fragility_set[liq_epf_id]
-                pgd_limit_states = selected_liq_fragility.calculate_limit_state(liq_hazard_val, std_dev=std_dev)
+                pgd_limit_states = \
+                    selected_liq_fragility.calculate_limit_state_w_conversion(liq_hazard_val, std_dev=std_dev)
 
                 # match id and add liqhaztype, liqhazval, liqprobability field as well as rewrite limit
                 # states and dmg_interval
diff --git a/pyincore/analyses/joplinempiricalrestoration/__init__.py b/pyincore/analyses/joplinempiricalrestoration/__init__.py
new file mode 100644
index 000000000..2d8f8d0e6
--- /dev/null
+++ b/pyincore/analyses/joplinempiricalrestoration/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021 University of Illinois and others. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Mozilla Public License v2.0 which accompanies this distribution,
+# and is available at https://www.mozilla.org/en-US/MPL/2.0/
+
+
+from pyincore.analyses.joplinempiricalrestoration.joplinempiricalrestoration import JoplinEmpiricalRestoration
+from pyincore.analyses.joplinempiricalrestoration.joplinempirrestor_util import JoplinEmpirRestorUtil
\ No newline at end of file
diff --git a/pyincore/analyses/joplinempiricalrestoration/joplinempiricalrestoration.py b/pyincore/analyses/joplinempiricalrestoration/joplinempiricalrestoration.py
new file mode 100755
index 000000000..63a1884f9
--- /dev/null
+++ b/pyincore/analyses/joplinempiricalrestoration/joplinempiricalrestoration.py
@@ -0,0 +1,169 @@
+# Copyright (c) 2021 University of Illinois and others. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Mozilla Public License v2.0 which accompanies this distribution,
+# and is available at https://www.mozilla.org/en-US/MPL/2.0/
+
+import numpy as np
+import pandas as pd
+
+from pyincore import BaseAnalysis
+from pyincore.analyses.joplinempiricalrestoration.joplinempirrestor_util import JoplinEmpirRestorUtil
+
+
+class JoplinEmpiricalRestoration(BaseAnalysis):
+    """ Joplin Empirical Restoration Model generates a random realization for the restoration time of
+    a building damaged in a tornado event to be restored to a certain functionality level. Functionality
+    levels in this model are defined according to Koliou and van de Lindt (2020) and range from Functionality
+    Level 4 (FL4, the lowest functionality) to Functionality Level 0 (FL0, full functionality).
+
+    Args:
+        incore_client (IncoreClient): Service authentication.
+
+    """
+
+    def __init__(self, incore_client):
+        super(JoplinEmpiricalRestoration, self).__init__(incore_client)
+
+    def run(self):
+        """ Executes Joplin empirical restoration model analysis.
+
+        Returns:
+            bool: True if successful, False otherwise.
+
+        """
+        # Get seed
+        seed_i = self.get_parameter("seed")
+        target_fl = self.get_parameter("target_functionality_level")
+
+        result_name = self.get_parameter("result_name")
+
+        # Building dataset
+        building_set = self.get_input_dataset("buildings").get_dataframe_from_shapefile()
+        # Building damage dataset
+        building_dmg = self.get_input_dataset("building_dmg").get_dataframe_from_csv(low_memory=False)
+
+        # merge and filter out archetypes > 5
+        building_dmg_all = pd.merge(building_dmg, building_set, how="left", on="guid", copy=True, validate="1:1")
+        building_dmg_5 = building_dmg_all[["guid", "archetype", "LS_0", "LS_1", "LS_2", "haz_expose"]].copy()
+        building_func_5 = building_dmg_5[building_dmg_all["archetype"] <= 5]
+
+        building_func = building_func_5[["guid", "LS_0", "LS_1", "LS_2", "haz_expose"]].copy()
+        building_func["targetFL"] = target_fl
+
+        initial_func_level, restoration_days = self.get_restoration_days(seed_i, building_func)
+        building_func["initialFL"] = initial_func_level
+        building_func["restorDays"] = restoration_days
+
+        building_func_fin = building_func[["guid", "initialFL", "targetFL", "restorDays"]]
+        csv_source = "dataframe"
+        self.set_result_csv_data("result", building_func_fin, result_name, csv_source)
+
+        return True
+
+    def get_restoration_days(self, seed_i, building_func):
+        """ Calculates restoration days.
+
+        Args:
+            seed_i (int): Seed for random number generator to ensure replication if run as part
+                    of a stochastic analysis, for example in connection with housing unit allocation analysis.
+            building_dmg (pd.DataFrame): Building damage dataset with guid, limit states, hazard exposure.
+
+        Returns:
+            np.array: Initial functionality level based on damage state
+            np.array: Building restoration days.
+
+        """
+        fl_coef = JoplinEmpirRestorUtil.FL_COEF
+
+        hazard_value = building_func[["haz_expose"]].to_numpy() != "no"
+        hazard_value = hazard_value.flatten()
+
+        bdnp = building_func[["LS_0", "LS_1", "LS_2"]].to_numpy()
+
+        # generate a random number between 0 and 1 and see where in boundaries it locates and use it to assign FL,
+        # for each building
+        rnd_num = np.random.uniform(0, 1, (len(building_func.index, )))
+        bdnp_init = np.zeros(len(building_func.index, )).astype(int)  # first, set all to 0
+        bdnp_init = np.where(rnd_num < bdnp[:, 0], 1, bdnp_init)  # if rnd_num < LS_0 set to 1
+        bdnp_init = np.where(rnd_num < bdnp[:, 1], 2, bdnp_init)  # if rnd_num < LS_0 set to 2
+        bdnp_init = np.where(rnd_num < bdnp[:, 2], 3, bdnp_init)  # if rnd_num < LS_0 set to 3
+
+        bdnp_target = building_func["targetFL"].to_numpy()
+
+        means = fl_coef[bdnp_init, bdnp_target, 0]
+        sigmas = fl_coef[bdnp_init, bdnp_target, 1]
+
+        np.random.seed(seed_i)
+        rest_days = np.random.lognormal(means, sigmas)
+
+        # only when exposed to hazard, otherwise no damage and restoration = 0
+        restoration_days = np.where(hazard_value, rest_days, 0).astype(int)
+        # F1-F4 notation
+        bdnp_init = bdnp_init + 1
+        # F0 where not exposed to hazard
+        initial_func_level = np.where(hazard_value, bdnp_init, 0).astype(int)
+
+        # Output of the model is restoration_days
+        return initial_func_level, restoration_days
+
+    def get_spec(self):
+        """Get specifications of the Joplin empirical restoration analysis.
+
+        Returns:
+            obj: A JSON object of specifications of the Joplin empirical restoration analysis.
+
+        """
+        return {
+            "name": "joplin-empirical-restoration",
+            "description": "Values (in days) for the predicted restoration time of the building.",
+            "input_parameters": [
+                {
+                    "id": "result_name",
+                    "required": True,
+                    "description": "result dataset name",
+                    "type": str
+                },
+                {
+                    "id": "target_functionality_level",
+                    "required": False,
+                    "description": "Target functionality level for all infrastructure",
+                    "type": int
+                },
+                {
+                    "id": "seed",
+                    "required": False,
+                    "description": "Initial seed for the tornado hazard value",
+                    "type": int
+                }
+            ],
+            "input_datasets": [
+                {
+                    'id': 'buildings',
+                    'required': True,
+                    'description': 'Building Inventory',
+                    'type': ['ergo:buildingInventoryVer4', 'ergo:buildingInventoryVer5',
+                             'ergo:buildingInventoryVer6', 'ergo:buildingInventoryVer7'],
+                },
+                {
+                    "id": "building_dmg",
+                    "required": True,
+                    "description": "Building damage results CSV file",
+                    "type": ["ergo:buildingDamageVer4",
+                             "ergo:buildingDamageVer5",
+                             "ergo:buildingDamageVer6",
+                             "ergo:buildingInventory",
+                             "ergo:nsBuildingInventoryDamage",
+                             "ergo:nsBuildingInventoryDamageVer2"]
+                }
+            ],
+            "output_datasets": [
+                {
+                    "id": "result",
+                    "parent_type": "buildings",
+                    "description": "A dataset containing results (format: CSV) with values (in days) for the predicted "
+                                   "restoration time of the building.",
+                    "type": "incore:restorationTime"
+                }
+            ]
+        }
diff --git a/pyincore/analyses/joplinempiricalrestoration/joplinempirrestor_util.py b/pyincore/analyses/joplinempiricalrestoration/joplinempirrestor_util.py
new file mode 100644
index 000000000..a63eda9de
--- /dev/null
+++ b/pyincore/analyses/joplinempiricalrestoration/joplinempirrestor_util.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2021 University of Illinois and others. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Mozilla Public License v2.0 which accompanies this distribution,
+# and is available at https://www.mozilla.org/en-US/MPL/2.0/
+
+import numpy as np
+
+
+class JoplinEmpirRestorUtil:
+    """Utility methods for the Joplin restoration analysis."""
+
+    # Empirical coefficients mean and sigma for lognormal distribution
+    FL_COEF = np.zeros((4, 4, 2), dtype=float)
+    # initial functionality is FL1
+    # target functionality is FL0
+    FL_COEF[0, 0, :] = [5.90, 0.5]
+    # other (for consistency)
+    FL_COEF[1, 1, :] = [0.0, 1.0]
+    FL_COEF[1, 2, :] = [0.0, 1.0]
+    FL_COEF[1, 3, :] = [0.0, 1.0]
+
+    # initial functionality is FL2
+    # target functionality is FL0
+    FL_COEF[1, 0, :] = [6.06, 0.32]
+    # target functionality is FL1
+    FL_COEF[1, 1, :] = [5.87, 0.35]
+    # other (for consistency)
+    FL_COEF[1, 2, :] = [0.0, 1.0]
+    FL_COEF[1, 3, :] = [0.0, 1.0]
+
+    # initial functionality is FL3
+    # target functionality is FL0
+    FL_COEF[2, 0, :] = [5.90, 0.60]
+    # target functionality is FL1
+    FL_COEF[2, 1, :] = [5.74, 0.55]
+    # target functionality is FL2
+    FL_COEF[2, 2, :] = [5.56, 0.46]
+    # other (for consistency)
+    FL_COEF[2, 3, :] = [0.0, 1.0]
+
+    # initial functionality is FL4
+    # target functionality is FL0
+    FL_COEF[3, 0, :] = [6.60, 0.53]
+    # target functionality is FL1
+    FL_COEF[3, 1, :] = [6.39, 0.48]
+    # target functionality is FL2
+    FL_COEF[3, 2, :] = [6.49, 0.6]
+    # target functionality is FL3
+    FL_COEF[3, 3, :] = [6.13, 0.33]
diff --git a/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingdamage.py b/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingdamage.py
index 559558045..beef894fc 100644
--- a/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingdamage.py
+++ b/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingdamage.py
@@ -10,7 +10,7 @@
 from pyincore import BaseAnalysis, HazardService, FragilityService
 from pyincore.analyses.nonstructbuildingdamage.nonstructbuildingutil import \
     NonStructBuildingUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class NonStructBuildingDamage(BaseAnalysis):
@@ -203,7 +203,7 @@ def building_damage_analysis_bulk_input(self, buildings):
 
             ###############
             # AS
-            if isinstance(fragility_set_as.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(fragility_set_as.fragility_curves[0], FragilityCurve):
                 hazard_vals_as = AnalysisUtil.update_precision_of_lists(hazard_resp_as[i]["hazardValues"])
                 demand_types_as = hazard_resp_as[i]["demands"]
                 demand_units_as = hazard_resp_as[i]["units"]
@@ -213,8 +213,8 @@ def building_damage_analysis_bulk_input(self, buildings):
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_resp_as[i]["hazardValues"]):
                     building_args = fragility_set_as.construct_expression_args_from_inventory(building)
                     dmg_probability_as = fragility_set_as. \
-                        calculate_limit_state_refactored_w_conversion(hval_dict_as, inventory_type="building",
-                                                                      **building_args)
+                        calculate_limit_state(hval_dict_as, inventory_type="building",
+                                              **building_args)
                     # adjust dmg probability for liquefaction
                     if use_liquefaction:
                         if liq_geology_dataset_id is not None:
@@ -232,7 +232,7 @@ def building_damage_analysis_bulk_input(self, buildings):
 
             ###############
             # DS
-            if isinstance(fragility_set_ds.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(fragility_set_ds.fragility_curves[0], FragilityCurve):
                 hazard_vals_ds = AnalysisUtil.update_precision_of_lists(hazard_resp_ds[i]["hazardValues"])
                 demand_types_ds = hazard_resp_ds[i]["demands"]
                 demand_units_ds = hazard_resp_ds[i]["units"]
@@ -243,8 +243,8 @@ def building_damage_analysis_bulk_input(self, buildings):
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_resp_ds[i]["hazardValues"]):
                     building_args = fragility_set_ds.construct_expression_args_from_inventory(building)
                     dmg_probability_ds = fragility_set_ds. \
-                        calculate_limit_state_refactored_w_conversion(hval_dict_ds, inventory_type="building",
-                                                                      **building_args)
+                        calculate_limit_state(hval_dict_ds, inventory_type="building",
+                                              **building_args)
                     # adjust dmg probability for liquefaction
                     if use_liquefaction:
                         if liq_geology_dataset_id is not None:
diff --git a/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingutil.py b/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingutil.py
index 0b64ef0ea..21e9f25a5 100644
--- a/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingutil.py
+++ b/pyincore/analyses/nonstructbuildingdamage/nonstructbuildingutil.py
@@ -48,13 +48,15 @@ def adjust_damage_for_liquefaction(limit_state_probabilities, ground_failure_pro
             else:
                 prob_ground_failure = ground_failure_probabilities[i]
 
-            adjusted_limit_state_probabilities[keys[i]] = limit_state_probabilities[keys[i]] + prob_ground_failure \
-                                                          - limit_state_probabilities[keys[i]] * prob_ground_failure
+            adjusted_limit_state_probabilities[keys[i]] = \
+                limit_state_probabilities[keys[i]] + prob_ground_failure \
+                - limit_state_probabilities[keys[i]] * prob_ground_failure
 
         # the final one is the last of limitStates should match with the last of ground failures
         j = len(limit_state_probabilities) - 1
         prob_ground_failure = ground_failure_probabilities[-1]
-        adjusted_limit_state_probabilities[keys[j]] = limit_state_probabilities[keys[j]] + prob_ground_failure \
-                                                      - limit_state_probabilities[keys[j]] * prob_ground_failure
+        adjusted_limit_state_probabilities[keys[j]] = \
+            limit_state_probabilities[keys[j]] \
+            + prob_ground_failure - limit_state_probabilities[keys[j]] * prob_ground_failure
 
         return adjusted_limit_state_probabilities
diff --git a/pyincore/analyses/pipelinedamage/pipelinedamage.py b/pyincore/analyses/pipelinedamage/pipelinedamage.py
index 4f2056456..751eb221c 100644
--- a/pyincore/analyses/pipelinedamage/pipelinedamage.py
+++ b/pyincore/analyses/pipelinedamage/pipelinedamage.py
@@ -12,7 +12,7 @@
 
 from pyincore import BaseAnalysis, HazardService, FragilityService, \
     FragilityCurveSet, AnalysisUtil, GeoUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class PipelineDamage(BaseAnalysis):
@@ -162,7 +162,7 @@ def pipeline_damage_analysis_bulk_input(self, pipelines, hazard_type,
             fragility_set = fragility_sets[pipeline["id"]]
 
             # TODO: Once all fragilities are migrated to new format, we can remove this condition
-            if isinstance(fragility_set.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(fragility_set.fragility_curves[0], FragilityCurve):
                 # Supports multiple demand types in same fragility
                 haz_vals = AnalysisUtil.update_precision_of_lists(hazard_vals[i]["hazardValues"])
                 demand_types = hazard_vals[i]["demands"]
@@ -175,9 +175,9 @@ def pipeline_damage_analysis_bulk_input(self, pipelines, hazard_type,
 
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_vals[i]["hazardValues"]):
                     pipeline_args = fragility_set.construct_expression_args_from_inventory(pipeline)
-                    limit_states = fragility_set.calculate_limit_state_refactored_w_conversion(hval_dict,
-                                                                                               inventory_type="pipeline",
-                                                                                               **pipeline_args)
+                    limit_states = fragility_set.calculate_limit_state(hval_dict,
+                                                                       inventory_type="pipeline",
+                                                                       **pipeline_args)
                     dmg_intervals = fragility_set.calculate_damage_interval(limit_states, hazard_type=hazard_type,
                                                                             inventory_type="pipeline")
 
diff --git a/pyincore/analyses/pipelinedamagerepairrate/pipelinedamagerepairrate.py b/pyincore/analyses/pipelinedamagerepairrate/pipelinedamagerepairrate.py
index 2a06694d3..a8ac64d75 100644
--- a/pyincore/analyses/pipelinedamagerepairrate/pipelinedamagerepairrate.py
+++ b/pyincore/analyses/pipelinedamagerepairrate/pipelinedamagerepairrate.py
@@ -18,7 +18,7 @@
     AnalysisUtil, GeoUtil
 from pyincore.analyses.pipelinedamagerepairrate.pipelineutil import \
     PipelineUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class PipelineDamageRepairRate(BaseAnalysis):
@@ -226,7 +226,7 @@ def pipeline_damage_analysis_bulk_input(self, pipelines, hazard_type,
             fragility_curve = fragility_set.fragility_curves[0]
 
             # TODO: Once all fragilities are migrated to new format, we can remove this condition
-            if isinstance(fragility_set.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(fragility_set.fragility_curves[0], FragilityCurve):
                 hazard_vals = AnalysisUtil.update_precision_of_lists(hazard_resp[i]["hazardValues"])
                 demand_types = hazard_resp[i]["demands"]
                 demand_units = hazard_resp[i]["units"]
@@ -237,9 +237,9 @@ def pipeline_damage_analysis_bulk_input(self, pipelines, hazard_type,
 
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_resp[i]["hazardValues"]):
                     pipeline_args = fragility_set.construct_expression_args_from_inventory(pipeline)
-                    pgv_repairs = fragility_curve.calculate_limit_state_probability(hval_dict,
-                                                                                    fragility_set.fragility_curve_parameters,
-                                                                                    **pipeline_args)
+                    pgv_repairs = \
+                        fragility_curve.calculate_limit_state_probability(
+                            hval_dict, fragility_set.fragility_curve_parameters, **pipeline_args)
                     # Convert PGV repairs to SI units
                     pgv_repairs = PipelineUtil.convert_result_unit(fragility_curve.return_type["unit"], pgv_repairs)
 
@@ -254,7 +254,7 @@ def pipeline_damage_analysis_bulk_input(self, pipelines, hazard_type,
                         liq_fragility_curve = fragility_set_liq.fragility_curves[0]
 
                         # TODO: Once all fragilities are migrated to new format, we can remove this condition
-                        if isinstance(fragility_set_liq.fragility_curves[0], FragilityCurveRefactored):
+                        if isinstance(fragility_set_liq.fragility_curves[0], FragilityCurve):
                             liq_hazard_vals = AnalysisUtil.update_precision_of_lists(liquefaction_resp[i]["pgdValues"])
                             liq_demand_types = liquefaction_resp[i]["demands"]
                             liq_demand_units = liquefaction_resp[i]["units"]
@@ -266,9 +266,9 @@ def pipeline_damage_analysis_bulk_input(self, pipelines, hazard_type,
                             # !important! removing the liqProbability and passing in the "diameter"
                             # no fragility is actually using liqProbability
                             pipeline_args = fragility_set_liq.construct_expression_args_from_inventory(pipeline)
-                            pgd_repairs = liq_fragility_curve.calculate_limit_state_probability(liq_hval_dict,
-                                                                                                fragility_set_liq.fragility_curve_parameters,
-                                                                                                **pipeline_args)
+                            pgd_repairs = \
+                                liq_fragility_curve.calculate_limit_state_probability(
+                                    liq_hval_dict, fragility_set_liq.fragility_curve_parameters, **pipeline_args)
                             # Convert PGD repairs to SI units
                             pgd_repairs = PipelineUtil.convert_result_unit(liq_fragility_curve.return_type["unit"],
                                                                            pgd_repairs)
diff --git a/pyincore/analyses/populationdislocation/populationdislocation.py b/pyincore/analyses/populationdislocation/populationdislocation.py
index fc024807a..66a168908 100644
--- a/pyincore/analyses/populationdislocation/populationdislocation.py
+++ b/pyincore/analyses/populationdislocation/populationdislocation.py
@@ -198,8 +198,7 @@ def get_dislocation(self, seed_i: int, inventory: pd.DataFrame, value_loss: pd.D
 
         # total_prob_disl is the sum of the probability of dislocation at four damage states
         # times the probability of being in that damage state.
-        total_prob_disl = prob0_disl * prob0 + prob1_disl * prob1 + prob2_disl * prob2 + \
-                          prob3_disl * prob3
+        total_prob_disl = prob0_disl * prob0 + prob1_disl * prob1 + prob2_disl * prob2 + prob3_disl * prob3
 
         inventory["prdis"] = total_prob_disl
 
diff --git a/pyincore/analyses/residentialbuildingrecovery/residentialbuildingrecovery.py b/pyincore/analyses/residentialbuildingrecovery/residentialbuildingrecovery.py
index 6e8b87496..86a56bb3d 100644
--- a/pyincore/analyses/residentialbuildingrecovery/residentialbuildingrecovery.py
+++ b/pyincore/analyses/residentialbuildingrecovery/residentialbuildingrecovery.py
@@ -155,8 +155,8 @@ def household_income_prediction(income_groups, num_samples):
             group_hhinc_values = group['hhinc'].values
 
             # Compute normal ddistribution parameters from group data
-            mean = np.mean(group_hhinc_values)
-            std = np.std(group_hhinc_values)
+            mean = np.nanmean(group_hhinc_values)
+            std = np.nanstd(group_hhinc_values)
 
             if np.isnan(mean):
                 mean = 3
@@ -166,7 +166,7 @@ def household_income_prediction(income_groups, num_samples):
 
             # Directly compute the indices of NaN values in the hhinc vector
             group_nan_idx = np.where(np.isnan(group_hhinc_values))
-            number_nan = len(group_nan_idx)
+            number_nan = len(group_nan_idx[0])
 
             # Now, generate a numpy matrix to hold the samples for the group
             group_samples = np.zeros((num_samples, group_size))
@@ -228,10 +228,10 @@ def household_aggregation(household_income_predictions):
 
             group_new = pd.DataFrame(no_guids_maxima, columns=colnames, index=group.index)
             group_new.insert(0, 'guid', local_guids)
-            new_groups.append(group_new)
+            new_groups.append(group_new.head(1))
 
         # Construct a new DataFrame
-        household_aggregation_results = pd.concat(new_groups).sort_index()
+        household_aggregation_results = pd.concat(new_groups).reset_index(drop=True)
 
         return household_aggregation_results
 
diff --git a/pyincore/analyses/roaddamage/roaddamage.py b/pyincore/analyses/roaddamage/roaddamage.py
index 759127e0f..4cbd3b59a 100644
--- a/pyincore/analyses/roaddamage/roaddamage.py
+++ b/pyincore/analyses/roaddamage/roaddamage.py
@@ -9,7 +9,7 @@
 from itertools import repeat
 
 from pyincore import BaseAnalysis, HazardService, FragilityService, AnalysisUtil, GeoUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class RoadDamage(BaseAnalysis):
@@ -191,7 +191,7 @@ def road_damage_analysis_bulk_input(self, roads, hazard_type, hazard_dataset_id,
             if use_hazard_uncertainty:
                 raise ValueError("Uncertainty Not Implemented Yet.")
 
-            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(selected_fragility_set.fragility_curves[0], FragilityCurve):
                 hazard_vals = AnalysisUtil.update_precision_of_lists(hazard_resp[i]["hazardValues"])
                 demand_types = hazard_resp[i]["demands"]
                 demand_units = hazard_resp[i]["units"]
@@ -201,7 +201,7 @@ def road_damage_analysis_bulk_input(self, roads, hazard_type, hazard_dataset_id,
 
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_resp[i]["hazardValues"]):
                     road_args = selected_fragility_set.construct_expression_args_from_inventory(road)
-                    dmg_probability = selected_fragility_set.calculate_limit_state_refactored_w_conversion(
+                    dmg_probability = selected_fragility_set.calculate_limit_state(
                         hval_dict, inventory_type='road', **road_args)
 
                     # if there is liquefaction, overwrite the hazardval with liquefaction value
@@ -214,12 +214,11 @@ def road_damage_analysis_bulk_input(self, roads, hazard_type, hazard_dataset_id,
                         liq_hval_dict = dict()
                         for j, d in enumerate(liquefaction_resp[i]["demands"]):
                             liq_hval_dict[d] = liq_hazard_vals[j]
-                        dmg_probability = selected_fragility_set.calculate_limit_state_refactored_w_conversion(
+                        dmg_probability = selected_fragility_set.calculate_limit_state(
                             liq_hval_dict,
                             inventory_type='road',
                             **road_args)
 
-
                     dmg_interval = selected_fragility_set.calculate_damage_interval(dmg_probability,
                                                                                     hazard_type=hazard_type,
                                                                                     inventory_type="road")
diff --git a/pyincore/analyses/tornadoepndamage/tornadoepndamage.py b/pyincore/analyses/tornadoepndamage/tornadoepndamage.py
index 23a62327b..4df1a7296 100644
--- a/pyincore/analyses/tornadoepndamage/tornadoepndamage.py
+++ b/pyincore/analyses/tornadoepndamage/tornadoepndamage.py
@@ -15,7 +15,7 @@
 
 from pyincore import BaseAnalysis, HazardService, FragilityService, DataService, FragilityCurveSet
 from pyincore import GeoUtil, NetworkUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class TornadoEpnDamage(BaseAnalysis):
@@ -305,11 +305,11 @@ def get_damage(self, node_dataset, link_dataset, tornado_dataset, tornado_id):
                                         hval_dict[d] = tor_hazard_values[j]
                                         j += 1
                                     if isinstance(fragility_set_used.fragility_curves[0],
-                                                  FragilityCurveRefactored):
+                                                  FragilityCurve):
                                         inventory_args = fragility_set_used.construct_expression_args_from_inventory(
                                             tornado_feature)
                                         resistivity_probability = \
-                                            fragility_set_used.calculate_limit_state_refactored_w_conversion(
+                                            fragility_set_used.calculate_limit_state(
                                                 hval_dict,
                                                 inventory_type=fragility_set_used.inventory_type, **inventory_args)
                                     else:
diff --git a/pyincore/analyses/waterfacilitydamage/waterfacilitydamage.py b/pyincore/analyses/waterfacilitydamage/waterfacilitydamage.py
index 6a1158769..4c0b7b9cb 100644
--- a/pyincore/analyses/waterfacilitydamage/waterfacilitydamage.py
+++ b/pyincore/analyses/waterfacilitydamage/waterfacilitydamage.py
@@ -14,7 +14,7 @@
 
 from pyincore import BaseAnalysis, HazardService, FragilityService, GeoUtil, \
     AnalysisUtil
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
+from pyincore.models.fragilitycurve import FragilityCurve
 
 
 class WaterFacilityDamage(BaseAnalysis):
@@ -241,7 +241,7 @@ def waterfacilityset_damage_analysis_bulk_input(self, facilities, hazard_type,
             if uncertainty:
                 hazard_std_dev = random.random()
 
-            if isinstance(fragility_set.fragility_curves[0], FragilityCurveRefactored):
+            if isinstance(fragility_set.fragility_curves[0], FragilityCurve):
                 hazard_vals = AnalysisUtil.update_precision_of_lists(hazard_resp[i]["hazardValues"])
                 demand_types = hazard_resp[i]["demands"]
                 demand_units = hazard_resp[i]["units"]
@@ -254,15 +254,15 @@ def waterfacilityset_damage_analysis_bulk_input(self, facilities, hazard_type,
                 if not AnalysisUtil.do_hazard_values_have_errors(hazard_resp[i]["hazardValues"]):
                     facility_args = fragility_set.construct_expression_args_from_inventory(facility)
                     limit_states = \
-                        fragility_set.calculate_limit_state_refactored_w_conversion(hval_dict,
-                                                                                    std_dev=hazard_std_dev,
-                                                                                    inventory_type='water_facility',
-                                                                                    **facility_args)
+                        fragility_set.calculate_limit_state(hval_dict,
+                                                            std_dev=hazard_std_dev,
+                                                            inventory_type='water_facility',
+                                                            **facility_args)
                     # Evaluate liquefaction: if it is not none, then liquefaction is available
                     if liquefaction_resp is not None:
                         fragility_set_liq = fragility_sets_liq[facility["id"]]
 
-                        if isinstance(fragility_set_liq.fragility_curves[0], FragilityCurveRefactored):
+                        if isinstance(fragility_set_liq.fragility_curves[0], FragilityCurve):
                             liq_hazard_vals = AnalysisUtil.update_precision_of_lists(liquefaction_resp[i]["pgdValues"])
                             liq_demand_types = liquefaction_resp[i]["demands"]
                             liq_demand_units = liquefaction_resp[i]["units"]
@@ -275,17 +275,18 @@ def waterfacilityset_damage_analysis_bulk_input(self, facilities, hazard_type,
 
                             facility_liq_args = fragility_set_liq.construct_expression_args_from_inventory(facility)
                             pgd_limit_states = \
-                                fragility_set_liq.calculate_limit_state_refactored_w_conversion(
-                                    hval_dict_liq,std_dev=hazard_std_dev,inventory_type="water_facility",
+                                fragility_set_liq.calculate_limit_state(
+                                    hval_dict_liq, std_dev=hazard_std_dev, inventory_type="water_facility",
                                     **facility_liq_args)
                         else:
-                            raise ValueError("One of the fragilities is in deprecated format. This should not happen. "
-                                         "If you are seeing this please report the issue.")
+                            raise ValueError("One of the fragilities is in deprecated format. "
+                                             "This should not happen If you are seeing this please report the issue.")
 
                         limit_states = AnalysisUtil.adjust_limit_states_for_pgd(limit_states, pgd_limit_states)
 
-                    dmg_intervals = fragility_set.calculate_damage_interval(limit_states, hazard_type=hazard_type,
-                                                                        inventory_type='water_facility')
+                    dmg_intervals = fragility_set.calculate_damage_interval(limit_states,
+                                                                            hazard_type=hazard_type,
+                                                                            inventory_type='water_facility')
             else:
                 raise ValueError("One of the fragilities is in deprecated format. This should not happen. If you are "
                                  "seeing this please report the issue.")
diff --git a/pyincore/baseanalysis.py b/pyincore/baseanalysis.py
index 7f395479c..40b85ffd4 100644
--- a/pyincore/baseanalysis.py
+++ b/pyincore/baseanalysis.py
@@ -224,12 +224,12 @@ def set_result_csv_data(self, result_id, result_data, name, source='file'):
         if not name.endswith(".csv"):
             name = name + ".csv"
 
+        dataset_type = self.output_datasets[result_id]["spec"]["type"]
         if source == 'file':
-            dataset = Dataset.from_csv_data(result_data, name)
+            dataset = Dataset.from_csv_data(result_data, name, dataset_type)
         elif source == 'dataframe':
-            dataset = Dataset.from_dataframe(result_data, name)
+            dataset = Dataset.from_dataframe(result_data, name, dataset_type)
 
-        dataset.data_type = self.output_datasets[result_id]["spec"]["type"]
         self.set_output_dataset(result_id, dataset)
 
     def set_result_json_data(self, result_id, result_data, name, source='file'):
@@ -239,10 +239,10 @@ def set_result_json_data(self, result_id, result_data, name, source='file'):
         if not name.endswith(".json"):
             name = name + ".json"
 
+        dataset_type = self.output_datasets[result_id]["spec"]["type"]
         if source == 'file':
-            dataset = Dataset.from_json_data(result_data, name)
+            dataset = Dataset.from_json_data(result_data, name, dataset_type)
 
-        dataset.data_type = self.output_datasets[result_id]["spec"]["type"]
         self.set_output_dataset(result_id, dataset)
 
     def run_analysis(self):
diff --git a/pyincore/client.py b/pyincore/client.py
index d4a589bf5..e34107f7e 100644
--- a/pyincore/client.py
+++ b/pyincore/client.py
@@ -104,11 +104,10 @@ def return_http_response(http_response):
             http_response.raise_for_status()
             return http_response
         except requests.exceptions.HTTPError:
-            logger.error('HTTPError: The server returned a '
-                         + str(http_response.status_code) + ' failure response code. You can '
-                                                            'find information about HTTP response '
-                                                            'status codes here: '
-                                                            'https://developer.mozilla.org/en-US/docs/Web/HTTP/Status')
+            logger.error('A HTTPError has occurred \n' +
+                         'HTTP Status code: ' + str(http_response.status_code) + '\n' +
+                         'Error Message: ' + http_response.content.decode()
+                         )
             raise
         except requests.exceptions.ConnectionError:
             logger.error("ConnectionError: Failed to establish a connection with the server. "
diff --git a/pyincore/dataset.py b/pyincore/dataset.py
index d84383d71..da51932ab 100644
--- a/pyincore/dataset.py
+++ b/pyincore/dataset.py
@@ -108,27 +108,29 @@ def from_file(cls, file_path, data_type):
         return instance
 
     @classmethod
-    def from_dataframe(cls, dataframe, name):
+    def from_dataframe(cls, dataframe, name, data_type):
         """Get Dataset from Panda's DataFrame.
 
         Args:
             dataframe (obj): Panda's DataFrame.
             name (str): filename.
+            data_type (str): Incore data type, e.g. incore:xxxx or ergo:xxxx
 
         Returns:
             obj: Dataset from file.
 
         """
         dataframe.to_csv(name, index=False)
-        return Dataset.from_file(name, "csv")
+        return Dataset.from_file(name, data_type)
 
     @classmethod
-    def from_csv_data(cls, result_data, name):
+    def from_csv_data(cls, result_data, name, data_type):
         """Get Dataset from CSV data.
 
         Args:
             result_data (obj): Result data and metadata.
             name (str): A CSV filename.
+            data_type (str): Incore data type, e.g. incore:xxxx or ergo:xxxx
 
         Returns:
             obj: Dataset from file.
@@ -140,15 +142,16 @@ def from_csv_data(cls, result_data, name):
                 writer = csv.DictWriter(csv_file, dialect="unix", fieldnames=result_data[0].keys())
                 writer.writeheader()
                 writer.writerows(result_data)
-        return Dataset.from_file(name, "csv")
+        return Dataset.from_file(name, data_type)
 
     @classmethod
-    def from_json_data(cls, result_data, name):
+    def from_json_data(cls, result_data, name, data_type):
         """Get Dataset from JSON data.
 
         Args:
             result_data (obj): Result data and metadata.
             name (str): A JSON filename.
+            data_type (str): Incore data type, e.g. incore:xxxx or ergo:xxxx
 
         Returns:
             obj: Dataset from file.
@@ -158,7 +161,7 @@ def from_json_data(cls, result_data, name):
             with open(name, 'w') as json_file:
                 json_dumps_str = json.dumps(result_data, indent=4)
                 json_file.write(json_dumps_str)
-        return Dataset.from_file(name, "json")
+        return Dataset.from_file(name, data_type)
 
     def cache_files(self, data_service: DataService):
         """Get the set of fragility data, curves.
diff --git a/pyincore/globals.py b/pyincore/globals.py
index 559d6642f..dfa90e3d4 100644
--- a/pyincore/globals.py
+++ b/pyincore/globals.py
@@ -10,7 +10,7 @@
 import os
 import shutil
 
-PACKAGE_VERSION = "1.0.0"
+PACKAGE_VERSION = "1.1.0"
 
 INCORE_API_PROD_URL = "https://incore.ncsa.illinois.edu"
 INCORE_API_DEV_URL = "https://incore-dev.ncsa.illinois.edu"
diff --git a/pyincore/hazardservice.py b/pyincore/hazardservice.py
index 4bd6e4b15..333d42e52 100644
--- a/pyincore/hazardservice.py
+++ b/pyincore/hazardservice.py
@@ -78,8 +78,6 @@ def get_earthquake_hazard_metadata(self, hazard_id: str):
 
         return response
 
-
-
     def get_earthquake_hazard_value_set(self, hazard_id: str, demand_type: str,
                                         demand_unit: str, bbox,
                                         grid_spacing: float):
diff --git a/pyincore/models/conditionalstandardfragilitycurve.py b/pyincore/models/conditionalstandardfragilitycurve.py
deleted file mode 100644
index f5a99ba15..000000000
--- a/pyincore/models/conditionalstandardfragilitycurve.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright (c) 2019 University of Illinois and others. All rights reserved.
-#
-# This program and the accompanying materials are made available under the
-# terms of the Mozilla Public License v2.0 which accompanies this distribution,
-# and is available at https://www.mozilla.org/en-US/MPL/2.0/
-
-
-import math
-
-from scipy.stats import norm
-
-from pyincore.models.fragilitycurve import FragilityCurve
-
-
-class ConditionalStandardFragilityCurve(FragilityCurve):
-    """
-    A class to represent conditional standard fragility curve.
-    """
-
-    def __init__(self, curve_parameters):
-        self.alpha = curve_parameters['alpha']
-        self.beta = curve_parameters['beta']
-        self.alpha_type = curve_parameters['alphaType']
-        self.curve_type = curve_parameters['curveType']
-        self.rules = curve_parameters['rules']
-
-        super(ConditionalStandardFragilityCurve, self).__init__(curve_parameters)
-
-    def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
-        """Computes limit state probabilities.
-
-        Args:
-            hazard (float): A hazard value to compute probability for.
-            period (float): A building period default to 0.
-            std_dev (float): A standard deviation.
-            **kwargs: Keyword arguments.
-
-        Returns:
-            float: A limit state probability.
-
-        """
-        probability = float(0.0)
-        if hazard > 0.0:
-            index = ConditionalStandardFragilityCurve._fragility_curve_rules_match(self.rules, hazard)
-            if index is not None:
-                alpha = float(self.alpha[index])
-                beta = math.sqrt(math.pow(self.beta[index], 2) + math.pow(std_dev, 2))
-
-                if self.alpha_type == 'median':
-                    sp = (math.log(hazard) - math.log(alpha)) / beta
-                    probability = norm.cdf(sp)
-                elif self.alpha_type == "lambda":
-                    x = (math.log(hazard) - alpha) / beta
-                    probability = norm.cdf(x)
-            else:
-                raise ValueError("No matching rule has been found in this conditonal standard fragility curve. "
-                                 "Please verify it's the right curve to use.")
-
-        return probability
-
-    @staticmethod
-    def _fragility_curve_rules_match(rules, value):
-        """Given value and rules; decide which index to use.
-
-        Args:
-            rules (dict): Index: ["rule1", "rule2"...].
-            value (float): A value to be evaluated against.
-
-        Returns:
-            int: index (which used to decide which pair of alpha and beta to use)
-        """
-
-        # add more operators if needed
-        known_operators = {
-            "EQ": "==",
-            "EQUALS": "==",
-            "NEQUALS": "!=",
-            "GT": ">",
-            "GE": ">=",
-            "LT": "<",
-            "LE": "<=",
-        }
-
-        # if rules is [[]] meaning it matches without any condition
-        for index, rule in rules.items():
-            # TODO: for now assuming only one rule; in the future need to consider if it's a range
-            # TODO: eg. demand GT 3, demand LT 4
-            # TODO: for now default it's always using the hazard value as the rule_key
-
-            # the format of a rule is always: rule_key + rule_operator + rule_value
-            elements = rule[0].split(" ", 2)
-
-            rule_operator = elements[1]
-            if rule_operator not in known_operators.keys():
-                raise ValueError(rule_operator + " Unknown. Cannot parse the rules of this mapping!")
-
-            rule_value = elements[2]
-
-            matched = eval(str(value) + known_operators[rule_operator] + rule_value)
-            if matched:
-                return int(index)
-
-        return None
diff --git a/pyincore/models/customexpressionfragilitycurve.py b/pyincore/models/customexpressionfragilitycurve.py
deleted file mode 100644
index 8c7fb2806..000000000
--- a/pyincore/models/customexpressionfragilitycurve.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (c) 2019 University of Illinois and others. All rights reserved.
-#
-# This program and the accompanying materials are made available under the
-# terms of the Mozilla Public License v2.0 which accompanies this distribution,
-# and is available at https://www.mozilla.org/en-US/MPL/2.0/
-
-
-from pyincore import Parser
-
-from pyincore.models.fragilitycurve import FragilityCurve
-
-
-class CustomExpressionFragilityCurve(FragilityCurve):
-    """A class to represent custom expression fragility curve"""
-
-    def __init__(self, curve_parameters):
-        self.expression = curve_parameters['expression']
-
-        super(CustomExpressionFragilityCurve, self).__init__(curve_parameters)
-
-    def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
-        """
-
-        Args:
-            hazard (float): A hazard value.
-            period (float): A period value.
-            std_dev (float): A standard deviation.
-            **kwargs: Keyword arguments.
-
-        Raises:
-            ValueError: If this limit state calculation method is not supported.
-
-        """
-        raise ValueError("Custom Expression Fragility Curve does not support this limit state calculation method. "
-                         "Please use compute_custom_limit_state_probability(variables) instead!")
-
-    def compute_custom_limit_state_probability(self, variables: dict):
-        """Computes custom limit state probabilities.
-
-        Args:
-            variables (dict): Variables to set.
-
-        Returns:
-            float: A limit state probability.
-
-        """
-        expression = self.expression
-        parser = Parser()
-        probability = parser.parse(expression).evaluate(variables)
-
-        return probability
diff --git a/pyincore/models/fragilitycurve.py b/pyincore/models/fragilitycurve.py
index a5dea06c2..3045d399d 100644
--- a/pyincore/models/fragilitycurve.py
+++ b/pyincore/models/fragilitycurve.py
@@ -1,68 +1,131 @@
-# Copyright (c) 2019 University of Illinois and others. All rights reserved.
+# Copyright (c) 2020 University of Illinois and others. All rights reserved.
 #
 # This program and the accompanying materials are made available under the
 # terms of the Mozilla Public License v2.0 which accompanies this distribution,
 # and is available at https://www.mozilla.org/en-US/MPL/2.0/
+import json
+import math
+from abc import ABC
 
-from deprecated.sphinx import deprecated
+from pyincore import globals as pyglobals
+from pyincore.utils import evaluateexpression
 
+logger = pyglobals.LOGGER
 
-class FragilityCurve:
-    """Abstract class for fragility curves."""
+
+class FragilityCurve(ABC):
+    """A class to represent conditional standard fragility curve."""
 
     def __init__(self, curve_parameters):
+        self.rules = curve_parameters['rules']
+        self.return_type = curve_parameters['returnType']
+
+        for rule in self.rules:
+            rule["expression"] = rule["expression"].replace("^", "**")
         self.description = curve_parameters['description']
 
-    def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
+    def calculate_limit_state_probability(self, hazard_values: dict, fragility_curve_parameters: dict, **kwargs):
         """Computes limit state probabilities.
 
         Args:
-            hazard (float): A hazard value.
-            period (float): A period value.
-            std_dev (float): A standard deviation.
+            hazard_values (dict): Hazard values.
+            fragility_curve_parameters (dict): Fragility curve parameters.
             **kwargs: Keyword arguments.
 
         Returns:
-            float: Limit state probability.
+            float: A limit state probability.
 
         """
-        probability = float(0.0)
-        return probability
-
-    @deprecated(version="0.9.7", reason="This method is already incorporated in refactored fragility curves and will "
-                                        "be deprecated")
-    def adjust_fragility_for_liquefaction(self, liquefaction: str):
-        """Adjusts fragility curve object by input parameter liquefaction.
+        parameters = {}
+        mapped_demand_types = {}
+        # For all curve parameters:
+        # 1. Figure out if parameter name needs to be mapped (i.e. the name contains forbidden characters)
+        # 2. Fetch all parameters listed in the curve from kwargs and if there are not in kwargs, use default values
+        # from the curve.
+        for parameter in fragility_curve_parameters:
+            # if default exists, use default
+            if "expression" in parameter and parameter["expression"] is not None:
+                parameters[parameter["name"]] = evaluateexpression.evaluate(parameter["expression"], parameters)
+            else:
+                parameters[parameter["name"]] = None
+
+            # e.g. map point_two_sec_sa to its full name (0.2 Sec Sa)
+            if "fullName" in parameter and parameter["fullName"] is not None:
+                mapped_demand_types[parameter["fullName"]] = parameter["name"]
+
+            # else overwrite with real values; make sure it handles case sensitivity
+            for kwargs_key, kwargs_value in kwargs.items():
+                if "fullName" in parameter and parameter["fullName"] is not None:
+                    if parameter["fullName"].lower() == kwargs_key.lower():
+                        parameters[parameter["name"]] = kwargs_value
+                elif parameter["name"].lower() == kwargs_key.lower():
+                    parameters[parameter["name"]] = kwargs_value
+
+        probability = 0.0
+
+        # use hazard values if present
+        # consider case insensitive situation
+        for key, value in hazard_values.items():
+            if key in mapped_demand_types:
+                key = mapped_demand_types[key]
+            for parameter_key in parameters.keys():
+                if parameter_key.lower() == key.lower():
+                    if value is not None:
+                        parameters[parameter_key] = value
+                    else:
+                        # returning 0 even if a single demand value is None, assumes there is no hazard exposure. TBD
+                        return probability
+
+        for rule in self.rules:
+            if "condition" not in rule or rule["condition"] is None:
+                probability = evaluateexpression.evaluate(rule["expression"], parameters)
+            else:
+                conditions_met = []
+                for condition in rule["condition"]:
+                    if evaluateexpression.evaluate(condition, parameters):
+                        conditions_met.append(True)
+                    else:
+                        conditions_met.append(False)
+                        break
+                if all(conditions_met):
+                    probability = evaluateexpression.evaluate(rule["expression"], parameters)
+                    break
+
+        if math.isnan(probability):
+            error_msg = "Unable to calculate limit state."
+            if self.rules:
+                error_msg += " Evaluation failed for expression: \n" + json.dumps(self.rules) + "\n"
+                error_msg += "Provided Inputs: \n" + json.dumps(hazard_values) + "\n" + json.dumps(kwargs)
+
+            raise ValueError(error_msg)
 
-        Args:
-            liquefaction (str): Liquefaction type.
-
-        """
-        raise NotImplementedError("This function is currently only applied to Standard Fragility Curve, "
-                                  "and Period Standard Fragility Curve")
+        return probability
 
     def get_building_period(self, num_stories):
         """Get building period from the fragility curve.
-
         Args:
             num_stories (int): Number of building stories.
-
         Returns:
             float: Building period.
-
         """
         period = 0.0
         return period
 
-    def compute_custom_limit_state_probability(self, variables: dict):
-        """Computes custom limit state probabilities.
-
-        Args:
-            variables (dict): Variables to set.
-
-        Returns:
-            float: A limit state probability.
-
-        """
-        probability = float(0.0)
-        return probability
+    # def get_building_period(self, fragility_curve_parameters, **kwargs):
+    #     period = 0.0
+    #     num_stories = 1.0
+    #     for parameter in fragility_curve_parameters:
+    #         # if default exists, use default
+    #         if parameter["name"] == "num_stories" and "expression" in parameter and parameter["expression"] is not None:
+    #             num_stories = evaluateexpression.evaluate(parameter["expression"])
+    #
+    #         # if exist in building inventory
+    #         for kwargs_key, kwargs_value in kwargs.items():
+    #             if kwargs_key.lower() == "num_stories" and kwargs_value is not None and kwargs_value > 0:
+    #                 num_stories = kwargs_value
+    #
+    #         # calculate period
+    #         if parameter["name"] == "period" and "expression" in parameter and parameter["expression"] is not None:
+    #             period = evaluateexpression.evaluate(parameter["expression"], {"num_stories": num_stories})
+    #
+    #     return period
diff --git a/pyincore/models/fragilitycurverefactored.py b/pyincore/models/fragilitycurverefactored.py
deleted file mode 100644
index de53f7a9f..000000000
--- a/pyincore/models/fragilitycurverefactored.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright (c) 2020 University of Illinois and others. All rights reserved.
-#
-# This program and the accompanying materials are made available under the
-# terms of the Mozilla Public License v2.0 which accompanies this distribution,
-# and is available at https://www.mozilla.org/en-US/MPL/2.0/
-import json
-import math
-from abc import ABC
-
-from pyincore import globals as pyglobals
-from pyincore.utils import evaluateexpression
-from pyincore.models.fragilitycurve import FragilityCurve
-
-logger = pyglobals.LOGGER
-
-
-class FragilityCurveRefactored(FragilityCurve, ABC):
-    """A class to represent conditional standard fragility curve."""
-
-    def __init__(self, curve_parameters):
-        self.rules = curve_parameters['rules']
-        self.return_type = curve_parameters['returnType']
-
-        for rule in self.rules:
-            rule["expression"] = rule["expression"].replace("^", "**")
-
-        super(FragilityCurveRefactored, self).__init__(curve_parameters)
-
-    def calculate_limit_state_probability(self, hazard_values: dict, fragility_curve_parameters: dict, **kwargs):
-        """Computes limit state probabilities.
-
-        Args:
-            hazard_values (dict): Hazard values.
-            fragility_curve_parameters (dict): Fragility curve parameters.
-            **kwargs: Keyword arguments.
-
-        Returns:
-            float: A limit state probability.
-
-        """
-        parameters = {}
-        mapped_demand_types = {}
-        # For all curve parameters:
-        # 1. Figure out if parameter name needs to be mapped (i.e. the name contains forbidden characters)
-        # 2. Fetch all parameters listed in the curve from kwargs and if there are not in kwargs, use default values
-        # from the curve.
-        for parameter in fragility_curve_parameters:
-            # if default exists, use default
-            if "expression" in parameter and parameter["expression"] is not None:
-                parameters[parameter["name"]] = evaluateexpression.evaluate(parameter["expression"], parameters)
-            else:
-                parameters[parameter["name"]] = None
-
-            # e.g. map point_two_sec_sa to its full name (0.2 Sec Sa)
-            if "fullName" in parameter and parameter["fullName"] is not None:
-                mapped_demand_types[parameter["fullName"]] = parameter["name"]
-
-            # else overwrite with real values; make sure it handles case sensitivity
-            for kwargs_key, kwargs_value in kwargs.items():
-                if "fullName" in parameter and parameter["fullName"] is not None:
-                    if parameter["fullName"].lower() == kwargs_key.lower():
-                        parameters[parameter["name"]] = kwargs_value
-                elif parameter["name"].lower() == kwargs_key.lower():
-                    parameters[parameter["name"]] = kwargs_value
-
-        probability = 0.0
-
-        # use hazard values if present
-        # consider case insensitive situation
-        for key, value in hazard_values.items():
-            if key in mapped_demand_types:
-                key = mapped_demand_types[key]
-            for parameter_key in parameters.keys():
-                if parameter_key.lower() == key.lower():
-                    if value is not None:
-                        parameters[parameter_key] = value
-                    else:
-                        # returning 0 even if a single demand value is None, assumes there is no hazard exposure. TBD
-                        return probability
-
-        for rule in self.rules:
-            if "condition" not in rule or rule["condition"] is None:
-                probability = evaluateexpression.evaluate(rule["expression"], parameters)
-            else:
-                conditions_met = []
-                for condition in rule["condition"]:
-                    if evaluateexpression.evaluate(condition, parameters):
-                        conditions_met.append(True)
-                    else:
-                        conditions_met.append(False)
-                        break
-                if all(conditions_met):
-                    probability = evaluateexpression.evaluate(rule["expression"], parameters)
-                    break
-
-        if math.isnan(probability):
-            error_msg = "Unable to calculate limit state."
-            if self.rules:
-                error_msg += " Evaluation failed for expression: \n" + json.dumps(self.rules) + "\n"
-                error_msg += "Provided Inputs: \n" + json.dumps(hazard_values) + "\n" + json.dumps(kwargs)
-
-            raise ValueError(error_msg)
-
-        return probability
-
-    # def get_building_period(self, fragility_curve_parameters, **kwargs):
-    #     period = 0.0
-    #     num_stories = 1.0
-    #     for parameter in fragility_curve_parameters:
-    #         # if default exists, use default
-    #         if parameter["name"] == "num_stories" and "expression" in parameter and parameter["expression"] is not None:
-    #             num_stories = evaluateexpression.evaluate(parameter["expression"])
-    #
-    #         # if exist in building inventory
-    #         for kwargs_key, kwargs_value in kwargs.items():
-    #             if kwargs_key.lower() == "num_stories" and kwargs_value is not None and kwargs_value > 0:
-    #                 num_stories = kwargs_value
-    #
-    #         # calculate period
-    #         if parameter["name"] == "period" and "expression" in parameter and parameter["expression"] is not None:
-    #             period = evaluateexpression.evaluate(parameter["expression"], {"num_stories": num_stories})
-    #
-    #     return period
diff --git a/pyincore/models/fragilitycurveset.py b/pyincore/models/fragilitycurveset.py
index 1b26641ca..0ed88d5ba 100644
--- a/pyincore/models/fragilitycurveset.py
+++ b/pyincore/models/fragilitycurveset.py
@@ -4,19 +4,9 @@
 # terms of the Mozilla Public License v2.0 which accompanies this distribution,
 # and is available at https://www.mozilla.org/en-US/MPL/2.0/
 
-import collections
 import json
 
-from deprecated.sphinx import deprecated
-
-from pyincore.models.customexpressionfragilitycurve import CustomExpressionFragilityCurve
 from pyincore.models.fragilitycurve import FragilityCurve
-from pyincore.models.periodbuildingfragilitycurve import PeriodBuildingFragilityCurve
-from pyincore.models.periodstandardfragilitycurve import PeriodStandardFragilityCurve
-from pyincore.models.standardfragilitycurve import StandardFragilityCurve
-from pyincore.models.conditionalstandardfragilitycurve import ConditionalStandardFragilityCurve
-from pyincore.models.parametricfragilitycurve import ParametricFragilityCurve
-from pyincore.models.fragilitycurverefactored import FragilityCurveRefactored
 from pyincore.utils.analysisutil import AnalysisUtil
 
 
@@ -52,29 +42,7 @@ def __init__(self, metadata):
 
         if 'fragilityCurves' in metadata.keys():
             for fragility_curve in metadata["fragilityCurves"]:
-
-                # if it's already an df3curve object, directly put it in the list:
-                if isinstance(fragility_curve, FragilityCurve):
-                    self.fragility_curves.append(fragility_curve)
-                # based on what type of fragility_curve it is, instantiate different fragility curve object
-                else:
-                    if fragility_curve['className'] == 'StandardFragilityCurve':
-                        self.fragility_curves.append(StandardFragilityCurve(fragility_curve))
-                    elif fragility_curve['className'] == 'PeriodBuildingFragilityCurve':
-                        self.fragility_curves.append(PeriodBuildingFragilityCurve(fragility_curve))
-                    elif fragility_curve['className'] == 'PeriodStandardFragilityCurve':
-                        self.fragility_curves.append(PeriodStandardFragilityCurve(fragility_curve))
-                    elif fragility_curve['className'] == 'CustomExpressionFragilityCurve':
-                        self.fragility_curves.append(CustomExpressionFragilityCurve(fragility_curve))
-                    elif fragility_curve['className'] == 'ConditionalStandardFragilityCurve':
-                        self.fragility_curves.append(ConditionalStandardFragilityCurve(fragility_curve))
-                    elif fragility_curve['className'] == 'ParametricFragilityCurve':
-                        self.fragility_curves.append(ParametricFragilityCurve(fragility_curve))
-                    elif fragility_curve['className'] == 'FragilityCurveRefactored':
-                        self.fragility_curves.append(FragilityCurveRefactored(fragility_curve))
-                    else:
-                        # TODO make a custom fragility curve class that accept whatever
-                        self.fragility_curves.append(fragility_curve)
+                self.fragility_curves.append(FragilityCurve(fragility_curve))
         elif 'repairCurves' in metadata.keys():
             self.repairCurves = metadata['repairCurves']
         elif 'restorationCurves' in metadata.keys():
@@ -111,75 +79,9 @@ def from_json_file(cls, file_path):
 
         return instance
 
-    @deprecated(version="0.9.0", reason="use calculate_limit_state_w_conversion instead")
-    def calculate_limit_state(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
-        """Computes limit state probabilities.
-
-        Args:
-            hazard (float): A hazard value to compute probability for.
-            period (float): A period of the structure, if applicable.
-            std_dev (float): A standard deviation.
-            **kwargs: Keyword arguments.
-
-        Returns:
-            OrderedDict: Limit state probabilities.
-
-        """
-        output = collections.OrderedDict()
-        index = 0
-
-        if len(self.fragility_curves) == 1:
-            limit_state = ['failure']
-        elif len(self.fragility_curves) == 3:
-            limit_state = ['immocc', 'lifesfty', 'collprev']
-        elif len(self.fragility_curves) == 4:
-            limit_state = ['ls-slight', 'ls-moderat', 'ls-extensi', 'ls-complet']
-        else:
-            raise ValueError("We can only handle fragility curves with 1, 3 or 4 limit states!")
-
-        for fragility_curve in self.fragility_curves:
-            probability = fragility_curve.calculate_limit_state_probability(hazard, period, std_dev, **kwargs)
-            output[limit_state[index]] = AnalysisUtil.update_precision(probability)  # round to default digits
-            index += 1
-
-        return output
-
-    @deprecated(version="0.9.0", reason="use calculate_limit_state_refactored_w_conversion instead")
-    def calculate_limit_state_refactored(self, hazard_values: dict = {}, **kwargs):
-        """WIP computation of limit state probabilities accounting for custom expressions.
-
-        Args:
-            hazard_values (dict): A dictionary with hazard values to compute probability.
-            **kwargs: Keyword arguments.
-
-        Returns:
-            OrderedDict: Limit state probabilities.
-
-        """
-        output = collections.OrderedDict()
-        index = 0
-
-        if len(self.fragility_curves) == 1:
-            limit_state = ['failure']
-        elif len(self.fragility_curves) == 3:
-            limit_state = ['immocc', 'lifesfty', 'collprev']
-        elif len(self.fragility_curves) == 4:
-            limit_state = ['ls-slight', 'ls-moderat', 'ls-extensi', 'ls-complet']
-        else:
-            raise ValueError("We can only handle fragility curves with 1, 3 or 4 limit states!")
-
-        for fragility_curve in self.fragility_curves:
-            probability = fragility_curve.calculate_limit_state_probability(hazard_values,
-                                                                            self.fragility_curve_parameters,
-                                                                            **kwargs)
-            output[limit_state[index]] = AnalysisUtil.update_precision(probability)  # round to default digits
-            index += 1
-
-        return output
-
-    def calculate_limit_state_refactored_w_conversion(self, hazard_values: dict = {},
-                                                      inventory_type: str = "building",
-                                                      **kwargs):
+    def calculate_limit_state(self, hazard_values: dict = {},
+                              inventory_type: str = "building",
+                              **kwargs):
         """WIP computation of limit state probabilities accounting for custom expressions.
 
         Args:
@@ -207,36 +109,6 @@ def calculate_limit_state_refactored_w_conversion(self, hazard_values: dict = {}
 
         return output
 
-    @deprecated(version="0.9.0", reason="use calculate_custom_limit_state_w_conversion instead")
-    def calculate_custom_limit_state(self, variables: dict):
-        """Computes limit state probabilities.
-
-        Args:
-            variables (dict): A dictionary of variables.
-
-        Returns:
-            OrderedDict: Limit state probabilities for custom expression fragilities.
-
-        """
-        output = collections.OrderedDict()
-        index = 0
-
-        if len(self.fragility_curves) == 1:
-            limit_state = ['failure']
-        elif len(self.fragility_curves) == 3:
-            limit_state = ['immocc', 'lifesfty', 'collprev']
-        elif len(self.fragility_curves) == 4:
-            limit_state = ['ls-slight', 'ls-moderat', 'ls-extensi', 'ls-complet']
-        else:
-            raise ValueError("We can only handle fragility curves with 1, 3 or 4 limit states!")
-
-        for fragility_curve in self.fragility_curves:
-            probability = fragility_curve.compute_custom_limit_state_probability(variables)
-            output[limit_state[index]] = AnalysisUtil.update_precision(probability)  # round to default digits
-            index += 1
-
-        return output
-
     def calculate_limit_state_w_conversion(self, hazard, period: float = 0.0, std_dev: float = 0.0,
                                            inventory_type: str = "building", **kwargs):
         """Computes limit state probabilities.
diff --git a/pyincore/models/parametricfragilitycurve.py b/pyincore/models/parametricfragilitycurve.py
deleted file mode 100644
index cee25cd9c..000000000
--- a/pyincore/models/parametricfragilitycurve.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright (c) 2019 University of Illinois and others. All rights reserved.
-#
-# This program and the accompanying materials are made available under the
-# terms of the Mozilla Public License v2.0 which accompanies this distribution,
-# and is available at https://www.mozilla.org/en-US/MPL/2.0/
-
-
-import math
-
-from pyincore.models.fragilitycurve import FragilityCurve
-
-
-class ParametricFragilityCurve(FragilityCurve):
-    """
-    class to represent parametric fragility curve
-    """
-
-    def __init__(self, curve_parameters):
-        # TODO: not sure if i need to define a class of parameters with "name", "unit", "coefficient" and
-        #  "interceptTermDefault" as fixed fields; is it going to be over complicated?
-        self.parameters = curve_parameters['parameters']
-        self.curve_type = curve_parameters['curveType']
-
-        super(ParametricFragilityCurve, self).__init__(curve_parameters)
-
-    def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
-        """
-
-        Args:
-            hazard (float): A hazard value to compute probability for.
-            period (float): A building period default to 0.
-            std_dev (float): A standard deviation.
-            **kwargs: Keyword arguments.
-
-        Returns:
-            float: A probability, pf (DS) = exp(X*theta')/(1+exp(X*theta'));
-
-        Example: pf(DS) = EXP(1 * A0 + log(PGA) * A1 + A2*X2 + ...) / (1 + EXP(1 *A0 + log(PGA) * A1 + ...))
-
-        """
-        probability = float(0.0)
-
-        if self.curve_type.lower() == "logit":
-            cumulate_term = 0  # X*theta'
-
-            for parameter_set in self.parameters:
-                name = parameter_set["name"].lower()
-                coefficient = parameter_set["coefficient"]
-                default = parameter_set["interceptTermDefault"]
-                if name == "demand":
-                    cumulate_term += math.log(hazard) * coefficient
-                else:
-                    if name not in kwargs.keys():
-                        cumulate_term += default * coefficient
-                    else:
-                        cumulate_term += kwargs[name] * coefficient
-
-            probability = math.exp(cumulate_term) / (1 + math.exp(cumulate_term))
-        else:
-            raise ValueError("Other parametric functions than Logit has not been implemented yet!")
-
-        return probability
diff --git a/pyincore/models/periodbuildingfragilitycurve.py b/pyincore/models/periodbuildingfragilitycurve.py
deleted file mode 100644
index 679e4fdc9..000000000
--- a/pyincore/models/periodbuildingfragilitycurve.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright (c) 2019 University of Illinois and others. All rights reserved.
-#
-# This program and the accompanying materials are made available under the
-# terms of the Mozilla Public License v2.0 which accompanies this distribution,
-# and is available at https://www.mozilla.org/en-US/MPL/2.0/
-
-
-import math
-
-from scipy.stats import norm
-
-from pyincore.models.fragilitycurve import FragilityCurve
-from deprecated.sphinx import deprecated
-
-
-class PeriodBuildingFragilityCurve(FragilityCurve):
-    """A class to represent period building fragility curve"""
-
-    def __init__(self, curve_parameters):
-        self.period_eqn_type = curve_parameters['periodEqnType']
-        self.period_param1 = curve_parameters['periodParam1']
-        self.period_param2 = curve_parameters['periodParam2']
-        self.period_param0 = curve_parameters['periodParam0']
-        self.fs_param0 = curve_parameters['fsParam0']
-        self.fs_param1 = curve_parameters['fsParam1']
-        self.fs_param2 = curve_parameters['fsParam2']
-        self.fs_param3 = curve_parameters['fsParam3']
-        self.fs_param4 = curve_parameters['fsParam4']
-        self.fs_param5 = curve_parameters['fsParam5']
-
-        super(PeriodBuildingFragilityCurve, self).__init__(curve_parameters)
-
-    def get_building_period(self, num_stories):
-        """Get building period from the fragility curve.
-
-        Args:
-            num_stories (int): Number of building stories.
-
-        Returns:
-            float: Building period.
-
-        """
-        period = 0.0
-        period_equation_type = self.period_eqn_type
-        if period_equation_type == 1:
-            period = self.period_param0
-        elif period_equation_type == 2:
-            period = self.period_param0 * num_stories
-        elif period_equation_type == 3:
-            period = self.period_param1 * math.pow(
-                self.period_param0 * num_stories,
-                self.period_param2)
-
-        return period
-
-    def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
-        """Computes limit state probabilities.
-
-            Args:
-                hazard (float): A hazard value to compute probability for.
-                period (float): A building period default to 0.
-                std_dev (float): A standard deviation.
-                **kwargs: Keyword arguments.
-
-            Returns:
-                float: A limit state probability.
-
-        """
-
-        probability = float(0.0)
-
-        if hazard > 0.0:
-            # If no period is provided, assumption is 0 since the user should check their
-            # data for missing parameters (e.g. number of building stories).
-            probability = PeriodBuildingFragilityCurve.compute_period_building_fragility_value(
-                hazard, period, self.fs_param0, self.fs_param1, self.fs_param2, self.fs_param3, self.fs_param4,
-                self.fs_param5)
-
-        return probability
-
-    @staticmethod
-    def compute_period_building_fragility_value(hazard, period, a11_param, a12_param, a13_param, a14_param,
-                                                a21_param, a22_param):
-
-        # Assumption from Ergo BuildingLowPeriodSolver
-        cutoff_period = 0.87
-
-        probability = 0.0
-        if period < cutoff_period:
-            multiplier = cutoff_period - period
-            surface_eq = (math.log(
-                hazard) - (cutoff_period * a12_param + a11_param)) / (
-                                 a13_param + a14_param * cutoff_period)
-            probability = norm.cdf(surface_eq + multiplier * (
-                    math.log(hazard) - a21_param) / a22_param)
-        else:
-            probability = norm.cdf(
-                (math.log(hazard) - (a11_param + a12_param * period)) / (
-                        a13_param + a14_param * period))
-
-        return probability
diff --git a/pyincore/models/periodstandardfragilitycurve.py b/pyincore/models/periodstandardfragilitycurve.py
deleted file mode 100644
index f78253662..000000000
--- a/pyincore/models/periodstandardfragilitycurve.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright (c) 2019 University of Illinois and others. All rights reserved.
-#
-# This program and the accompanying materials are made available under the
-# terms of the Mozilla Public License v2.0 which accompanies this distribution,
-# and is available at https://www.mozilla.org/en-US/MPL/2.0/
-
-
-import math
-
-from scipy.stats import norm
-
-from pyincore.models.fragilitycurve import FragilityCurve
-from deprecated.sphinx import deprecated
-
-
-class PeriodStandardFragilityCurve(FragilityCurve):
-    """A class to represent period standard fragility curve"""
-
-    def __init__(self, curve_parameters):
-        self.alpha = curve_parameters['alpha']
-        self.beta = curve_parameters['beta']
-        self.alpha_type = curve_parameters['alphaType']
-        self.curve_type = curve_parameters['curveType']
-        self.period_param2 = curve_parameters['periodParam2']
-        self.period_param1 = curve_parameters['periodParam1']
-        self.period_param0 = curve_parameters['periodParam0']
-        self.period_eqn_type = curve_parameters['periodEqnType']
-
-        super(PeriodStandardFragilityCurve, self).__init__(curve_parameters)
-
-    def get_building_period(self, num_stories):
-        """Get building period from the fragility curve.
-
-        Args:
-            num_stories (int): Number of building stories.
-
-        Returns:
-            float: Building period.
-
-        """
-        period = 0.0
-        period_equation_type = self.period_eqn_type
-        if period_equation_type == 1:
-            period = self.period_param0
-        elif period_equation_type == 2:
-            period = self.period_param0 * num_stories
-        elif period_equation_type == 3:
-            period = self.period_param1 * math.pow(
-                self.period_param0 * num_stories,
-                self.period_param2)
-
-        return period
-
-    def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
-        """Computes limit state probabilities.
-
-            Args:
-                hazard (float): A hazard value to compute probability for.
-                period (float): A building period default to 0.
-                std_dev (float): A standard deviation.
-                **kwargs: Keyword arguments.
-
-            Returns:
-                float: A limit state probability.
-
-        """
-        probability = float(0.0)
-
-        if hazard > 0.0:
-            alpha = float(self.alpha)
-            beta = math.sqrt(math.pow(self.beta, 2) + math.pow(std_dev, 2))
-
-            if self.alpha_type == 'median':
-                sp = (math.log(hazard) - math.log(alpha)) / beta
-                probability = norm.cdf(sp)
-            elif self.alpha_type == "lambda":
-                x = (math.log(hazard) - alpha) / beta
-                probability = norm.cdf(x)
-
-        return probability
-
-    @deprecated(version="0.9.7", reason="This method is already incorporated in refactored fragility curves and will "
-                                        "be deprecated")
-    def adjust_fragility_for_liquefaction(self, liquefaction: str):
-        """Adjusts fragility curve object by input parameter liquefaction.
-
-        Args:
-            liquefaction (str): Liquefaction type.
-
-        """
-        liquefaction_unified = str(liquefaction).upper()
-        if liquefaction_unified == "U":
-            multiplier = 0.85
-        elif liquefaction_unified == "Y":
-            multiplier = 0.65
-        else:
-            multiplier = 1.0
-
-        self.alpha = self.alpha * multiplier
-        self.beta = self.beta
diff --git a/pyincore/models/repaircurveset.py b/pyincore/models/repaircurveset.py
index c5f11ff2b..0e88a5841 100644
--- a/pyincore/models/repaircurveset.py
+++ b/pyincore/models/repaircurveset.py
@@ -5,7 +5,7 @@
 # and is available at https://www.mozilla.org/en-US/MPL/2.0/
 
 import json
-from pyincore.models.standardfragilitycurve import StandardFragilityCurve
+from pyincore.models.standardrepaircurve import StandardRepairCurve
 
 
 class RepairCurveSet:
@@ -35,9 +35,7 @@ def __init__(self, metadata):
         if 'repairCurves' in metadata.keys():
             for repair_curve in metadata["repairCurves"]:
                 if repair_curve['className'] == 'StandardRepairCurve':
-                    # Using StandardFragilityCurve for now instead of creating a StandardRepairCurve because it will be
-                    # deprecated and repair curves will be using the expression based format the in near future.
-                    self.repair_curves.append(StandardFragilityCurve(repair_curve))
+                    self.repair_curves.append(StandardRepairCurve(repair_curve))
                 else:
                     # TODO make a custom repair curve class that accept other formats
                     self.repair_curves.append(repair_curve)
diff --git a/pyincore/models/standardfragilitycurve.py b/pyincore/models/standardrepaircurve.py
similarity index 61%
rename from pyincore/models/standardfragilitycurve.py
rename to pyincore/models/standardrepaircurve.py
index 05acb66cb..98a52e612 100644
--- a/pyincore/models/standardfragilitycurve.py
+++ b/pyincore/models/standardrepaircurve.py
@@ -9,21 +9,18 @@
 
 from scipy.stats import norm
 
-from pyincore.models.fragilitycurve import FragilityCurve
-from deprecated.sphinx import deprecated
 
-
-class StandardFragilityCurve(FragilityCurve):
-    """A class to represent standard fragility curve"""
+# TODO: This will be deprecated when repair curves are migrated to expression based format.
+class StandardRepairCurve:
+    """A class to represent standard Repair curve."""
 
     def __init__(self, curve_parameters):
+        self.description = curve_parameters['description']
         self.alpha = curve_parameters['alpha']
         self.beta = curve_parameters['beta']
         self.alpha_type = curve_parameters['alphaType']
         self.curve_type = curve_parameters['curveType']
 
-        super(StandardFragilityCurve, self).__init__(curve_parameters)
-
     def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev: float = 0.0, **kwargs):
         """Computes limit state probabilities.
 
@@ -52,22 +49,3 @@ def calculate_limit_state_probability(self, hazard, period: float = 0.0, std_dev
 
         return probability
 
-    @deprecated(version="0.9.7", reason="This method is already incorporated in refactored fragility curves and will "
-                                        "be deprecated")
-    def adjust_fragility_for_liquefaction(self, liquefaction: str):
-        """Adjusts fragility curve object by input parameter liquefaction.
-
-        Args:
-            liquefaction (str): Liquefaction type.
-
-        """
-        liquefaction_unified = str(liquefaction).upper()
-        if liquefaction_unified == "U":
-            multiplier = 0.85
-        elif liquefaction_unified == "Y":
-            multiplier = 0.65
-        else:
-            multiplier = 1.0
-
-        self.alpha = self.alpha * multiplier
-        self.beta = self.beta
diff --git a/pyincore/utils/analysisutil.py b/pyincore/utils/analysisutil.py
index 364351391..d6c60d2bd 100644
--- a/pyincore/utils/analysisutil.py
+++ b/pyincore/utils/analysisutil.py
@@ -295,20 +295,23 @@ def create_gdocstr_from_spec(specs):
         rets = ""
 
         for dataset in specs['input_datasets']:
-            isOpt = ""
+            is_opt = ""
             if not dataset['required']:
-                isOpt = ", " + "optional"
+                is_opt = ", " + "optional"
 
-            args = args + dataset['id'] + "(str" + isOpt + ") : " + dataset['description'] + ". " \
-                   + AnalysisUtil.get_custom_types_str(dataset['type']) + "\n\t"
+            args = \
+                args + dataset['id'] + "(str" + is_opt + ") : " \
+                + dataset['description'] + ". " \
+                + AnalysisUtil.get_custom_types_str(dataset['type']) + "\n\t"
 
         for param in specs['input_parameters']:
-            isOpt = ""
+            is_opt = ""
             if not param['required']:
-                isOpt = ", " + "optional"
+                is_opt = ", " + "optional"
 
-            args = args + param['id'] + "(" \
-                   + AnalysisUtil.get_type_str(param['type']) + isOpt + ") : " + param['description'] + "\n\t"
+            args = \
+                args + param['id'] + "(" + AnalysisUtil.get_type_str(param['type']) + is_opt + ") : " \
+                + param['description'] + "\n\t"
 
         for dataset in specs['output_datasets']:
             rets = rets + dataset['id'] + ": " \
diff --git a/pyincore/utils/cgeoutputprocess.py b/pyincore/utils/cgeoutputprocess.py
index a5d18ec2e..35dafe89c 100644
--- a/pyincore/utils/cgeoutputprocess.py
+++ b/pyincore/utils/cgeoutputprocess.py
@@ -18,6 +18,7 @@ def get_cge_household_count(household_count, household_count_path=None, filename
         {
             "beforeEvent": {"HH1": 3611, "HH2": 5997.0, "HH3": 7544.1, "HH4": 2394.1, "HH5": 793.0},
             "afterEvent": {"HH1": 3588, "HH2": 5929.8, "HH3": 7324.1, "HH4": 2207.5, "HH5": 766.4},
+            "%_change": {"HH1": -0.6369, "HH2": -1.1, "HH3": -2.92, "HH4": -7.8, "HH5": -3.35}
         }
 
         Args:
@@ -42,11 +43,16 @@ def get_cge_household_count(household_count, household_count_path=None, filename
 
         before_event = {}
         after_event = {}
+        pct_change = {}
         for i in range(len(income_categories)):
             before_event[income_categories[i]] = before_values[i]
             after_event[income_categories[i]] = after_values[i]
+            if before_values[i]:
+                pct_change[income_categories[i]] = 100 * ((after_values[i] - before_values[i]) / abs(before_values[i]))
+            else:
+                pct_change[income_categories[i]] = None
 
-        cge_total_household_count = {"beforeEvent": before_event, "afterEvent": after_event}
+        cge_total_household_count = {"beforeEvent": before_event, "afterEvent": after_event, "%_change": pct_change}
 
         if filename_json:
             with open(filename_json, "w") as outfile:
@@ -61,6 +67,7 @@ def get_cge_gross_income(gross_income, gross_income_path=None, filename_json=Non
         {
             "beforeEvent": {"HH1": 13, "HH2": 153.5, "HH3": 453.1, "HH4": 438.9, "HH5": 125.0},
             "afterEvent": {"HH1": 13, "HH2": 152.5, "HH3": 445.6, "HH4": 432.9, "HH5": 124.5},
+            "%_change": {"HH1": -0, "HH2": -x.x, "HH3": -x.x, "HH4": -x.x, "HH5": -x.x}
         }
 
         Args:
@@ -85,11 +92,16 @@ def get_cge_gross_income(gross_income, gross_income_path=None, filename_json=Non
 
         before_event = {}
         after_event = {}
+        pct_change = {}
         for i in range(len(income_categories)):
             before_event[income_categories[i]] = before_values[i]
             after_event[income_categories[i]] = after_values[i]
+            if before_values[i]:
+                pct_change[income_categories[i]] = 100 * ((after_values[i] - before_values[i]) / abs(before_values[i]))
+            else:
+                pct_change[income_categories[i]] = None
 
-        cge_total_household_income = {"beforeEvent": before_event, "afterEvent": after_event}
+        cge_total_household_income = {"beforeEvent": before_event, "afterEvent": after_event, "%_change": pct_change}
 
         if filename_json:
             with open(filename_json, "w") as outfile:
@@ -109,7 +121,8 @@ def get_cge_employment(pre_demand, post_demand,
                 "Trade": 8876,
                 "Other": 23767
             },
-            "beforeEvent": {"Goods": 6744, "Trade": 8940, "Other": 24147}
+            "beforeEvent": {"Goods": 6744, "Trade": 8940, "Other": 24147},
+            "%_change": {"Goods": -0, "Trade": -x.x, "Other": -x.x}
         }
 
         Args:
@@ -145,11 +158,16 @@ def get_cge_employment(pre_demand, post_demand,
 
         before_event = {}
         after_event = {}
+        pct_change = {}
         for i in range(len(demand_categories)):
             before_event[demand_categories[i]] = before_values[i]
             after_event[demand_categories[i]] = after_values[i]
+            if before_values[i]:
+                pct_change[demand_categories[i]] = 100 * ((after_values[i] - before_values[i]) / abs(before_values[i]))
+            else:
+                pct_change[demand_categories[i]] = None
 
-        cge_employment = {"beforeEvent": before_event, "afterEvent": after_event}
+        cge_employment = {"beforeEvent": before_event, "afterEvent": after_event, "%_change": pct_change}
 
         if filename_json:
             with open(filename_json, "w") as outfile:
@@ -165,7 +183,9 @@ def get_cge_domestic_supply(domestic_supply, domestic_supply_path=None, filename
             "afterEvent": {"Goods": 662.3, "Trade": 209.0, "Other": 254.1,
                            "HS1": 22.0, "HS2": 1337.1, "HS3": 466.2},
             "beforeEvent": {"Goods": 662.3, "Trade": 209.0, "Other": 254.1,
-                            "HS1": 22.0, "HS2": 1337.1, "HS3": 466.2}
+                            "HS1": 22.0, "HS2": 1337.1, "HS3": 466.2},
+            "%_change": {"Goods": -1.1, "Trade":  -1.1, "Other":  -1.1,
+                            "HS1":  -1.1, "HS2":  -1.1, "HS3":  -1.1}
         }
 
         Args:
@@ -189,11 +209,16 @@ def get_cge_domestic_supply(domestic_supply, domestic_supply_path=None, filename
 
         before_event = {}
         after_event = {}
+        pct_change = {}
         for i in range(len(supply_categories)):
             before_event[supply_categories[i]] = before_values[i]
             after_event[supply_categories[i]] = after_values[i]
+            if before_values[i]:
+                pct_change[supply_categories[i]] = 100 * ((after_values[i] - before_values[i]) / abs(before_values[i]))
+            else:
+                pct_change[supply_categories[i]] = None
 
-        cge_domestic_supply = {"beforeEvent": before_event, "afterEvent": after_event}
+        cge_domestic_supply = {"beforeEvent": before_event, "afterEvent": after_event, "%_change": pct_change}
 
         if filename_json:
             with open(filename_json, "w") as outfile:
diff --git a/pyincore/utils/popdisloutputprocess.py b/pyincore/utils/popdisloutputprocess.py
new file mode 100644
index 000000000..3d32d92e2
--- /dev/null
+++ b/pyincore/utils/popdisloutputprocess.py
@@ -0,0 +1,523 @@
+# Copyright (c) 2021 University of Illinois and others. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Mozilla Public License v2.0 which accompanies this distribution,
+# and is available at https://www.mozilla.org/en-US/MPL/2.0/
+
+import json
+import pandas as pd
+import geopandas as gpd
+from shapely import wkt
+
+
+class PopDislOutputProcess:
+    """This class converts csv results outputs of Population dislocation analysis to json format and shapefiles.
+
+    Args:
+        pop_disl_result (obj): IN-CORE dataset for Joplin Population Dislocation (PD) results.
+        pop_disl_result_path (obj): A fallback for the case that Joplin PD object is not provided.
+            For example a user wants to directly pass in csv files, a path to PD results.
+        filter_on (bool): A flag to filter all data, default True counts only Joplin buildings
+
+    """
+    HUPD_CATEGORIES = ["household_characteristics",
+                       "household_dislocated",
+                       "total_households",
+                       "%_household_dislocated",
+                       "population_dislocated",
+                       "total_population",
+                       "%_population_dislocated"
+                       ]
+
+    def __init__(self, pop_disl_result, pop_disl_result_path=None, filter_on=True):
+        if pop_disl_result_path:
+            pd_result = pd.read_csv(pop_disl_result_path, low_memory=False)
+        else:
+            pd_result = pop_disl_result.get_dataframe_from_csv(low_memory=False)
+        pd_result["geometry"] = pd_result["geometry"].apply(wkt.loads)
+
+        pd_result_shp = None
+        # keep only inventory with guid; filter for Joplin since only Joplin inventory has guids
+        if filter_on:
+            pd_result = pd_result[(pd_result["guid"].notnull()) &
+                                  (pd_result["numprec"].notnull())]
+            # only keep guid and dislocated
+            pd_result_shp = pd_result[(pd_result["dislocated"]) &
+                                      (pd_result["guid"].notnull()) &
+                                      (pd_result["numprec"].notnull())]
+        self.pop_disl_result = pd_result
+        self.pop_disl_result_shp = pd_result_shp
+
+    def get_heatmap_shp(self, filename="pop-disl-numprec.shp"):
+        """ Convert and filter population dislocation output to shapefile that contains only guid and numprec columns
+
+            Args:
+                filename (str): Path and name to save shapefile output file in. E.g "heatmap.shp"
+
+            Returns:
+                str: full path and filename of the shapefile
+
+        """
+        df = self.pop_disl_result_shp
+
+        # save as shapefile
+        gdf = gpd.GeoDataFrame(df, crs='epsg:4326')
+        gdf = gdf[["guid", "numprec", "geometry"]]
+        gdf.to_file(filename)
+
+        return filename
+
+    def pd_by_race(self, filename_json=None):
+        """ Calculate race results from the output files of the Joplin Population Dislocation analysis
+        and convert the results to json format.
+        [
+            {"household_characteristics": "Not Hispanic/White",
+             "household_dislocated": 1521,
+             "total_households": 18507,
+             "%_household_dislocated": 7.3,
+             "population_dislocated",
+             "total_population",
+             "%_population_dislocated"
+            },{"household_characteristics": "Not Hispanic/Black",..,..},{},{},
+            {"No race Ethnicity Data"},{"Total"}
+        ]
+
+        Args:
+            filename_json (str): Path and name to save json output file in. E.g "pd_race_count.json"
+
+        Returns:
+            obj: PD total count by race. A JSON of the hua and population dislocation race results by category.
+
+        """
+        # Race categories
+        # The numbering follows the Community description notebook
+        # 0 - Vacant HU No Race Ethnicity Data, 1 - Not Hispanic/White, 2 - Not Hispanic/Black
+        # 3 - Not Hispanic/Other race, 4 - Hispanic, 5 - No Race or Ethnicity Data
+        race_categories = ["Not Hispanic/White",
+                           "Not Hispanic/Black",
+                           "Not Hispanic/Other Race",
+                           "Hispanic",
+                           "No Race or Ethnicity Data",
+                           "Total"]
+
+        huapd = self.pop_disl_result
+        # Allocated by race and ethnicity
+        huapd["hua_re"] = "0"
+        huapd.loc[(huapd["race"] == 1) & (huapd["hispan"] == 0), "hua_re"] = "1"
+        huapd.loc[(huapd["race"] == 2) & (huapd["hispan"] == 0), "hua_re"] = "2"
+        huapd.loc[(huapd["race"].isin([3, 4, 5, 6, 7])) & (huapd["hispan"] == 0), "hua_re"] = "3"
+        huapd.loc[(huapd["hispan"] == 1), "hua_re"] = "4"
+        huapd.loc[(huapd["gqtype"] >= 1), "hua_re"] = "5"
+        hua_vals = huapd["hua_re"].value_counts()
+        hua_tot = []
+        for i in range(len(race_categories)):
+            try:
+                hua_tot.append(int(hua_vals[str(i)]))
+            except Exception:
+                hua_tot.append(0)
+        hua_tot.append(int(sum(hua_tot[1:])))
+
+        pop_tot = []
+        for i in range(len(race_categories)):
+            pop_tot.append(int(huapd["numprec"].where(huapd["hua_re"] == str(i)).sum()))
+        pop_tot.append(int(sum(pop_tot[1:])))
+
+        # Dislocated by race and ethnicity
+        huapd["hud_re"] = "0"
+        huapd.loc[(huapd["race"] == 1) & (huapd["hispan"] == 0) & huapd["dislocated"], "hud_re"] = "1"
+        huapd.loc[(huapd["race"] == 2) & (huapd["hispan"] == 0) & huapd["dislocated"], "hud_re"] = "2"
+        huapd.loc[(huapd["race"].isin([3, 4, 5, 6, 7])) & (huapd["hispan"] == 0) & huapd["dislocated"], "hud_re"] = "3"
+        huapd.loc[(huapd["hispan"] == 1) & huapd["dislocated"], "hud_re"] = "4"
+        huapd.loc[(huapd["gqtype"] >= 1) & huapd["dislocated"], "hud_re"] = "5"
+        hud_vals = huapd["hud_re"].value_counts()
+        hua_disl = []
+        for i in range(len(race_categories)):
+            try:
+                hua_disl.append(int(hud_vals[str(i)]))
+            except Exception:
+                hua_disl.append(0)
+        hua_disl.append(int(sum(hua_disl[1:])))
+
+        pd_disl = []
+        for i in range(len(race_categories)):
+            pd_disl.append(int(huapd["numprec"].where(huapd["hud_re"] == str(i)).sum()))
+        pd_disl.append(int(sum(pd_disl[1:])))
+
+        pd_by_race_json = []
+        for i in range(len(race_categories)):
+            huapd_race = {}
+            huapd_race[self.HUPD_CATEGORIES[0]] = race_categories[i]
+            huapd_race[self.HUPD_CATEGORIES[1]] = hua_disl[i + 1]
+            huapd_race[self.HUPD_CATEGORIES[2]] = hua_tot[i + 1]
+            if hua_tot[i + 1]:
+                huapd_race[self.HUPD_CATEGORIES[3]] = 100 * (hua_disl[i + 1] / hua_tot[i + 1])
+            else:
+                huapd_race[self.HUPD_CATEGORIES[3]] = None
+            huapd_race[self.HUPD_CATEGORIES[4]] = pd_disl[i + 1]
+            huapd_race[self.HUPD_CATEGORIES[5]] = pop_tot[i + 1]
+            if pop_tot[i + 1]:
+                huapd_race[self.HUPD_CATEGORIES[6]] = 100 * (pd_disl[i + 1] / pop_tot[i + 1])
+            else:
+                huapd_race[self.HUPD_CATEGORIES[6]] = None
+            pd_by_race_json.append(huapd_race)
+        # print(pd_by_race_json)
+
+        if filename_json:
+            with open(filename_json, "w") as outfile:
+                json.dump(pd_by_race_json, outfile)
+        # Serializing json
+        return json.dumps(pd_by_race_json)
+
+    def pd_by_income(self, filename_json=None):
+        """ Calculate income results from the output files of the Joplin Population Dislocation analysis
+        and convert the results to json format.
+        [
+            {"household_characteristics": "HH1 (less than $15,000)",
+             "household_dislocated": 311,
+             "total_households": 3252,
+             "%_household_dislocated": 7.3,
+             "population_dislocated": 311,
+             "total_population": 3252,
+             "%_population_dislocated"
+             },
+             {"HH2 ($15,000 to $35,000)",..,..,..,..},{},{},{},{},
+             {"Unknown",..,..,..,..}
+        ]
+
+        Args:
+            filename_json (str): Path and name to save json output file in. E.g "pd_income_count.json"
+
+        Returns:
+            obj: PD total count by income. A JSON of the hua and population dislocation income results by category.
+
+        """
+        income_categories = ["HH1 (less than $15,000)",
+                             "HH2 ($15,000 to $35,000)",
+                             "HH3 ($35,000 to $70,000)",
+                             "HH4 ($70,000 to $120,000)",
+                             "HH5 (More than $120,000)",
+                             "Unknown",
+                             "Total"]
+
+        huapd = self.pop_disl_result
+        # Allocated by income
+        hua_tot = []
+        for i in range(1, 6):
+            alloc_inc = (huapd["hhinc"] == i).sum()
+            hua_tot.append(int(alloc_inc))
+        hua_tot.append(int(pd.isna(huapd["hhinc"]).sum()))
+        hua_tot.append(int(sum(hua_tot)))
+
+        pop_tot = []
+        for i in range(1, 6):
+            alloc = huapd["numprec"].where(huapd["hhinc"] == i).sum()
+            pop_tot.append(int(alloc))
+        pop_tot.append(int(huapd["numprec"].where(pd.isna(huapd["hhinc"])).sum()))
+        pop_tot.append(int(sum(pop_tot)))
+
+        # Dislocated by income
+        hua_disl = []
+        for i in range(1, 6):
+            disl = huapd.loc[(huapd["hhinc"] == i) & huapd["dislocated"], ["dislocated"]].sum()
+            hua_disl.append(int(disl))
+        disl_unknown = huapd.loc[pd.isna(huapd["hhinc"]) & huapd["dislocated"], ["dislocated"]].sum()
+        hua_disl.append(int(disl_unknown))
+        hua_disl.append(int(sum(hua_disl)))
+
+        pd_disl = []
+        for i in range(1, 6):
+            disl = huapd.loc[(huapd["hhinc"] == i) & huapd["dislocated"], ["numprec"]].sum()
+            pd_disl.append(int(disl))
+        disl_unknown = huapd.loc[pd.isna(huapd["hhinc"]) & huapd["dislocated"], ["numprec"]].sum()
+        pd_disl.append(int(disl_unknown))
+        pd_disl.append(int(sum(pd_disl)))
+
+        pd_by_income_json = []
+        for i in range(len(income_categories)):
+            huapd_income = {}
+            huapd_income[self.HUPD_CATEGORIES[0]] = income_categories[i]
+            huapd_income[self.HUPD_CATEGORIES[1]] = hua_disl[i]
+            huapd_income[self.HUPD_CATEGORIES[2]] = hua_tot[i]
+            if hua_tot[i]:
+                huapd_income[self.HUPD_CATEGORIES[3]] = 100 * (hua_disl[i] / hua_tot[i])
+            else:
+                huapd_income[self.HUPD_CATEGORIES[3]] = None
+            huapd_income[self.HUPD_CATEGORIES[4]] = pd_disl[i]
+            huapd_income[self.HUPD_CATEGORIES[5]] = pop_tot[i]
+            if pop_tot[i]:
+                huapd_income[self.HUPD_CATEGORIES[6]] = 100 * (pd_disl[i] / pop_tot[i])
+            else:
+                huapd_income[self.HUPD_CATEGORIES[6]] = None
+            pd_by_income_json.append(huapd_income)
+        # print(pd_by_income_json)
+
+        if filename_json:
+            with open(filename_json, "w") as outfile:
+                json.dump(pd_by_income_json, outfile)
+        # Serializing json
+        return json.dumps(pd_by_income_json)
+
+    def pd_by_tenure(self, filename_json=None):
+        """ Calculate tenure results from the output files of the Joplin Population Dislocation analysis
+        and convert the results to json format.
+        [
+            {"household_characteristics": "Owner occupied",
+             "household_dislocated": 1018,
+             "total_households": 11344,
+             "%_household_dislocated": 7.3,
+             "population_dislocated": 1018,
+             "total_population": 11344,
+             "%_population_dislocated"
+            },
+            {"household_characteristics": "Renter occupied",..,..,..,..},{},{},{},{},{},
+            {"total",..,..,..,..}
+        ]
+
+        Args:
+            filename_json (str): Path and name to save json output file in. E.g "pd_income_count.json"
+
+        Returns:
+            obj: PD total count by income. A JSON of the hua and population dislocation income results by category.
+
+        """
+        # Tenure categories
+        # The numbering follows the Community description notebook
+        # 0 - Vacant HU No Tenure Data, 1 - Owner occupied, 2 - Renter occupied,
+        # 3 - Nursing facilities, 4 - Other group quarters, 5 - Vacant for rent
+        # 6 - Vacant for sale, 7 - Vacant other
+        tenure_categories = ["Owner occupied",
+                             "Renter occupied",
+                             "Nursing facilities",
+                             "Other group quarters",
+                             "Vacant for rent",
+                             "Vacant for sale",
+                             "Vacant other",
+                             "Total"]
+
+        huapd = self.pop_disl_result
+        # Allocated by tenure
+        huapd["hua_tnr"] = "0"
+        huapd.loc[huapd["ownershp"] == 1.0, "hua_tnr"] = "1"
+        huapd.loc[huapd["ownershp"] == 2.0, "hua_tnr"] = "2"
+        huapd.loc[huapd["gqtype"] == 3, "hua_tnr"] = "3"
+        huapd.loc[huapd["gqtype"].isin([1, 2, 4, 5, 6, 7, 8]), "hua_tnr"] = "4"
+        huapd.loc[huapd["vacancy"].isin([1, 2]), "hua_tnr"] = "5"
+        huapd.loc[huapd["vacancy"].isin([3, 4]), "hua_tnr"] = "6"
+        huapd.loc[huapd["vacancy"].isin([5, 6, 7]), "hua_tnr"] = "7"
+        hua_vals = huapd["hua_tnr"].value_counts()
+        hua_tot = []
+        for i in range(len(tenure_categories)):
+            try:
+                hua_tot.append(int(hua_vals[str(i)]))
+            except Exception:
+                hua_tot.append(0)
+        hua_tot.append(int(sum(hua_tot[1:])))
+
+        pop_tot = []
+        for i in range(len(tenure_categories)):
+            pop_tot.append(int(huapd["numprec"].where(huapd["hua_tnr"] == str(i)).sum()))
+        pop_tot.append(int(sum(pop_tot[1:])))
+
+        # Dislocated by tenure
+        huapd["hud_tnr"] = "0"
+        huapd.loc[(huapd["ownershp"] == 1.0) & huapd["dislocated"], "hud_tnr"] = "1"
+        huapd.loc[(huapd["ownershp"] == 2.0) & huapd["dislocated"], "hud_tnr"] = "2"
+        huapd.loc[(huapd["gqtype"] == 3) & huapd["dislocated"], "hud_tnr"] = "3"
+        huapd.loc[huapd["gqtype"].isin([1, 2, 4, 5, 6, 7, 8]) & huapd["dislocated"], "hud_tnr"] = "4"
+        huapd.loc[huapd["vacancy"].isin([1, 2]) & huapd["dislocated"], "hud_tnr"] = "5"
+        huapd.loc[huapd["vacancy"].isin([3, 4]) & huapd["dislocated"], "hud_tnr"] = "6"
+        huapd.loc[huapd["vacancy"].isin([5, 6, 7]) & huapd["dislocated"], "hud_tnr"] = "7"
+        hud_vals = huapd["hud_tnr"].value_counts()
+        hua_disl = []
+        for i in range(len(tenure_categories)):
+            try:
+                hua_disl.append(int(hud_vals[str(i)]))
+            except Exception:
+                hua_disl.append(0)
+        hua_disl.append(int(sum(hua_disl[1:])))
+
+        pd_disl = []
+        for i in range(len(tenure_categories)):
+            pd_disl.append(int(huapd["numprec"].where(huapd["hud_tnr"] == str(i)).sum()))
+        pd_disl.append(int(sum(pd_disl[1:])))
+
+        pd_by_tenure_json = []
+        for i in range(len(tenure_categories)):
+            huapd_tenure = {}
+            huapd_tenure[self.HUPD_CATEGORIES[0]] = tenure_categories[i]
+            huapd_tenure[self.HUPD_CATEGORIES[1]] = hua_disl[i + 1]
+            huapd_tenure[self.HUPD_CATEGORIES[2]] = hua_tot[i + 1]
+            if hua_tot[i + 1]:
+                huapd_tenure[self.HUPD_CATEGORIES[3]] = 100 * (hua_disl[i + 1] / hua_tot[i + 1])
+            else:
+                huapd_tenure[self.HUPD_CATEGORIES[3]] = None
+            huapd_tenure[self.HUPD_CATEGORIES[4]] = pd_disl[i + 1]
+            huapd_tenure[self.HUPD_CATEGORIES[5]] = pop_tot[i + 1]
+            if pop_tot[i + 1]:
+                huapd_tenure[self.HUPD_CATEGORIES[6]] = 100 * (pd_disl[i + 1] / pop_tot[i + 1])
+            else:
+                huapd_tenure[self.HUPD_CATEGORIES[6]] = None
+            pd_by_tenure_json.append(huapd_tenure)
+        # print(pd_by_tenure_json)
+
+        if filename_json:
+            with open(filename_json, "w") as outfile:
+                json.dump(pd_by_tenure_json, outfile)
+        # Serializing json
+        return json.dumps(pd_by_tenure_json)
+
+    def pd_by_housing(self, filename_json=None):
+        """ Calculate housing results from the output files of the Joplin Population Dislocation analysis
+        using huestimate column (huestimate = 1 is single family, huestimate > 1 means multi family house)
+        and convert the results to json format.
+        [
+            {"household_characteristics": "Single Family",
+             "household_dislocated": 1162,
+             "total_households": 837,
+             "%_household_dislocated": 7.3,
+             "population_dislocated": 1162,
+             "total_population": 837,
+             "%_population_dislocated"
+             },{},{"Total",..,..,..,..}
+        ]
+
+        Args:
+            filename_json (str): Path and name to save json output file in. E.g "pd_housing_count.json"
+
+        Returns:
+            obj: PD total count by housing. A JSON of the hua and population dislocation housing results by category.
+
+        """
+        # Household categories
+        # 0 - Vacant HU No Tenure Data, 1 - Single Family, 2 - Multi Family
+        household_categories = ["Single Family",
+                                "Multi Family",
+                                "Total"]
+
+        huapd = self.pop_disl_result
+        # Allocated by housing
+        huapd["hua_house"] = "0"
+        huapd.loc[(huapd["huestimate"] == 1.0), "hua_house"] = "1"
+        huapd.loc[(huapd["huestimate"] > 1.0), "hua_house"] = "2"
+        hua_vals = huapd["hua_house"].value_counts()
+        hua_tot = []
+        for i in range(len(household_categories)):
+            try:
+                hua_tot.append(int(hua_vals[str(i)]))
+            except Exception:
+                hua_tot.append(0)
+        hua_tot.append(int(sum(hua_tot[1:])))
+
+        pop_tot = []
+        for i in range(len(household_categories)):
+            pop_tot.append(int(huapd["numprec"].where(huapd["hua_house"] == str(i)).sum()))
+        pop_tot.append(int(sum(pop_tot[1:])))
+
+        # Dislocated by household
+        huapd["hud_house"] = "0"
+        huapd.loc[(huapd["huestimate"] == 1.0) & huapd["dislocated"], "hud_house"] = "1"
+        huapd.loc[(huapd["huestimate"] > 1.0) & huapd["dislocated"], "hud_house"] = "2"
+        hud_vals = huapd["hud_house"].value_counts()
+        hua_disl = []
+        for i in range(len(household_categories)):
+            try:
+                hua_disl.append(int(hud_vals[str(i)]))
+            except Exception:
+                hua_disl.append(0)
+        hua_disl.append(int(sum(hua_disl[1:])))
+
+        pd_disl = []
+        for i in range(len(household_categories)):
+            pd_disl.append(int(huapd["numprec"].where(huapd["hud_house"] == str(i)).sum()))
+        pd_disl.append(int(sum(pd_disl[1:])))
+
+        pd_by_housing_json = []
+        for i in range(len(household_categories)):
+            huapd_household = {}
+            huapd_household[self.HUPD_CATEGORIES[0]] = household_categories[i]
+            huapd_household[self.HUPD_CATEGORIES[1]] = hua_disl[i + 1]
+            huapd_household[self.HUPD_CATEGORIES[2]] = hua_tot[i + 1]
+            if hua_tot[i + 1]:
+                huapd_household[self.HUPD_CATEGORIES[3]] = 100 * (hua_disl[i + 1] / hua_tot[i + 1])
+            else:
+                huapd_household[self.HUPD_CATEGORIES[3]] = None
+            huapd_household[self.HUPD_CATEGORIES[4]] = pd_disl[i + 1]
+            huapd_household[self.HUPD_CATEGORIES[5]] = pop_tot[i + 1]
+            if pop_tot[i + 1]:
+                huapd_household[self.HUPD_CATEGORIES[6]] = 100 * (pd_disl[i + 1] / pop_tot[i + 1])
+            else:
+                huapd_household[self.HUPD_CATEGORIES[6]] = None
+            pd_by_housing_json.append(huapd_household)
+        # print(pd_by_housing_json)
+
+        if filename_json:
+            with open(filename_json, "w") as outfile:
+                json.dump(pd_by_housing_json, outfile)
+        return json.dumps(pd_by_housing_json)
+
+    def pd_total(self, filename_json=None):
+        """ Calculate total results from the output files of the Joplin Population Dislocation analysis
+        and convert the results to json format.
+        {   "household_dislocated": {
+                "dislocated": {
+                    "number": 1999,
+                    "percentage": 0.085
+                }, "not_dislocated": {}, "total": {}
+            },"population_dislocated": {"dislocated": {},"not_dislocated": {}, "total": {}}
+        }
+
+        Args:
+            filename_json (str): Path and name to save json output file in. E.g "pd_total_count.json"
+
+        Returns:
+            obj: PD total count. A JSON of the hua and population dislocation total results by category.
+
+        """
+        # Dislocated by race and ethnicity
+        hud = self.pop_disl_result
+        hud_vals = hud["dislocated"].value_counts()
+        hua_disl = [int(hud_vals[False]), int(hud_vals[True])]
+
+        pd_disl = []
+        pd_disl.append(int(hud["numprec"].where(hud["dislocated"] == 0).sum()))
+        pd_disl.append(int(hud["numprec"].where(hud["dislocated"] == 1).sum()))
+
+        hua_tot = sum(hua_disl)
+        pop_tot = sum(pd_disl)
+
+        hua_disl_tot = {}
+        if hua_tot:
+            hua_disl_tot["dislocated"] = {"households": hua_disl[1],
+                                          "%_of_households": 100 * (hua_disl[1]/hua_tot)}
+            hua_disl_tot["not_dislocated"] = {"households": hua_tot - hua_disl[1],
+                                              "%_of_households": 100 * ((hua_tot - hua_disl[1])/hua_tot)}
+            hua_disl_tot["total"] = {"households": hua_tot, "%_of_households": 100}
+        else:
+            hua_disl_tot["dislocated"] = {"households": None,
+                                          "%_of_households": None}
+            hua_disl_tot["not_dislocated"] = {"households": None,
+                                              "%_of_households": None}
+            hua_disl_tot["total"] = {"households": None, "%_of_households": None}
+
+        pop_disl_tot = {}
+        if pop_tot:
+            pop_disl_tot["dislocated"] = {"population": pd_disl[1],
+                                          "%_of_population": 100 * (pd_disl[1]/pop_tot)}
+            pop_disl_tot["not_dislocated"] = {"population": pop_tot - pd_disl[1],
+                                              "%_of_population": 100 * ((pop_tot - pd_disl[1])/pop_tot)}
+            pop_disl_tot["total"] = {"population": pop_tot, "%_of_population": 100}
+        else:
+            pop_disl_tot["dislocated"] = {"population": None,
+                                          "%_of_population": None}
+            pop_disl_tot["not_dislocated"] = {"population": None,
+                                              "%_of_population": None}
+            pop_disl_tot["total"] = {"population": None, "%_of_population": None}
+
+        pd_total_json = {"household_dislocation_in_total": hua_disl_tot,
+                         "population_dislocation_in_total": pop_disl_tot}
+        # print(pd_total_json)
+
+        if filename_json:
+            with open(filename_json, "w") as outfile:
+                json.dump(pd_total_json, outfile)
+        return json.dumps(pd_total_json)
diff --git a/recipes/meta.yaml b/recipes/meta.yaml
new file mode 100644
index 000000000..ac07cf5ef
--- /dev/null
+++ b/recipes/meta.yaml
@@ -0,0 +1,81 @@
+{% set name = "pyincore" %}
+{% set version = "1.0.0" %}
+ 
+package:
+  name: {{ name|lower }}
+  version: {{ version }}
+ 
+source:
+  path: /path/to/pyincore
+  # PR: https://opensource.ncsa.illinois.edu/bitbucket/projects/INCORE1/repos/pyincore/pull-requests/12/overview
+ 
+build:
+  # If this is a new build for the same version, increment the build
+  # number. If you do not include this key, it defaults to 0.
+  #number: 1
+  noarch: python
+  script: "{{ PYTHON }} -m pip install --no-deps --ignore-installed -vv . " # verbose
+ 
+requirements:
+  build:
+    - python>=3.8
+    - pip
+    - numpy 1.16
+ 
+  host:
+    - python>=3.6
+    - pip
+    - numpy 1.16
+     
+  run:
+    - python>=3.6
+    - {{ pin_compatible('numpy') }}
+    - boto3
+    - deprecated
+    - fiona>=1.8.4
+    - geopandas>=0.6.1
+    - jsonpickle>=1.1
+    - libspatialindex>=1.9.0
+    - networkx>=2.2
+    - numpy>=1.16.1
+    - pytest>=3.9.0
+    - python-jose>=3.0
+    - pyproj>=1.9.6
+    - pyyaml>=3.13
+    - pycodestyle>=2.6.0
+    - pyomo>=5.6
+    - owslib>=0.17.1
+    - pandas>=0.24.1
+    - rasterio>=1.0.18
+    - requests>=2.21.0
+    - rtree>=0.8.3
+    - scipy>=1.2.0
+    - shapely>=1.6.4.post1
+    - wntr>=0.1.6
+    - ipopt>=3.11
+ 
+test:
+  # Python imports
+  imports:
+    - pyincore
+    - pyomo
+   
+  requires:
+    # Put any additional test requirements here.  For example
+    - pytest>=3.9
+  commands:
+    # You can put built-in test commands to be run here.  Use this to test that the entry points work.
+    python -c "import pyincore; print('SUCCESS')" #; pyincore.test_client()"
+    # You can also put a file called run_test.py in the recipe that will be run at test time.
+ 
+about:
+  home: https://incore.ncsa.illinois.edu
+  license: MPL-2.0
+  summary: 'Python library for IN-CORE (Interdependent Networked Community Resilience Modeling Environment)'
+  description: 'pyIncore is a component of IN-CORE. It is a python package consisting of two primary components: 
+  1) a set of service classes to interact with the IN-CORE web services, and 2) IN-CORE analyses. The pyIncore 
+  package allows users to apply various hazards to infrastructure in selected areas, propagating the effect of 
+  physical infrastructure damage and loss of functionality to social and economic impacts.'
+  dev_url: https://github.com/IN-CORE/pyincore
+  doc_url: https://incore.ncsa.illinois.edu/doc/incore
+
diff --git a/scripts/release-packages.yml b/scripts/release-packages.yml
index ee4d75644..519ca72cc 100644
--- a/scripts/release-packages.yml
+++ b/scripts/release-packages.yml
@@ -1,7 +1,7 @@
 # List of pyincore analyses to either include or exclude for release
 # If provided, 'excludes' takes preference over 'includeOnly'
 
-version: '1.0.0'
+version: '1.1.0'
 
 includeOnly:
   - buildingdamage
diff --git a/setup.py b/setup.py
index 6068db454..408405c0e 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@
 
 setup(
     name='pyincore',
-    version='1.0.0',
+    version='1.1.0',
     packages=find_packages(where=".", exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
     include_package_data=True,
     package_data={


Test patch:
diff --git a/.github/workflows/pytests.yml b/.github/workflows/pytests.yml
new file mode 100644
index 000000000..a1711db0d
--- /dev/null
+++ b/.github/workflows/pytests.yml
@@ -0,0 +1,48 @@
+name: pytests
+
+# Runs unit tests on:
+#  - any push to any branch
+#  - when a PR is opened/reopened - this is just for additional safety and covers the case of master -> develop PRs without new pushes
+
+# TODO: Use mamba instead of conda for installing packages. Improves on ~5mins it takes to install requirements.
+#  https://github.com/mamba-org/provision-with-micromamba
+
+on:
+  push:
+
+  pull_request:
+     types: [opened, reopened]
+
+jobs:
+  unit_test:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout source code
+        uses: actions/checkout@v2
+
+      - name: Cache conda
+        uses: actions/cache@v2
+        env:
+          # Increase this value to reset cache if environment.yml has not changed
+          CACHE_NUMBER: 0
+        with:
+          path: ~/conda_pkgs_dir
+          key:
+            ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{
+            hashFiles('environment.yml') }}
+
+      - name: Install miniconda
+        uses: conda-incubator/setup-miniconda@v2
+        with:
+          miniconda-version: "latest"
+          mamba-version: "*"
+          use-mamba: true
+          python-version: 3.8
+          activate-environment: base
+          environment-file: environment.yml
+          use-only-tar-bz2: true
+
+      - name: Run tests with pytest
+        run: |
+          echo "${{secrets.PYTEST_USER_TOKEN}}" > tests/pyincore/.incorepw
+          $CONDA/bin/python -m pytest --ignore=tests/test_format.py --ignore=tests/pyincore/analyses
diff --git a/tests/data/fragility_curves/ConditionalStandardFragilityCurve_original.json b/tests/data/fragility_curves/ConditionalStandardFragilityCurve_original.json
deleted file mode 100644
index 7f25b9cb5..000000000
--- a/tests/data/fragility_curves/ConditionalStandardFragilityCurve_original.json
+++ /dev/null
@@ -1,108 +0,0 @@
-{
-  "className": "FragilitySet",
-  "demandTypes": [
-    "Vmax"
-  ],
-  "demandUnits": [
-    "ft/s"
-  ],
-  "fragilityCurves": [
-    {
-      "className": "ConditionalStandardFragilityCurve",
-      "alpha": [
-        2.6,
-        2.6
-      ],
-      "beta": [
-        0.4,
-        0.5
-      ],
-      "alphaType": "median",
-      "curveType": "LogNormal",
-      "rules": {
-        "0": [
-          "demand LT 3.2"
-        ],
-        "1": [
-          "demand GE 3.2"
-        ]
-      },
-      "description": "Slight"
-    },
-    {
-      "className": "ConditionalStandardFragilityCurve",
-      "alpha": [
-        4.8,
-        4.8
-      ],
-      "beta": [
-        0.4,
-        0.5
-      ],
-      "alphaType": "median",
-      "curveType": "LogNormal",
-      "rules": {
-        "0": [
-          "demand LT 3.2"
-        ],
-        "1": [
-          "demand GE 3.2"
-        ]
-      },
-      "description": "Moderate"
-    },
-    {
-      "className": "ConditionalStandardFragilityCurve",
-      "alpha": [
-        8.4,
-        8.4
-      ],
-      "beta": [
-        0.4,
-        0.5
-      ],
-      "alphaType": "median",
-      "curveType": "LogNormal",
-      "rules": {
-        "0": [
-          "demand LT 3.2"
-        ],
-        "1": [
-          "demand GE 3.2"
-        ]
-      },
-      "description": "Extensive"
-    },
-    {
-      "className": "ConditionalStandardFragilityCurve",
-      "alpha": [
-        13.1,
-        13.1
-      ],
-      "beta": [
-        0.4,
-        0.5
-      ],
-      "alphaType": "median",
-      "curveType": "LogNormal",
-      "rules": {
-        "0": [
-          "demand LT 3.2"
-        ],
-        "1": [
-          "demand GE 3.2"
-        ]
-      },
-      "description": "Complete"
-    }
-  ],
-  "description": "seaside epf substation conditional fragility",
-  "authors": [
-    "Dylan Sanderson"
-  ],
-  "resultType": "Limit State",
-  "hazardType": "tsunami",
-  "inventoryType": "electric_facility",
-  "creator": "cwang138",
-  "id": "5ebb0db727e5d0c756a7575f"
-}
\ No newline at end of file
diff --git a/tests/data/fragility_curves/ParametricFragilityCurve_original.json b/tests/data/fragility_curves/ParametricFragilityCurve_original.json
deleted file mode 100644
index 943e3e027..000000000
--- a/tests/data/fragility_curves/ParametricFragilityCurve_original.json
+++ /dev/null
@@ -1,534 +0,0 @@
-{
-  "className": "FragilitySet",
-  "demandTypes": [
-    "PGA"
-  ],
-  "demandUnits": [
-    "g"
-  ],
-  "fragilityCurves": [
-    {
-      "className": "ParametricFragilityCurve",
-      "parameters": [
-        {
-          "name": "Constant",
-          "unit": "",
-          "coefficient": -7.779087306,
-          "interceptTermDefault": 1.0
-        },
-        {
-          "name": "Demand",
-          "unit": "g",
-          "coefficient": 2.744724342,
-          "interceptTermDefault": 0.0
-        },
-        {
-          "name": "fc",
-          "unit": "MPa",
-          "coefficient": -0.02438759,
-          "interceptTermDefault": 33.78
-        },
-        {
-          "name": "fy",
-          "unit": "MPa",
-          "coefficient": -0.00180294,
-          "interceptTermDefault": 459.436160679934
-        },
-        {
-          "name": "EBL",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": -3.22
-        },
-        {
-          "name": "EBT",
-          "unit": "",
-          "coefficient": 0.242332657,
-          "interceptTermDefault": -2.3
-        },
-        {
-          "name": "FBL",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": -1.56
-        },
-        {
-          "name": "FBT",
-          "unit": "",
-          "coefficient": -0.242469282,
-          "interceptTermDefault": 0.99
-        },
-        {
-          "name": "pass_stf",
-          "unit": "N/mm/mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 21.545
-        },
-        {
-          "name": "act_stf",
-          "unit": "N/pile",
-          "coefficient": -4.8e-06,
-          "interceptTermDefault": 177795.0
-        },
-        {
-          "name": "rot_stf",
-          "unit": "N/mm/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 175000.0
-        },
-        {
-          "name": "trns_stf",
-          "unit": "N/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 222245.0
-        },
-        {
-          "name": "damp",
-          "unit": "",
-          "coefficient": -0.423772472,
-          "interceptTermDefault": 0.045
-        },
-        {
-          "name": "abt_gp_L1",
-          "unit": "mm",
-          "coefficient": 0.014277084,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "abt_gp_L2",
-          "unit": "mm",
-          "coefficient": -0.00512963,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "hng_gp_L1",
-          "unit": "mm",
-          "coefficient": -0.013161096,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "hng_gp_L2",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "spn_ln",
-          "unit": "mm",
-          "coefficient": 0.000278567,
-          "interceptTermDefault": 13950.0
-        },
-        {
-          "name": "col_ht",
-          "unit": "mm",
-          "coefficient": 0.001902897,
-          "interceptTermDefault": 4070.0
-        },
-        {
-          "name": "width",
-          "unit": "ft",
-          "coefficient": 2.45e-06,
-          "interceptTermDefault": 6350.0
-        }
-      ],
-      "curveType": "Logit",
-      "description": "Slight"
-    },
-    {
-      "className": "ParametricFragilityCurve",
-      "parameters": [
-        {
-          "name": "Constant",
-          "unit": "",
-          "coefficient": -7.755722087,
-          "interceptTermDefault": 1.0
-        },
-        {
-          "name": "Demand",
-          "unit": "g",
-          "coefficient": 2.401040162,
-          "interceptTermDefault": 0.0
-        },
-        {
-          "name": "fc",
-          "unit": "MPa",
-          "coefficient": -0.00515359,
-          "interceptTermDefault": 33.78
-        },
-        {
-          "name": "fy",
-          "unit": "MPa",
-          "coefficient": -0.000574063,
-          "interceptTermDefault": 459.436160679934
-        },
-        {
-          "name": "EBL",
-          "unit": "",
-          "coefficient": 0.000701378,
-          "interceptTermDefault": -3.22
-        },
-        {
-          "name": "EBT",
-          "unit": "",
-          "coefficient": 0.096521252,
-          "interceptTermDefault": -2.3
-        },
-        {
-          "name": "FBL",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": -1.56
-        },
-        {
-          "name": "FBT",
-          "unit": "",
-          "coefficient": -0.106471211,
-          "interceptTermDefault": 0.99
-        },
-        {
-          "name": "pass_stf",
-          "unit": "N/mm/mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 21.545
-        },
-        {
-          "name": "act_stf",
-          "unit": "N/pile",
-          "coefficient": -7.52e-06,
-          "interceptTermDefault": 177795.0
-        },
-        {
-          "name": "rot_stf",
-          "unit": "N/mm/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 175000.0
-        },
-        {
-          "name": "trns_stf",
-          "unit": "N/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 222245.0
-        },
-        {
-          "name": "damp",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": 0.045
-        },
-        {
-          "name": "abt_gp_L1",
-          "unit": "mm",
-          "coefficient": 0.011928621,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "abt_gp_L2",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "hng_gp_L1",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "hng_gp_L2",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "spn_ln",
-          "unit": "mm",
-          "coefficient": 0.00023725,
-          "interceptTermDefault": 13950.0
-        },
-        {
-          "name": "col_ht",
-          "unit": "mm",
-          "coefficient": 0.001395892,
-          "interceptTermDefault": 4070.0
-        },
-        {
-          "name": "width",
-          "unit": "ft",
-          "coefficient": 1.48e-05,
-          "interceptTermDefault": 6350.0
-        }
-      ],
-      "curveType": "Logit",
-      "description": "Moderate"
-    },
-    {
-      "className": "ParametricFragilityCurve",
-      "parameters": [
-        {
-          "name": "Constant",
-          "unit": "",
-          "coefficient": -10.04589897,
-          "interceptTermDefault": 1.0
-        },
-        {
-          "name": "Demand",
-          "unit": "g",
-          "coefficient": 2.739809257,
-          "interceptTermDefault": 0.0
-        },
-        {
-          "name": "fc",
-          "unit": "MPa",
-          "coefficient": 0.0,
-          "interceptTermDefault": 33.78
-        },
-        {
-          "name": "fy",
-          "unit": "MPa",
-          "coefficient": 0.0,
-          "interceptTermDefault": 459.436160679934
-        },
-        {
-          "name": "EBL",
-          "unit": "",
-          "coefficient": 0.030960656,
-          "interceptTermDefault": -3.22
-        },
-        {
-          "name": "EBT",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": -2.3
-        },
-        {
-          "name": "FBL",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": -1.56
-        },
-        {
-          "name": "FBT",
-          "unit": "",
-          "coefficient": -0.124315732,
-          "interceptTermDefault": 0.99
-        },
-        {
-          "name": "pass_stf",
-          "unit": "N/mm/mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 21.545
-        },
-        {
-          "name": "act_stf",
-          "unit": "N/pile",
-          "coefficient": -7.93e-06,
-          "interceptTermDefault": 177795.0
-        },
-        {
-          "name": "rot_stf",
-          "unit": "N/mm/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 175000.0
-        },
-        {
-          "name": "trns_stf",
-          "unit": "N/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 222245.0
-        },
-        {
-          "name": "damp",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": 0.045
-        },
-        {
-          "name": "abt_gp_L1",
-          "unit": "mm",
-          "coefficient": 0.008521309,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "abt_gp_L2",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "hng_gp_L1",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "hng_gp_L2",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "spn_ln",
-          "unit": "mm",
-          "coefficient": 0.000273989,
-          "interceptTermDefault": 13950.0
-        },
-        {
-          "name": "col_ht",
-          "unit": "mm",
-          "coefficient": 0.001510439,
-          "interceptTermDefault": 4070.0
-        },
-        {
-          "name": "width",
-          "unit": "ft",
-          "coefficient": 4.27e-05,
-          "interceptTermDefault": 6350.0
-        }
-      ],
-      "curveType": "Logit",
-      "description": "Extensive"
-    },
-    {
-      "className": "ParametricFragilityCurve",
-      "parameters": [
-        {
-          "name": "Constant",
-          "unit": "",
-          "coefficient": -10.04589897,
-          "interceptTermDefault": 1.0
-        },
-        {
-          "name": "Demand",
-          "unit": "g",
-          "coefficient": 2.739809257,
-          "interceptTermDefault": 0.0
-        },
-        {
-          "name": "fc",
-          "unit": "MPa",
-          "coefficient": 0.0,
-          "interceptTermDefault": 33.78
-        },
-        {
-          "name": "fy",
-          "unit": "MPa",
-          "coefficient": 0.0,
-          "interceptTermDefault": 459.436160679934
-        },
-        {
-          "name": "EBL",
-          "unit": "",
-          "coefficient": 0.030960656,
-          "interceptTermDefault": -3.22
-        },
-        {
-          "name": "EBT",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": -2.3
-        },
-        {
-          "name": "FBL",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": -1.56
-        },
-        {
-          "name": "FBT",
-          "unit": "",
-          "coefficient": -0.124315732,
-          "interceptTermDefault": 0.99
-        },
-        {
-          "name": "pass_stf",
-          "unit": "N/mm/mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 21.545
-        },
-        {
-          "name": "act_stf",
-          "unit": "N/pile",
-          "coefficient": -7.93e-06,
-          "interceptTermDefault": 177795.0
-        },
-        {
-          "name": "rot_stf",
-          "unit": "N/mm/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 175000.0
-        },
-        {
-          "name": "trns_stf",
-          "unit": "N/pile",
-          "coefficient": 0.0,
-          "interceptTermDefault": 222245.0
-        },
-        {
-          "name": "damp",
-          "unit": "",
-          "coefficient": 0.0,
-          "interceptTermDefault": 0.045
-        },
-        {
-          "name": "abt_gp_L1",
-          "unit": "mm",
-          "coefficient": 0.008521309,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "abt_gp_L2",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 38.1
-        },
-        {
-          "name": "hng_gp_L1",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "hng_gp_L2",
-          "unit": "mm",
-          "coefficient": 0.0,
-          "interceptTermDefault": 25.4
-        },
-        {
-          "name": "spn_ln",
-          "unit": "mm",
-          "coefficient": 0.000273989,
-          "interceptTermDefault": 13950.0
-        },
-        {
-          "name": "col_ht",
-          "unit": "mm",
-          "coefficient": 0.001510439,
-          "interceptTermDefault": 4070.0
-        },
-        {
-          "name": "width",
-          "unit": "ft",
-          "coefficient": 4.27e-05,
-          "interceptTermDefault": 6350.0
-        }
-      ],
-      "curveType": "Logit",
-      "description": "Complete"
-    }
-  ],
-  "description": "MSSS_Steel_Bridge-aba",
-  "paperReference": {
-    "name": "Seismic Fragility of Railway Bridge Classes:Methods, Models, and Comparison with the State of the Art",
-    "doi": "10.1061/(ASCE)BE.1943-5592.0001485.",
-    "yearPublished": "2019"
-  },
-  "authors": [
-    "Sushreyo Misra",
-    "Jamie Ellen Padgett"
-  ],
-  "resultType": "Limit State",
-  "hazardType": "earthquake",
-  "inventoryType": "bridge",
-  "creator": "cwang138",
-  "id": "5ed6bfc35b6166000155d0d9"
-}
\ No newline at end of file
diff --git a/tests/data/fragility_curves/PeriodBuildingFragilityCurve_original.json b/tests/data/fragility_curves/PeriodBuildingFragilityCurve_original.json
deleted file mode 100644
index ac9e00074..000000000
--- a/tests/data/fragility_curves/PeriodBuildingFragilityCurve_original.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
-  "className": "FragilitySet",
-  "legacyId": "SF_C1_10",
-  "hazardType": "earthquake",
-  "inventoryType": "building",
-  "authors": [
-    "J.M.Bracci",
-    "A.M.Reinhorn",
-    "J.B.Mander"
-  ],
-  "paperReference": {
-    "name": "NCEER-92-0027",
-    "yearPublished": "1992"
-  },
-  "description": "Gravity Load Designed Concrete Frames",
-  "resultType": "Limit State",
-  "demandTypes": [
-    "Sa"
-  ],
-  "demandUnits": [
-    "g"
-  ],
-  "fragilityCurves": [
-    {
-      "className": "PeriodBuildingFragilityCurve",
-      "periodEqnType": 3,
-      "periodParam1": 0.097,
-      "periodParam2": 0.624,
-      "periodParam0": 13.0,
-      "fsParam0": -1.4297,
-      "fsParam1": -0.5882,
-      "fsParam2": 0.4863,
-      "fsParam3": 0.0237,
-      "fsParam4": 33.6713,
-      "fsParam5": 11.7658,
-      "description": "Immediate Occupancy"
-    },
-    {
-      "className": "PeriodBuildingFragilityCurve",
-      "periodEqnType": 3,
-      "periodParam1": 0.097,
-      "periodParam2": 0.624,
-      "periodParam0": 13.0,
-      "fsParam0": -1.0435,
-      "fsParam1": -0.5021,
-      "fsParam2": 0.3111,
-      "fsParam3": 0.0851,
-      "fsParam4": 12.533,
-      "fsParam5": 3.892,
-      "description": "Life Safety"
-    },
-    {
-      "className": "PeriodBuildingFragilityCurve",
-      "periodEqnType": 3,
-      "periodParam1": 0.097,
-      "periodParam2": 0.624,
-      "periodParam0": 13.0,
-      "fsParam0": -0.8408,
-      "fsParam1": -0.35,
-      "fsParam2": 0.3651,
-      "fsParam3": 0.0631,
-      "fsParam4": 6.4062,
-      "fsParam5": 2.6323,
-      "description": "Collapse Prevention"
-    }
-  ],
-  "creator": "incore",
-  "id": "5b47b2d8337d4a36187c6c05"
-}
\ No newline at end of file
diff --git a/tests/data/fragility_curves/PeriodStandardFragilityCurve_original.json b/tests/data/fragility_curves/PeriodStandardFragilityCurve_original.json
deleted file mode 100644
index 5ae8633e9..000000000
--- a/tests/data/fragility_curves/PeriodStandardFragilityCurve_original.json
+++ /dev/null
@@ -1,57 +0,0 @@
-{
-  "className": "FragilitySet",
-  "legacyId": "SF_S1_212",
-  "hazardType": "earthquake",
-  "inventoryType": "building",
-  "authors": [
-    "Elnashai and Jeong"
-  ],
-  "description": "Mid-Rise Steel Moment Frame",
-  "resultType": "Limit State",
-  "demandTypes": [
-    "0.2 sec Sa"
-  ],
-  "demandUnits": [
-    "g"
-  ],
-  "fragilityCurves": [
-    {
-      "className": "PeriodStandardFragilityCurve",
-      "periodParam2": 0.0,
-      "periodParam1": 0.0,
-      "periodParam0": 1.08,
-      "periodEqnType": 1,
-      "beta": 0.836,
-      "curveType": "LogNormal",
-      "description": "Moderate",
-      "alpha": -0.576,
-      "alphaType": "lambda"
-    },
-    {
-      "className": "PeriodStandardFragilityCurve",
-      "periodParam2": 0.0,
-      "periodParam1": 0.0,
-      "periodParam0": 1.08,
-      "periodEqnType": 1,
-      "beta": 0.911,
-      "curveType": "LogNormal",
-      "description": "Extensive",
-      "alpha": 0.23,
-      "alphaType": "lambda"
-    },
-    {
-      "className": "PeriodStandardFragilityCurve",
-      "periodParam2": 0.0,
-      "periodParam1": 0.0,
-      "periodParam0": 1.08,
-      "periodEqnType": 1,
-      "beta": 1.05,
-      "curveType": "LogNormal",
-      "description": "Complete",
-      "alpha": 1.197,
-      "alphaType": "lambda"
-    }
-  ],
-  "creator": "incore",
-  "id": "5b47b2d7337d4a36187c61c9"
-}
\ No newline at end of file
diff --git a/tests/data/fragility_curves/StandardFragilityCurve_original.json b/tests/data/fragility_curves/StandardFragilityCurve_original.json
deleted file mode 100644
index 81369ebc1..000000000
--- a/tests/data/fragility_curves/StandardFragilityCurve_original.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
-  "className": "FragilitySet",
-  "demandTypes": [
-    "momentumFlux"
-  ],
-  "demandUnits": [
-    "ft^3/s^2"
-  ],
-  "fragilityCurves": [
-    {
-      "className": "StandardFragilityCurve",
-      "alpha": 1170.0,
-      "beta": 0.79,
-      "alphaType": "median",
-      "curveType": "LogNormal",
-      "description": "Moderate"
-    },
-    {
-      "className": "StandardFragilityCurve",
-      "alpha": 3259.0,
-      "beta": 0.79,
-      "alphaType": "median",
-      "curveType": "LogNormal",
-      "description": "Extensive"
-    },
-    {
-      "className": "StandardFragilityCurve",
-      "alpha": 5347.0,
-      "beta": 0.79,
-      "alphaType": "median",
-      "curveType": "LogNormal",
-      "description": "Complete"
-    }
-  ],
-  "description": "Concrete Moment frame 4 to 7",
-  "authors": [
-    "FEMA"
-  ],
-  "resultType": "Limit State",
-  "hazardType": "tsunami",
-  "inventoryType": "building",
-  "creator": "incrtest",
-  "id": "5fb5984e96513b1f4bd7a0a3"
-}
\ No newline at end of file
diff --git a/tests/pyincore/analyses/joplinempiricalrestoration/test_joplinempiricalrestoration.py b/tests/pyincore/analyses/joplinempiricalrestoration/test_joplinempiricalrestoration.py
new file mode 100644
index 000000000..c85d8db50
--- /dev/null
+++ b/tests/pyincore/analyses/joplinempiricalrestoration/test_joplinempiricalrestoration.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+from pyincore import IncoreClient, FragilityService, MappingSet
+from pyincore.analyses.buildingdamage import BuildingDamage
+from pyincore.analyses.joplinempiricalrestoration import JoplinEmpiricalRestoration
+import pyincore.globals as pyglobals
+
+
+def run_with_base_class():
+    client = IncoreClient(pyglobals.INCORE_API_DEV_URL)
+
+    # Joplin tornado building damage
+    bldg_dataset_id = "5df7d0de425e0b00092d0082"  # joplin building v6
+
+    bldg_dmg = BuildingDamage(client)
+    bldg_dmg.load_remote_input_dataset("buildings", bldg_dataset_id)
+
+    mapping_id = "5e8e3a21eaa8b80001f04f1c"  # 19 archetype, non-retrofit
+    fragility_service = FragilityService(client)
+    mapping_set = MappingSet(fragility_service.get_mapping(mapping_id))
+    bldg_dmg.set_input_dataset("dfr3_mapping_set", mapping_set)
+    bldg_dmg.set_parameter("fragility_key", "Non-Retrofit Fragility ID Code")
+
+    # The simulated EF-5 tornado shows geographical locations and the range of wind speed
+    # of tornado hazard in Joplin.
+    hazard_type = "tornado"
+    hazard_id = "5dfa32bbc0601200080893fb"
+
+    result_name = "joplin_tornado_dmg_result"
+    bldg_dmg.set_parameter("result_name", result_name)
+    bldg_dmg.set_parameter("hazard_type", hazard_type)
+    bldg_dmg.set_parameter("hazard_id", hazard_id)
+    bldg_dmg.set_parameter("num_cpu", 4)
+    bldg_dmg.set_parameter("seed", 1000)
+    bldg_dmg.run_analysis()
+    # end of Building damage analysis
+
+    # get csv results from Building damage analysis
+    building_dmg_result = bldg_dmg.get_output_dataset("ds_result")
+    building_dmg_result.get_dataframe_from_csv()
+
+    restoration = JoplinEmpiricalRestoration(client)
+
+    restoration.load_remote_input_dataset("buildings", bldg_dataset_id)
+    # restoration.load_remote_input_dataset("building_dmg", building_dmg_result)
+    restoration.set_input_dataset("building_dmg", building_dmg_result)
+
+    result_name = "Joplin_empirical_restoration_result"
+    restoration.set_parameter("result_name", result_name)
+    restoration.set_parameter("target_functionality_level", 0)
+    # restoration.set_parameter("seed", 1234)
+
+    # Run Analysis
+    restoration.run_analysis()
+
+
+if __name__ == '__main__':
+    run_with_base_class()
diff --git a/tests/pyincore/analyses/populationdislocation/test_populationdislocation.py b/tests/pyincore/analyses/populationdislocation/test_populationdislocation.py
index 5737c0493..205f0a931 100644
--- a/tests/pyincore/analyses/populationdislocation/test_populationdislocation.py
+++ b/tests/pyincore/analyses/populationdislocation/test_populationdislocation.py
@@ -14,8 +14,7 @@ def run_with_base_class():
     # incore-dev
     building_dmg = "602d96e4b1db9c28aeeebdce"  # dev Joplin
     # building_dmg = "602d975db1db9c28aeeebe35" # 15 guids test - dev Joplin
-    housing_unit_alloc = "5df7c989425e0b00092c5eb4"  # dev Joplin
-    # housing_unit_alloc = "602ea965b1db9c28aeefa5d6" # 23 address ids test - dev Joplin
+    housing_unit_alloc = "61563545483ecb19e4304c2a"  # dev Joplin
     bg_data = "5df7cb0b425e0b00092c9464"  # Joplin 2ev2
     value_loss = "602d508fb1db9c28aeedb2a5"
 
diff --git a/tests/pyincore/models/test_dataset.py b/tests/pyincore/models/test_dataset.py
new file mode 100644
index 000000000..4ae2fd8fc
--- /dev/null
+++ b/tests/pyincore/models/test_dataset.py
@@ -0,0 +1,21 @@
+import pandas as pd
+
+from pyincore.dataset import Dataset
+
+
+def test_from_dataframe():
+    df = pd.DataFrame()
+    dataset = Dataset.from_csv_data(df, "empty.csv", "ergo:buildingDamageVer6")
+    assert dataset.data_type == "ergo:buildingDamageVer6"
+
+
+def test_from_csv_data():
+    result_data = []
+    dataset = Dataset.from_csv_data(result_data, "empty.csv", "ergo:buildingDamageVer6")
+    assert dataset.data_type == "ergo:buildingDamageVer6"
+
+
+def test_from_json_data():
+    result_data = {}
+    dataset = Dataset.from_json_data(result_data, "empty.json", "incore:buildingDamageSupplement")
+    assert dataset.data_type == "incore:buildingDamageSupplement"
diff --git a/tests/pyincore/models/test_fragilitycurverefactored.py b/tests/pyincore/models/test_fragilitycurverefactored.py
index bf4cb96ad..39934c5fa 100644
--- a/tests/pyincore/models/test_fragilitycurverefactored.py
+++ b/tests/pyincore/models/test_fragilitycurverefactored.py
@@ -71,54 +71,7 @@ def test_create_fragility_set():
      0.8097974088),
 ])
 def test_calculate_limit_state_probability(fragility_set, hazard_values, args, expected):
-    result = fragility_set.calculate_limit_state_refactored_w_conversion(hazard_values, **args)
+    result = fragility_set.calculate_limit_state(hazard_values, **args)
     assert np.isclose(result["LS_0"], expected)
 
 
-@pytest.mark.parametrize("curve, hazard_val, refactored_curve, hazard_val_refactored, num_stories, inventory_type", [
-    ("fragility_curves/ConditionalStandardFragilityCurve_original.json", 4,
-     "fragility_curves/ConditionalStandardFragilityCurve_refactored.json", {"Vmax": 4}, 1, "electric_facility"),
-    ("fragility_curves/ParametricFragilityCurve_original.json", 4,
-     "fragility_curves/ParametricFragilityCurve_refactored.json", {"PGA": 4}, 1, "bridge"),
-    ("fragility_curves/PeriodBuildingFragilityCurve_original.json", 0.05,
-     "fragility_curves/PeriodBuildingFragilityCurve_refactored.json", {"Sa": 0.05}, 6, "building"),
-    ("fragility_curves/PeriodStandardFragilityCurve_original.json", 4,
-     "fragility_curves/PeriodStandardFragilityCurve_refactored.json", {"0.2 sec Sa": 4}, 6, "building"),
-    ("fragility_curves/StandardFragilityCurve_original.json", 4,
-     "fragility_curves/StandardFragilityCurve_refactored.json", {"momentumFlux": 4}, 1, "building"),
-])
-def test_curves_results(curve, hazard_val, refactored_curve, hazard_val_refactored, num_stories, inventory_type):
-    fragility_set = get_fragility_set(curve)
-    refactored_fragility_set = get_fragility_set(refactored_curve)
-
-    # add period if applicable
-
-    building_period = fragility_set.fragility_curves[0].get_building_period(num_stories)
-    if len(fragility_set.fragility_curves) <= 4:
-        result = fragility_set.calculate_limit_state_w_conversion(hazard_val, period=building_period,
-                                                                  inventory_type=inventory_type)
-        refactored_result = refactored_fragility_set.calculate_limit_state_refactored_w_conversion(
-            hazard_val_refactored,
-            num_stories=num_stories,
-            inventory_type=inventory_type
-        )
-
-        assert result == refactored_result
-
-    # no longer handle fragility curves > 4, test if can catch this error
-    else:
-        with pytest.raises(ValueError):
-            refactored_result = refactored_fragility_set.calculate_limit_state_refactored_w_conversion(
-                hazard_val_refactored,
-                num_stories=1)
-
-
-# @pytest.mark.parametrize("fragility_set,args,expected", [
-#     (get_remote_fragility_set("5b47b2d7337d4a36187c61c9"), {}, 1.08),
-#     # 	"(0.097) * math.pow(num_stories * (13.0), 0.624)"
-#     (get_remote_fragility_set("5b47b2d8337d4a36187c6c05"), {"num_stories": 2}, 0.7408241022436427),
-#     (get_remote_fragility_set("5b47b2d8337d4a36187c6c05"), {}, 0.4806980784822461),
-# ])
-# def test_get_building_period(fragility_set, args, expected):
-#     fragility_curve = fragility_set.fragility_curves[0]
-#     assert fragility_curve.get_building_period(fragility_set.fragility_curve_parameters, **args) == expected
diff --git a/tests/pyincore/test_hazardservice.py b/tests/pyincore/test_hazardservice.py
index a7e72f77a..9b0ce0c98 100644
--- a/tests/pyincore/test_hazardservice.py
+++ b/tests/pyincore/test_hazardservice.py
@@ -4,9 +4,9 @@
 # terms of the Mozilla Public License v2.0 which accompanies this distribution,
 # and is available at https://www.mozilla.org/en-US/MPL/2.0/
 import os
-
 import numpy as np
 import pytest
+import requests.exceptions
 
 from pyincore import globals as pyglobals
 
@@ -70,6 +70,42 @@ def test_post_earthquake_hazard_values(hazardsvc):
                                                2.5884360071121133, 34.445240752324956]
 
 
+def test_bad_units_post_earthquake_hazard_values(hazardsvc):
+    payload = [
+        {
+            "demands": ["1.0 SD", "0.2 SA"],
+            "units": ["cm", "zzz"],
+            "loc": "35.84,-89.90"
+        }
+    ]
+    response = hazardsvc.post_earthquake_hazard_values(
+        "5b902cb273c3371e1236b36b",
+        payload
+    )
+
+    assert len(response) == len(payload) and response[0]['hazardValues'][1] == -9999.3
+
+
+def test_bad_format_post_earthquake_hazard_values(hazardsvc):
+    payload = [
+        {
+            "demands": ["1.0 SD", "0.2 SA"],
+            "units": ["cm", "g"],
+            "loc": "35.84-89.90"
+        }
+    ]
+
+    try:
+        hazardsvc.post_earthquake_hazard_values(
+            "5b902cb273c3371e1236b36b",
+            payload
+        )
+    except requests.exceptions.HTTPError as e:
+        assert e.response.status_code == 400
+    else:
+        assert False
+
+
 def test_get_liquefaction_values(hazardsvc):
     liq_vals = hazardsvc.get_liquefaction_values("5b902cb273c3371e1236b36b",
                                                  "5a284f53c7d30d13bc08249c",
diff --git a/tests/pyincore/analyses/joplincge/test_joplincge_json.py b/tests/pyincore/utils/test_cgecsvoutputjson.py
similarity index 62%
rename from tests/pyincore/analyses/joplincge/test_joplincge_json.py
rename to tests/pyincore/utils/test_cgecsvoutputjson.py
index ffe5c4013..637f4af5a 100644
--- a/tests/pyincore/analyses/joplincge/test_joplincge_json.py
+++ b/tests/pyincore/utils/test_cgecsvoutputjson.py
@@ -1,14 +1,19 @@
+# This program and the accompanying materials are made available under the
+# terms of the Mozilla Public License v2.0 which accompanies this distribution,
+# and is available at https://www.mozilla.org/en-US/MPL/2.0/
+
 from pyincore import IncoreClient
 from pyincore.analyses.joplincge import JoplinCGEModel
 from pyincore.utils.cgeoutputprocess import CGEOutputProcess
 import pyincore.globals as pyglobals
+import os
 
 
 # This script runs JoplinCGEModel analysis with input files from
 # IN-CORE development services. The output csv files are converted to json
 # format suitable for the IN-CORE Playbook tool.
 
-def run_base_analysis():
+def run_convert_cge_json_chained():
     client = IncoreClient(pyglobals.INCORE_API_DEV_URL)
     joplin_cge = JoplinCGEModel(client)
 
@@ -59,16 +64,37 @@ def run_base_analysis():
     cge_json.get_cge_gross_income(gross_income_result, None, "cge_total_household_income.json")
     cge_json.get_cge_employment(pre_demand_result, post_demand_result, None, None, "cge_employment.json")
 
-    # Alternatively, you can run the output conversion with files on the file system.
-    # my_path = "PATH_TO_PYINCORE/pyincore/tests/pyincore/analyses/joplincge/"
-    # cge_json.get_cge_household_count(None, my_path + "household-count.csv", "cge_total_household_count.json")
-    # cge_json.get_cge_gross_income(None, my_path + "gross-income.csv", "cge_total_household_income.json")
-    # cge_json.get_cge_employment(None, None,
-    #                             my_path + "pre-disaster-factor-demand.csv",
-    #                             my_path + "post-disaster-factor-demand.csv",
-    #                             "cge_employment.json")
-    # cge_json.get_cge_domestic_supply(None, my_path + "domestic-supply.csv", "cge_domestic_supply.json")
+    return True
+
+
+def run_convert_cge_json_path(testpath):
+    # test the external file with a path
+
+    cge_json = CGEOutputProcess()
+    cge_json.get_cge_household_count(None,
+                                     os.path.join(testpath, "household-count.csv"),
+                                     "cge_total_household_count.json")
+    cge_json.get_cge_gross_income(None,
+                                  os.path.join(testpath, "gross-income.csv"),
+                                  "cge_total_household_income.json")
+    cge_json.get_cge_employment(None, None,
+                                os.path.join(testpath, "pre-disaster-factor-demand.csv"),
+                                os.path.join(testpath, "post-disaster-factor-demand.csv"),
+                                "cge_employment.json")
+    cge_json.get_cge_domestic_supply(None,
+                                     os.path.join(testpath, "domestic-supply.csv"),
+                                     "cge_domestic_supply.json")
+    return True
 
 
 if __name__ == '__main__':
-    run_base_analysis()
+    # test chaining with Joplin CGE analysis
+    run_convert_cge_json_chained()
+
+    # test the external file with a path
+    testpath = ""
+    # testpath = "/Users/<user>/<path_to_pyincore>/pyincore/tests/pyincore/utils"
+    if testpath:
+        run_convert_cge_json_path(testpath)
+
+    print("DONE")
diff --git a/tests/pyincore/utils/test_popdisloutputprocess.py b/tests/pyincore/utils/test_popdisloutputprocess.py
new file mode 100644
index 000000000..16784c30b
--- /dev/null
+++ b/tests/pyincore/utils/test_popdisloutputprocess.py
@@ -0,0 +1,98 @@
+# This program and the accompanying materials are made available under the
+# terms of the Mozilla Public License v2.0 which accompanies this distribution,
+# and is available at https://www.mozilla.org/en-US/MPL/2.0/
+
+from pyincore import PopDislOutputProcess
+from pyincore import IncoreClient, DataService, SpaceService
+from pyincore.analyses.populationdislocation import PopulationDislocation
+import pyincore.globals as pyglobals
+import os
+
+
+def upload_shapefile_to_services(client):
+    # Upload to incore services and put under commresilience space
+    # It assumes the shapefile is in the utils directory
+    datasvc = DataService(client)
+    dataset_prop = {
+        "title": "Joplin Population Dislocation For Heatmap Plotting",
+        "description": "Contains only dislocated numprec for Joplin playbook plotting usage",
+        "contributors": [],
+        "dataType": "incore:popdislocationShp",
+        "storedUrl": "",
+        "format": "shapefile"
+    }
+    response = datasvc.create_dataset(dataset_prop)
+    dataset_id = response['id']
+    files = ['joplin-pop-disl-numprec.shp',
+             'joplin-pop-disl-numprec.dbf',
+             'joplin-pop-disl-numprec.shx',
+             'joplin-pop-disl-numprec.prj']
+    datasvc.add_files_to_dataset(dataset_id, files)
+
+    # add to space
+    spacesvc = SpaceService(client)
+    spacesvc.add_dataset_to_space("5f99ba8b0ace240b22a82e00", dataset_id=dataset_id)  # commresilience
+    print(dataset_id + " successfully uploaded and move to commresilience space!")
+
+
+def run_convert_pd_json_chained(client):
+    # Joplin population dislocation
+    # incore-dev
+    building_dmg = "602d96e4b1db9c28aeeebdce"  # dev Joplin
+    # building_dmg = "602d975db1db9c28aeeebe35" # 15 guids test - dev Joplin
+    housing_unit_alloc = "61563545483ecb19e4304c2a"  # dev Joplin 2ev3
+    bg_data = "5df7cb0b425e0b00092c9464"  # Joplin 2ev2
+    value_loss = "602d508fb1db9c28aeedb2a5"
+
+    result_name = "joplin-pop-disl-results"
+    seed = 1111
+
+    pop_dis = PopulationDislocation(client)
+
+    pop_dis.load_remote_input_dataset("building_dmg", building_dmg)
+    pop_dis.load_remote_input_dataset("housing_unit_allocation", housing_unit_alloc)
+    pop_dis.load_remote_input_dataset("block_group_data", bg_data)
+    pop_dis.load_remote_input_dataset("value_poss_param", value_loss)
+
+    pop_dis.set_parameter("result_name", result_name)
+    pop_dis.set_parameter("seed", seed)
+
+    pop_dis.run_analysis()
+    pd_result = pop_dis.get_output_dataset("result")
+
+    return pd_result
+
+
+if __name__ == '__main__':
+    # test chaining with population dislocation
+    client = IncoreClient(pyglobals.INCORE_API_DEV_URL)
+
+    pd_result = run_convert_pd_json_chained(client)
+
+    pd_process = PopDislOutputProcess(pd_result)
+
+    pd_process.pd_by_race("PD_by_race.json")
+    pd_process.pd_by_income("PD_by_income.json")
+    pd_process.pd_by_tenure("PD_by_tenure.json")
+    pd_process.pd_by_housing("PD_by_housing.json")
+    pd_process.pd_total("PD_by_total.json")
+
+    filename = pd_process.get_heatmap_shp("joplin-pop-disl-numprec.shp")
+    print("Test chaining", filename)
+    # upload_shapefile_to_services(client)
+
+    # test the external file with a path
+    testpath = ""
+    # testpath = "/Users/<user>/<path_to_pyincore>/pyincore/tests/pyincore/utils"
+    if testpath:
+        pd_process = PopDislOutputProcess(None, os.path.join(testpath, "joplin-pop-disl-results.csv"))
+
+        pd_process.pd_by_race("PD_by_race.json")
+        pd_process.pd_by_income("PD_by_income.json")
+        pd_process.pd_by_tenure("PD_by_tenure.json")
+        pd_process.pd_by_housing("PD_by_housing.json")
+        pd_process.pd_total("PD_by_total.json")
+
+        filename = pd_process.get_heatmap_shp("joplin-pop-disl-numprec.shp")
+        print("Test path", filename)
+    print("DONE")
diff --git a/tests/test_format.py b/tests/test_format.py
index a9dd3585e..a41de7708 100644
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -49,7 +49,16 @@
     os.path.join(PYINCORE_ROOT_FOLDER, 'pyincore/analyses/populationdislocation/'),
     os.path.join(PYINCORE_ROOT_FOLDER,
                  'tests/pyincore/analyses/cumulativebuildingdamage/test_cumulativebuildingdamage.py'),
-    os.path.join(PYINCORE_ROOT_FOLDER, 'tests/pyincore/analyses/populationdislocation/test_populationdislocation.py')
+    os.path.join(PYINCORE_ROOT_FOLDER, 'tests/pyincore/analyses/populationdislocation/test_populationdislocation.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER, 'tests/pyincore/models/test_dataset.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER, 'pyincore/utils/cgeoutputprocess.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER, 'tests/pyincore/utils/test_cgecsvoutputjson.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER, 'pyincore/analyses/joplinempiricalrestoration/joplinempiricalrestoration.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER, 'pyincore/analyses/joplinempiricalrestoration/joplinempirrestor_util.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER,
+                 'tests/pyincore/analyses/joplinempiricalrestoration/test_joplinempiricalrestoration.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER, 'tests/pyincore/analyses/populationdislocation/test_populationdislocation.py'),
+    os.path.join(PYINCORE_ROOT_FOLDER, 'tests/pyincore/utils/test_popdisloutputprocess.py')
 ]