Repository: NREL/OpenStudio (language: python)

Issue:
Broken transition rules for OS:Coil:Heating:Gas:MultiStage
Issue overview
--------------

Reg test multi_stage.osm is failing because this ends up blank: https://github.com/NREL/OpenStudio-resources/blob/7ee4b1be25a7794651e28be6836119d7d69a166e/model/simulationtests/multi_stage.osm#L1667

This is only a problem on develop, not in any release.

Broken transition rules for OS:Coil:Heating:Gas:MultiStage
Issue overview
--------------

Reg test multi_stage.osm is failing because this ends up blank: https://github.com/NREL/OpenStudio-resources/blob/7ee4b1be25a7794651e28be6836119d7d69a166e/model/simulationtests/multi_stage.osm#L1667

This is only a problem on develop, not in any release.

E+ 22.2: ZoneHVAC:PackagedTerminal AirConditionner/HeatPump requires a supply air fan operating mode schedule
Issue overview
--------------

In 22.2.0, there’s a new error in ZoneHVAC:PackagedTerminal:AC/HP if you don’t supply a Supply Air Fan Operating Mode Schedule when you have a Fan:ConstantVolume

```
   ** Severe  ** ZoneHVAC:PackagedTerminalAirConditioner = ZONE HVAC PACKAGED TERMINAL AIR CONDITIONER 4
   **   ~~~   ** Fan type must be Fan:OnOff or Fan:SystemModel when Supply Air Fan Operating Mode Schedule Name is blank
```

Except:
* if I pass a Always 0 schedule, there’s another error saying value must be > 0 for Fan:CV. 
* If I pass Always 1 schedule, my EUI changes quite dramatically. 
* If I use a Fan:OnOff with always 0, my EUI is closer but still different.

We need a way to handle this so we don't end up with too broken models (lots are failing on OS-resources right now) and ideally without changing EUI too drastically.

cf https://github.com/NREL/OpenStudio/commit/9abf9e42edbe75ab01a160f3ba61fa1dd25dea89#diff-6792272e2d22af45d561660c93bc735d74a30b2a15218ed60d1b05a91eeca176R310


## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->

## Expected Behavior
<!--- Tell us what should happen -->

## Steps to Reproduce
<!-- Provide an unambiguous set of steps to reproduce this bug. -->
<!--- Provide a link to a model that has the bug (a minimum complete and verifiable example (MCVE) is preferred) and/or code to reproduce -->
1.
2.
3.
4.

## Possible Solution
<!--- Optional, but if you can, suggest a fix/reason for the bug, -->

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Platform (Operating system, version):
 - Version of OpenStudio (if using an intermediate build, include SHA):

### Context
<!--- How has this issue affected you? What are you trying to accomplish? -->

Addresses #4646, wrap E+ 22.2 new supplemental heating coil type for AirLoopHVAC:UnitarySystem object
Pull request overview
---------------------

 - Fixes #4646
 - Also adds model and FT tests for `Coil:Heating:Gas:MultiStage` (appears possibly some issues with FT from `AirLoopHVAC:UnitarySystem` and `AirLoopHVAC:UnitaryHeatPump:AirToAir:MultiSpeed`)
 - Breaks API for `Coil:Heating:Gas:MultiStage` by making Availability Schedule required. Creates VT rules to handle this.

### Pull Request Author

<!--- Add to this list or remove from it as applicable.  This is a simple templated set of guidelines. -->

 - [x] Model API Changes / Additions
 - [x] Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
 - [x] Model API methods are tested (in `src/model/test`)
 - [x] EnergyPlus ForwardTranslator Tests (in `src/energyplus/Test`)
 - [x] If a new object or method, added a test in NREL/OpenStudio-resources: https://github.com/NREL/OpenStudio-resources/pull/176
 - [x] If needed, added VersionTranslation rules for the objects (`src/osversion/VersionTranslator.cpp`)
 - [x] Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
 - [x] All new and existing tests passes
 - [x] If methods have been deprecated, update rest of code to use the new methods

**Labels:**

 - [x] If change to an IDD file, add the label `IDDChange`
 - [x] If breaking existing API, add the label `APIChange`
 - [x] If deemed ready, add label `Pull Request - Ready for CI` so that CI builds your PR

### Review Checklist

This will not be exhaustively relevant to every PR.
 - [x] Perform a Code Review on GitHub
 - [x] Code Style, strip trailing whitespace, etc.
 - [x] All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
 - [x] Labeling is ok
 - [x] If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
 - [x] If feature, test running new feature, try creative ways to break it
 - [x] CI status: all green or justified



Discussion / hints:


The change is due to https://github.com/NREL/EnergyPlus/pull/9273

I took resources/EnergyPlus/Office_HVAC_With_Many_Types.idf and ran some combinations on both 22.1.0 and 22.2.0

<img width="759" alt="image" src="https://user-images.githubusercontent.com/5479063/192811493-23e2b83c-a513-47b8-bed2-8f599d5a8f70.png">

I also discussed it with some members of E+ dev team. Seems like a good idea would be to do

```
If a PTAC had a FanConstantVolume and the op schedule is blank:
    Replace PTAC Fan with FanSystemModel Discrete (=Model a Fan:OnOff) copying inputs where they map
    Set PTAC fan Op schedule to alwaysOff
```
Reg tests right now:
```
Success    250
Fail        67
Name: (22.2.0, 3.5.0-alpha, 515b977a2c), dtype: int64
```
CI Results for a948f15c574f1ddd472103a993d4d4cf99dcc408:

* Ubuntu 18.04:
* Ubuntu 20.04:
    * [OpenStudio-3.5.0-alpha+a948f15c57-Ubuntu-20.04.deb](http://openstudio-ci-builds.s3-website-us-west-2.amazonaws.com/PR-4666/OpenStudio-3.5.0-alpha%2Ba948f15c57-Ubuntu-20.04.deb)
    * [OpenStudio-3.5.0-alpha+a948f15c57-Ubuntu-20.04.tar.gz](http://openstudio-ci-builds.s3-website-us-west-2.amazonaws.com/PR-4666/OpenStudio-3.5.0-alpha%2Ba948f15c57-Ubuntu-20.04.tar.gz)
    * [perf_test/results.html](https://openstudio-ci-builds.s3-us-west-2.amazonaws.com/PR-4666/a948f15c574f1ddd472103a993d4d4cf99dcc408/perf_test/results.html)

* Darwin:
    * [OpenStudio-3.5.0-alpha+a948f15c57-Darwin-x86_64.dmg](http://openstudio-ci-builds.s3-website-us-west-2.amazonaws.com/PR-4666/OpenStudio-3.5.0-alpha+a948f15c57-Darwin-x86_64.dmg)
    * [OpenStudio-3.5.0-alpha+a948f15c57-Darwin-x86_64.tar.gz](http://openstudio-ci-builds.s3-website-us-west-2.amazonaws.com/PR-4666/OpenStudio-3.5.0-alpha+a948f15c57-Darwin-x86_64.tar.gz)

* Windows:
* Code Coverage:
@joseph-robertson I pushed a few changes. I put the CoilHeatingElectricMultiStageStageData and CoilHeatingGasMultiStageStageData back to ModelObject. I extended testing by quite a bit. I fixed the clone method for both. This can merge as far as I'm concerned. I also adjusted https://github.com/NREL/OpenStudio-resources/pull/176 (and added it to the the PR Author checklist  in your original post for clarity)

Fix patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0683de4fb52..353394a6506 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -191,11 +191,14 @@ set(ENERGYPLUS_VERSION_MINOR 2)
 set(ENERGYPLUS_VERSION_PATCH 0)
 set(ENERGYPLUS_VERSION "${ENERGYPLUS_VERSION_MAJOR}.${ENERGYPLUS_VERSION_MINOR}.${ENERGYPLUS_VERSION_PATCH}")
 # Build SHA is not required to have a value, but if it does OpenStudio will require this build.
-set(ENERGYPLUS_BUILD_SHA "c249759bad")
+set(ENERGYPLUS_BUILD_SHA "aa78da9668")
 
 # ENERGYPLUS_RELEASE_NAME is used to locate the E+ download
 # from the github releases
-set(ENERGYPLUS_RELEASE_NAME "v22.2.0")
+set(ENERGYPLUS_RELEASE_NAME "v22.2.0-WithIHGFix")
+
+# TODO: Temporary change to a fork until a patched release is issued on NREL's
+set(ENERGYPLUS_REPO "jmarrec")
 
 # Radiance
 set(RADIANCE_VERSION "5.0.a.12")
@@ -579,21 +582,21 @@ endif()
 if(UNIX)
   if(APPLE)
     if (ARCH MATCHES "arm64")
-      set(ENERGYPLUS_EXPECTED_HASH 48af06ee5b4b0393444e6e57f4adadd5)
+      set(ENERGYPLUS_EXPECTED_HASH 35ba21891e7059987391f1ab59958025)
       set(ENERGYPLUS_PLATFORM "Darwin-macOS12.1-arm64")
     else()
-      set(ENERGYPLUS_EXPECTED_HASH 92a76c5d60cf91536bdeacbe780d135a)
+      set(ENERGYPLUS_EXPECTED_HASH 481fafde9496735d809e810bd95b720f)
       set(ENERGYPLUS_PLATFORM "Darwin-macOS10.15-x86_64")
     endif()
   elseif(LSB_RELEASE_ID_SHORT MATCHES "CentOS")
     # TODO: There aren't any redhat releases anymore, see PR #3145 too
-    set(ENERGYPLUS_EXPECTED_HASH ebf23ac4f54a6c4f9ac0da561de4196e)
+    set(ENERGYPLUS_EXPECTED_HASH ebf23ac4f54a6c4f9ac0da561de4196e)   # TODO: not updated yet
     set(ENERGYPLUS_PLATFORM "Centos7-x86_64")
   else()
     if(LSB_RELEASE_VERSION_SHORT MATCHES "20.04")
-      set(ENERGYPLUS_EXPECTED_HASH f8a45aa0a01523b5f904d5c2ae8302f8)
+      set(ENERGYPLUS_EXPECTED_HASH fd2b9e55a15227f872601e9e57ffe819)
     else() # 18.04
-      set(ENERGYPLUS_EXPECTED_HASH 58b8397f55c2a1ede7ed08c5892e62ed)
+      set(ENERGYPLUS_EXPECTED_HASH 05a97ef513fa3f2ac7678bd9eef026db)
     endif()
     set(ENERGYPLUS_PLATFORM "Linux${ENERGYPLUS_SYSTEM_VERSION}-x86_64")
   endif()
@@ -603,7 +606,7 @@ if(UNIX)
   endif()
   if(NOT EXISTS "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.tar.gz" OR NOT "${ENERGYPLUS_HASH}" MATCHES "${ENERGYPLUS_EXPECTED_HASH}")
     message(STATUS "Downloading EnergyPlus ${ENERGYPLUS_VERSION} (${ENERGYPLUS_PLATFORM})")
-    message(STATUS "https://github.com/NREL/EnergyPlus/releases/download/${ENERGYPLUS_RELEASE_NAME}/${ENERGYPLUS_PATH}.tar.gz") # TODO: Temp: custom build on my fork
+    message(STATUS "https://github.com/${ENERGYPLUS_REPO}/EnergyPlus/releases/download/${ENERGYPLUS_RELEASE_NAME}/${ENERGYPLUS_PATH}.tar.gz")
 
     if(LSB_RELEASE_ID_SHORT MATCHES "CentOS")
       file(DOWNLOAD "http://openstudio-resources.s3.amazonaws.com/dependencies/${ENERGYPLUS_PATH}.tar.gz" "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.tar.gz"
@@ -611,7 +614,7 @@ if(UNIX)
            SHOW_PROGRESS
            EXPECTED_MD5 ${ENERGYPLUS_EXPECTED_HASH})
     else()
-      file(DOWNLOAD "https://github.com/NREL/EnergyPlus/releases/download/${ENERGYPLUS_RELEASE_NAME}/${ENERGYPLUS_PATH}.tar.gz" "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.tar.gz"
+      file(DOWNLOAD "https://github.com/${ENERGYPLUS_REPO}/EnergyPlus/releases/download/${ENERGYPLUS_RELEASE_NAME}/${ENERGYPLUS_PATH}.tar.gz" "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.tar.gz"
            INACTIVITY_TIMEOUT 300 # 5-min timeout
            SHOW_PROGRESS
            EXPECTED_MD5 ${ENERGYPLUS_EXPECTED_HASH})
@@ -625,11 +628,11 @@ elseif(WIN32)
   if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64 bit
     set(ENERGYPLUS_PATH "EnergyPlus-${ENERGYPLUS_VERSION}-${ENERGYPLUS_BUILD_SHA}-Windows-x86_64")
     set(ENERGYPLUS_ARCH 64)
-    set(ENERGYPLUS_EXPECTED_HASH c6d296be45420d6e497f33727f044676)
+    set(ENERGYPLUS_EXPECTED_HASH 8611314c5056abedca59144627e79078)
   else()
     set(ENERGYPLUS_PATH "EnergyPlus-${ENERGYPLUS_VERSION}-${ENERGYPLUS_BUILD_SHA}-Windows-i386")
     set(ENERGYPLUS_ARCH 32)
-    set(ENERGYPLUS_EXPECTED_HASH 68108677fceb17bd58be1c9c7a68f418)
+    set(ENERGYPLUS_EXPECTED_HASH 76afec50c80fc4b53f2f4da572573688)
   endif()
   if(EXISTS "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.zip")
     file(MD5 "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.zip" ENERGYPLUS_HASH)
@@ -637,7 +640,7 @@ elseif(WIN32)
   if(NOT EXISTS "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.zip" OR NOT "${ENERGYPLUS_HASH}" MATCHES "${ENERGYPLUS_EXPECTED_HASH}")
     message(STATUS "Downloading EnergyPlus ${ENERGYPLUS_VERSION} (${ENERGYPLUS_ARCH}-bit)")
 
-    file(DOWNLOAD "https://github.com/NREL/EnergyPlus/releases/download/${ENERGYPLUS_RELEASE_NAME}/${ENERGYPLUS_PATH}.zip" "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.zip"
+    file(DOWNLOAD "https://github.com/${ENERGYPLUS_REPO}/EnergyPlus/releases/download/${ENERGYPLUS_RELEASE_NAME}/${ENERGYPLUS_PATH}.zip" "${PROJECT_BINARY_DIR}/${ENERGYPLUS_PATH}.zip"
          INACTIVITY_TIMEOUT 300 # 5 minute timeout
          SHOW_PROGRESS
          EXPECTED_MD5 ${ENERGYPLUS_EXPECTED_HASH})
diff --git a/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_4_1_TBD.md b/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_5_0_TBD.md
similarity index 81%
rename from developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_4_1_TBD.md
rename to developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_5_0_TBD.md
index 80fa128c3a2..48b468d9b79 100644
--- a/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_4_1_TBD.md
+++ b/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_5_0_TBD.md
@@ -1,8 +1,8 @@
-# OpenStudio Version 3.4.1 (TDB)
+# OpenStudio Version 3.5.0 (TDB)
 
 _Release Notes_ -  _TDB_
 
-These release notes describe version 3.4.1 of the OpenStudio SDK developed by the National Renewable Energy Laboratory (NREL), Buildings and Thermal Sciences Center, Commercial Buildings Research Group, Tools Development Section, and associated collaborators. The notes are organized into the following sections:
+These release notes describe version 3.5.0 of the OpenStudio SDK developed by the National Renewable Energy Laboratory (NREL), Buildings and Thermal Sciences Center, Commercial Buildings Research Group, Tools Development Section, and associated collaborators. The notes are organized into the following sections:
 
 -  Overview
 -  Where to Find OpenStudio Documentation
@@ -15,7 +15,7 @@ As of April 2020, development and distribution of the OpenStudioApplication and
 
 Below is the list of components that is included in this SDK installer:
 
-__**OpenStudio SDK 3.4.1**__
+__**OpenStudio SDK 3.5.0**__
 - EnergyPlus
 - Command Line Interface (CLI)
 - Radiance
@@ -33,11 +33,11 @@ __**OpenStudio SDK 3.4.1**__
 
 # Installation Notes
 
-OpenStudio SDK 3.4.1 is supported on 64-bit Windows 7 – 10, OS X 10.15, and Ubuntu 18.04, 20.04
+OpenStudio SDK 3.5.0 is supported on 64-bit Windows 7 – 10, OS X 10.15, and Ubuntu 18.04, 20.04
 
-OpenStudio SDK 3.4.1 supports [EnergyPlus Release 22.1.0](https://github.com/NREL/EnergyPlus/releases/tag/v22.1.0), which is bundled with the OpenStudio installer. It is no longer necessary to download and install EnergyPlus separately. Other builds of EnergyPlus are not supported by OpenStudio SDK 3.4.1.
+OpenStudio SDK 3.5.0 supports [EnergyPlus Release 22.1.0](https://github.com/NREL/EnergyPlus/releases/tag/v22.1.0), which is bundled with the OpenStudio installer. It is no longer necessary to download and install EnergyPlus separately. Other builds of EnergyPlus are not supported by OpenStudio SDK 3.5.0.
 
-OpenStudio SDK 3.4.1 supports Radiance 5.0.a.12, which is bundled with the OpenStudio installer; users no longer must install Radiance separately, and OpenStudio will use the included Radiance version regardless of any other versions that may be installed on the system. Other builds of Radiance are not supported by OpenStudio SDK 3.4.1.
+OpenStudio SDK 3.5.0 supports Radiance 5.0.a.12, which is bundled with the OpenStudio installer; users no longer must install Radiance separately, and OpenStudio will use the included Radiance version regardless of any other versions that may be installed on the system. Other builds of Radiance are not supported by OpenStudio SDK 3.5.0.
 
 As usual, you can refer to the **[OpenStudio SDK Compatibility Matrix](https://github.com/NREL/OpenStudio/wiki/OpenStudio-SDK-Version-Compatibility-Matrix)** for more information.
 
@@ -52,7 +52,7 @@ For help with common installation problems please visit, http://nrel.github.io/O
 
 # OpenStudio SDK: Changelog
 
-The 3.4.1 is a minor release. This update includes several new features, performance improvements, and bug fixes.
+The 3.5.0 is a minor release. This update includes several new features, performance improvements, and bug fixes.
 You can find the list of Pull Requests that got into this release (**NEEDS UPDATE**) [here](https://github.com/NREL/OpenStudio/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+created%3A2021-04-27..2022-09-26+).
 
 
@@ -60,7 +60,7 @@ You can find the list of Pull Requests that got into this release (**NEEDS UPDAT
 
 As of OpenStudio SDK 3.2.0, Python bindings are officially supported and distributed through Python Package Index (PyPI). To install, users will need to have Python3 installed along with pip and simply run the following command in a terminal window.
 
-`pip install openstudio==3.4.1`
+`pip install openstudio==3.5.0`
 
 Please see [openstudio on PyPi](https://pypi.org/project/openstudio/) for further instructions on how to install. Users can also visit the test channel at [openstudio on TestPyPi](https://test.pypi.org/project/openstudio/) to install development bindings.
 
@@ -83,7 +83,12 @@ You can also refer to the [OpenStudio SDK Python Binding Version Compatibility M
     * `Coil:Cooling:DX:SingleSpeed`, `Coil:Cooling:DX:MultiSpeed:StageData`, and `Coil:Cooling:DX:CurveFit:Speed`: `ratedEvaporatorFanPowerPerVolumeFlowRate` and `setRatedEvaporatorFanPowerPerVolumeFlowRate` in favor of `ratedEvaporatorFanPowerPerVolumeFlowRate2017` and `setRatedEvaporatorFanPowerPerVolumeFlowRate2017`
     * `Coil:Heating:DX:SingleSpeed` and `Coil:Heating:DX:MultiSpeed:StageData`: `ratedSupplyFanPowerPerVolumeFlowRate` and `setRatedSupplyFanPowerPerVolumeFlowRate` in favor of `ratedSupplyFanPowerPerVolumeFlowRate2017` and `setRatedSupplyFanPowerPerVolumeFlowRate2017`
 * [#4666](https://github.com/NREL/OpenStudio/pull/4666) - Changes related to availability schedule methods
-    * `Coil:Heating:Gas:MultiStage` has an API-breaking change related to its `availabilitySchedule` getter. It is now a required field that returns `Schedule` instead of `boost::optional<Schedule>`. Method `resetAvailabilitySchedule` is also removed.     
+    * `Coil:Heating:Gas:MultiStage` has an API-breaking change related to its `availabilitySchedule` getter. It is now a required field that returns `Schedule` instead of `boost::optional<Schedule>`. Method `resetAvailabilitySchedule` is also removed.
+* [#4701](https://github.com/NREL/OpenStudio/pull/4701) - `ZoneHVACPackagedTerminalAirConditioner` and `ZoneHVACPackagedTerminalHeatPump`
+    * `ZoneHVACPackagedTerminalAirConditioner` and `ZoneHVACPackagedTerminalHeatPump` have an API-breaking change related to its `supplyAirFanOperatingModeSchedule` getter. It is now a required field that returns `Schedule` instead of `boost::optional<Schedule>`. Method `resetSupplyAirFanOperatingModeSchedule` is also removed.
+        * It is set to `alwaysOnDiscreteSchedule` (=Constant) in the Constructor if you provide a `FanConstantVolume` (This is **required** by E+)
+        * It is set to `alwaysOffDiscreteSchedule` (=Cycling) in the Constructor if you provide any other fan types (E+ treats a blank schedule as always off)
+    * There are unusual `VersionTranslator` Rules for Packaged Systems (PTAC or PTHP) that use a `FanConstantVolume` and that do not have a `Supply Air Fan Operating Mode Schedule`. In 22.1.0 this would effectively, and mistakenly, function as a cycling fan, but this is now disallowed in E+ 22.2.0. In order to retain a similar functionality and energy usage, the `FanConstantVolume` will be replaced by a `FanSystemModel` with an Always Off Schedule (=cycling fan, similar to a `Fan:OnOff`), mapping inputs such as pressure rise and efficiency appropriately.
 
 
 ## Minor changes and bug fixes
diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd
index 6b0aa46e98f..a4fc5dfc0f8 100644
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -27545,6 +27545,7 @@ OS:ZoneHVAC:PackagedTerminalHeatPump,
        \note cycling fan operation (fan cycles with cooling or heating coil). Schedule Name values greater
        \note than 0 denote constant fan operation (fan runs continually regardless of coil operation).
        \note The fan operating mode defaults to cycling fan operation if this field is left blank.
+       \required-field
        \type object-list
        \object-list ScheduleNames
 
@@ -27659,6 +27660,7 @@ OS:ZoneHVAC:PackagedTerminalAirConditioner,
        \note Enter the name of a schedule that controls fan operation. Schedule Name values of 0 denote
        \note cycling fan operation (fan cycles with cooling or heating coil). Schedule Name values greater
        \note than 0 denote constant fan operation (fan runs continually regardless of coil operation).
+       \required-field
        \type object-list
        \object-list ScheduleNames
 
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalAirConditioner.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalAirConditioner.cpp
index e68fb23ef27..975d3dda6c3 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalAirConditioner.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalAirConditioner.cpp
@@ -359,10 +359,9 @@ namespace energyplus {
 
     // SupplyAirFanOperatingModeScheduleName
 
-    if (boost::optional<Schedule> schedule = modelObject.supplyAirFanOperatingModeSchedule()) {
-      if (boost::optional<IdfObject> _schedule = translateAndMapModelObject(schedule.get())) {
-        idfObject.setString(ZoneHVAC_PackagedTerminalAirConditionerFields::SupplyAirFanOperatingModeScheduleName, _schedule->name().get());
-      }
+    Schedule fanOpSchedule = modelObject.supplyAirFanOperatingModeSchedule();
+    if (boost::optional<IdfObject> _schedule = translateAndMapModelObject(fanOpSchedule)) {
+      idfObject.setString(ZoneHVAC_PackagedTerminalAirConditionerFields::SupplyAirFanOperatingModeScheduleName, _schedule->name().get());
     }
 
     return idfObject;
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalHeatPump.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalHeatPump.cpp
index 17864f9b676..394d02a51a3 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalHeatPump.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateZoneHVACPackagedTerminalHeatPump.cpp
@@ -433,10 +433,9 @@ namespace energyplus {
 
     // SupplyAirFanOperatingModeScheduleName
 
-    if (boost::optional<Schedule> schedule = modelObject.supplyAirFanOperatingModeSchedule()) {
-      if (boost::optional<IdfObject> _schedule = translateAndMapModelObject(schedule.get())) {
-        idfObject.setString(ZoneHVAC_PackagedTerminalHeatPumpFields::SupplyAirFanOperatingModeScheduleName, _schedule->name().get());
-      }
+    Schedule fanOpSchedule = modelObject.supplyAirFanOperatingModeSchedule();
+    if (boost::optional<IdfObject> _schedule = translateAndMapModelObject(fanOpSchedule)) {
+      idfObject.setString(ZoneHVAC_PackagedTerminalHeatPumpFields::SupplyAirFanOperatingModeScheduleName, _schedule->name().get());
     }
 
     return idfObject;
diff --git a/src/model/ZoneHVACPackagedTerminalAirConditioner.cpp b/src/model/ZoneHVACPackagedTerminalAirConditioner.cpp
index 5851405525b..367231d6662 100644
--- a/src/model/ZoneHVACPackagedTerminalAirConditioner.cpp
+++ b/src/model/ZoneHVACPackagedTerminalAirConditioner.cpp
@@ -69,15 +69,15 @@ namespace model {
       : ZoneHVACComponent_Impl(other, model, keepHandle) {}
 
     ModelObject ZoneHVACPackagedTerminalAirConditioner_Impl::clone(Model model) const {
-      ZoneHVACPackagedTerminalAirConditioner ptacClone = ZoneHVACComponent_Impl::clone(model).cast<ZoneHVACPackagedTerminalAirConditioner>();
+      auto ptacClone = ZoneHVACComponent_Impl::clone(model).cast<ZoneHVACPackagedTerminalAirConditioner>();
 
-      HVACComponent supplyFanClone = this->supplyAirFan().clone(model).cast<HVACComponent>();
+      auto supplyFanClone = this->supplyAirFan().clone(model).cast<HVACComponent>();
 
       auto t_heatingCoil = heatingCoil();
-      HVACComponent heatingCoilClone = t_heatingCoil.clone(model).cast<HVACComponent>();
+      auto heatingCoilClone = t_heatingCoil.clone(model).cast<HVACComponent>();
 
       auto t_coolingCoil = coolingCoil();
-      HVACComponent coolingCoilClone = t_coolingCoil.clone(model).cast<HVACComponent>();
+      auto coolingCoilClone = t_coolingCoil.clone(model).cast<HVACComponent>();
 
       ptacClone.setSupplyAirFan(supplyFanClone);
 
@@ -132,12 +132,13 @@ namespace model {
     std::vector<ScheduleTypeKey> ZoneHVACPackagedTerminalAirConditioner_Impl::getScheduleTypeKeys(const Schedule& schedule) const {
       std::vector<ScheduleTypeKey> result;
       UnsignedVector fieldIndices = getSourceIndices(schedule.handle());
-      UnsignedVector::const_iterator b(fieldIndices.begin()), e(fieldIndices.end());
+      UnsignedVector::const_iterator b(fieldIndices.begin());
+      UnsignedVector::const_iterator e(fieldIndices.end());
       if (std::find(b, e, OS_ZoneHVAC_PackagedTerminalAirConditionerFields::AvailabilityScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneHVACPackagedTerminalAirConditioner", "Availability"));
+        result.emplace_back("ZoneHVACPackagedTerminalAirConditioner", "Availability");
       }
       if (std::find(b, e, OS_ZoneHVAC_PackagedTerminalAirConditionerFields::SupplyAirFanOperatingModeScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneHVACPackagedTerminalAirConditioner", "Supply Air Fan Operating Mode"));
+        result.emplace_back("ZoneHVACPackagedTerminalAirConditioner", "Supply Air Fan Operating Mode");
       }
       return result;
     }
@@ -172,7 +173,7 @@ namespace model {
         LOG(Error, "Required availability schedule not set, using 'Always On' schedule");
         value = this->model().alwaysOnDiscreteSchedule();
         OS_ASSERT(value);
-        const_cast<ZoneHVACPackagedTerminalAirConditioner_Impl*>(this)->setAvailabilitySchedule(*value);
+        const_cast<ZoneHVACPackagedTerminalAirConditioner_Impl*>(this)->setAvailabilitySchedule(*value);  // NOLINT
         value = optionalAvailabilitySchedule();
       }
       OS_ASSERT(value);
@@ -301,9 +302,19 @@ namespace model {
       return isEmpty(OS_ZoneHVAC_PackagedTerminalAirConditionerFields::FanPlacement);
     }
 
-    boost::optional<Schedule> ZoneHVACPackagedTerminalAirConditioner_Impl::supplyAirFanOperatingModeSchedule() const {
-      return getObject<ModelObject>().getModelObjectTarget<Schedule>(
-        OS_ZoneHVAC_PackagedTerminalAirConditionerFields::SupplyAirFanOperatingModeScheduleName);
+    Schedule ZoneHVACPackagedTerminalAirConditioner_Impl::supplyAirFanOperatingModeSchedule() const {
+      boost::optional<Schedule> value = optionalSupplyAirFanOperatingModeSchedule();
+      if (!value) {
+        // it is an error if we get here, however we don't want to crash
+        // so we hook up to global always on schedule
+        LOG(Error, "Required availability schedule not set, using 'Always Off' schedule");
+        value = this->model().alwaysOffDiscreteSchedule();
+        OS_ASSERT(value);
+        const_cast<ZoneHVACPackagedTerminalAirConditioner_Impl*>(this)->setSupplyAirFanOperatingModeSchedule(*value);  // NOLINT
+        value = optionalSupplyAirFanOperatingModeSchedule();
+      }
+      OS_ASSERT(value);
+      return value.get();
     }
 
     bool ZoneHVACPackagedTerminalAirConditioner_Impl::setAvailabilitySchedule(Schedule& schedule) {
@@ -312,12 +323,12 @@ namespace model {
       return result;
     }
 
-    bool ZoneHVACPackagedTerminalAirConditioner_Impl::setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType) {
+    bool ZoneHVACPackagedTerminalAirConditioner_Impl::setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType) {
       bool result = setString(OS_ZoneHVAC_PackagedTerminalAirConditionerFields::OutdoorAirMixerObjectType, outdoorAirMixerObjectType);
       return result;
     }
 
-    bool ZoneHVACPackagedTerminalAirConditioner_Impl::setOutdoorAirMixerName(std::string outdoorAirMixerName) {
+    bool ZoneHVACPackagedTerminalAirConditioner_Impl::setOutdoorAirMixerName(const std::string& outdoorAirMixerName) {
       bool result = setString(OS_ZoneHVAC_PackagedTerminalAirConditionerFields::OutdoorAirMixerName, outdoorAirMixerName);
       OS_ASSERT(result);
       return result;
@@ -449,11 +460,9 @@ namespace model {
     bool ZoneHVACPackagedTerminalAirConditioner_Impl::setHeatingCoil(HVACComponent& heatingCoil) {
       bool isAllowedType = false;
 
-      if (heatingCoil.iddObjectType() == IddObjectType::OS_Coil_Heating_Gas) {
-        isAllowedType = true;
-      } else if (heatingCoil.iddObjectType() == IddObjectType::OS_Coil_Heating_Electric) {
-        isAllowedType = true;
-      } else if (heatingCoil.iddObjectType() == IddObjectType::OS_Coil_Heating_Water) {
+      auto iddObjectType = heatingCoil.iddObjectType();
+      if ((iddObjectType == IddObjectType::OS_Coil_Heating_Gas) || (iddObjectType == IddObjectType::OS_Coil_Heating_Electric)
+          || (iddObjectType == IddObjectType::OS_Coil_Heating_Water)) {
         isAllowedType = true;
       }
 
@@ -469,10 +478,10 @@ namespace model {
     bool ZoneHVACPackagedTerminalAirConditioner_Impl::setCoolingCoil(HVACComponent& coolingCoil) {
       bool isAllowedType = false;
 
-      if ((coolingCoil.iddObjectType() == IddObjectType::OS_Coil_Cooling_DX_SingleSpeed)
-          || (coolingCoil.iddObjectType() == IddObjectType::OS_Coil_Cooling_DX_VariableSpeed)
-          || (coolingCoil.iddObjectType() == IddObjectType::OS_CoilSystem_Cooling_DX_HeatExchangerAssisted)
-          || (coolingCoil.iddObjectType() == IddObjectType::OS_Coil_Cooling_DX)) {
+      auto iddObjectType = coolingCoil.iddObjectType();
+      if ((iddObjectType == IddObjectType::OS_Coil_Cooling_DX_SingleSpeed) || (iddObjectType == IddObjectType::OS_Coil_Cooling_DX_VariableSpeed)
+          || (iddObjectType == IddObjectType::OS_CoilSystem_Cooling_DX_HeatExchangerAssisted)
+          || (iddObjectType == IddObjectType::OS_Coil_Cooling_DX)) {
         isAllowedType = true;
       }
 
@@ -486,7 +495,7 @@ namespace model {
       }
     }
 
-    bool ZoneHVACPackagedTerminalAirConditioner_Impl::setFanPlacement(std::string fanPlacement) {
+    bool ZoneHVACPackagedTerminalAirConditioner_Impl::setFanPlacement(const std::string& fanPlacement) {
       bool result = setString(OS_ZoneHVAC_PackagedTerminalAirConditionerFields::FanPlacement, fanPlacement);
       return result;
     }
@@ -502,11 +511,6 @@ namespace model {
       return result;
     }
 
-    void ZoneHVACPackagedTerminalAirConditioner_Impl::resetSupplyAirFanOperatingModeSchedule() {
-      bool result = setString(OS_ZoneHVAC_PackagedTerminalAirConditionerFields::SupplyAirFanOperatingModeScheduleName, "");
-      OS_ASSERT(result);
-    }
-
     boost::optional<Schedule> ZoneHVACPackagedTerminalAirConditioner_Impl::optionalAvailabilitySchedule() const {
       return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneHVAC_PackagedTerminalAirConditionerFields::AvailabilityScheduleName);
     }
@@ -520,6 +524,11 @@ namespace model {
       return getObject<ModelObject>().getModelObjectTarget<HVACComponent>(OS_ZoneHVAC_PackagedTerminalAirConditionerFields::CoolingCoilName);
     }
 
+    boost::optional<Schedule> ZoneHVACPackagedTerminalAirConditioner_Impl::optionalSupplyAirFanOperatingModeSchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(
+        OS_ZoneHVAC_PackagedTerminalAirConditionerFields::SupplyAirFanOperatingModeScheduleName);
+    }
+
     boost::optional<ModelObject> ZoneHVACPackagedTerminalAirConditioner_Impl::availabilityScheduleAsModelObject() const {
       OptionalModelObject result = availabilitySchedule();
       return result;
@@ -600,13 +609,9 @@ namespace model {
         if (intermediate) {
           Schedule schedule(*intermediate);
           return setSupplyAirFanOperatingModeSchedule(schedule);
-        } else {
-          return false;
         }
-      } else {
-        resetSupplyAirFanOperatingModeSchedule();
       }
-      return true;
+      return false;
     }
 
     boost::optional<double> ZoneHVACPackagedTerminalAirConditioner_Impl::autosizedSupplyAirFlowRateDuringCoolingOperation() const {
@@ -693,7 +698,16 @@ namespace model {
     setHeatingCoil(heatingCoil);
     setCoolingCoil(coolingCoil);
     setFanPlacement("DrawThrough");
-    resetSupplyAirFanOperatingModeSchedule();
+    // When Blank, E+ defaults to 0 (cycling). If it's a FanConstantVolume though, you must provide an always On or E+ will Fatal.
+    if (supplyAirFan.iddObjectType() == IddObjectType::OS_Fan_ConstantVolume) {
+      auto alwaysOn = model.alwaysOnDiscreteSchedule();
+      ok = setSupplyAirFanOperatingModeSchedule(alwaysOn);
+      OS_ASSERT(ok);
+    } else {
+      auto alwaysOff = model.alwaysOffDiscreteSchedule();
+      ok = setSupplyAirFanOperatingModeSchedule(alwaysOff);
+      OS_ASSERT(ok);
+    }
 
     autosizeSupplyAirFlowRateDuringCoolingOperation();
     autosizeSupplyAirFlowRateDuringHeatingOperation();
@@ -704,8 +718,7 @@ namespace model {
   }
 
   IddObjectType ZoneHVACPackagedTerminalAirConditioner::iddObjectType() {
-    IddObjectType result(IddObjectType::OS_ZoneHVAC_PackagedTerminalAirConditioner);
-    return result;
+    return IddObjectType::OS_ZoneHVAC_PackagedTerminalAirConditioner;
   }
 
   std::vector<std::string> ZoneHVACPackagedTerminalAirConditioner::outdoorAirMixerObjectTypeValues() {
@@ -789,11 +802,11 @@ namespace model {
     return getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->isFanPlacementDefaulted();
   }
 
-  bool ZoneHVACPackagedTerminalAirConditioner::setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType) {
+  bool ZoneHVACPackagedTerminalAirConditioner::setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType) {
     return getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->setOutdoorAirMixerObjectType(outdoorAirMixerObjectType);
   }
 
-  bool ZoneHVACPackagedTerminalAirConditioner::setOutdoorAirMixerName(std::string outdoorAirMixerName) {
+  bool ZoneHVACPackagedTerminalAirConditioner::setOutdoorAirMixerName(const std::string& outdoorAirMixerName) {
     return getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->setOutdoorAirMixerName(outdoorAirMixerName);
   }
 
@@ -861,7 +874,7 @@ namespace model {
     getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->autosizeOutdoorAirFlowRateWhenNoCoolingorHeatingisNeeded();
   }
 
-  bool ZoneHVACPackagedTerminalAirConditioner::setFanPlacement(std::string fanPlacement) {
+  bool ZoneHVACPackagedTerminalAirConditioner::setFanPlacement(const std::string& fanPlacement) {
     return getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->setFanPlacement(fanPlacement);
   }
 
@@ -873,7 +886,7 @@ namespace model {
     std::shared_ptr<detail::ZoneHVACPackagedTerminalAirConditioner_Impl> impl)
     : ZoneHVACComponent(std::move(impl)) {}
 
-  boost::optional<Schedule> ZoneHVACPackagedTerminalAirConditioner::supplyAirFanOperatingModeSchedule() const {
+  Schedule ZoneHVACPackagedTerminalAirConditioner::supplyAirFanOperatingModeSchedule() const {
     return getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->supplyAirFanOperatingModeSchedule();
   }
 
@@ -881,10 +894,6 @@ namespace model {
     return getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->setSupplyAirFanOperatingModeSchedule(schedule);
   }
 
-  void ZoneHVACPackagedTerminalAirConditioner::resetSupplyAirFanOperatingModeSchedule() {
-    getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->resetSupplyAirFanOperatingModeSchedule();
-  }
-
   Schedule ZoneHVACPackagedTerminalAirConditioner::availabilitySchedule() const {
     return getImpl<detail::ZoneHVACPackagedTerminalAirConditioner_Impl>()->availabilitySchedule();
   }
diff --git a/src/model/ZoneHVACPackagedTerminalAirConditioner.hpp b/src/model/ZoneHVACPackagedTerminalAirConditioner.hpp
index a4299f0c668..9c9092711cf 100644
--- a/src/model/ZoneHVACPackagedTerminalAirConditioner.hpp
+++ b/src/model/ZoneHVACPackagedTerminalAirConditioner.hpp
@@ -56,7 +56,7 @@ namespace model {
     ZoneHVACPackagedTerminalAirConditioner(const Model& model, Schedule& availabilitySchedule, HVACComponent& supplyAirFan,
                                            HVACComponent& heatingCoil, HVACComponent& coolingCoil);
 
-    virtual ~ZoneHVACPackagedTerminalAirConditioner() {}
+    virtual ~ZoneHVACPackagedTerminalAirConditioner() = default;
 
     //@}
 
@@ -121,7 +121,7 @@ namespace model {
 
     bool isFanPlacementDefaulted() const;
 
-    boost::optional<Schedule> supplyAirFanOperatingModeSchedule() const;
+    Schedule supplyAirFanOperatingModeSchedule() const;
 
     //@}
     /** @name Setters */
@@ -129,9 +129,9 @@ namespace model {
 
     bool setAvailabilitySchedule(Schedule& schedule);
 
-    bool setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType);
+    bool setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType);
 
-    bool setOutdoorAirMixerName(std::string outdoorAirMixerName);
+    bool setOutdoorAirMixerName(const std::string& outdoorAirMixerName);
 
     bool setSupplyAirFlowRateDuringCoolingOperation(double supplyAirFlowRateDuringCoolingOperation);
 
@@ -167,7 +167,7 @@ namespace model {
 
     bool setCoolingCoil(HVACComponent& coolingCoil);
 
-    bool setFanPlacement(std::string fanPlacement);
+    bool setFanPlacement(const std::string& fanPlacement);
 
     void resetFanPlacement();
 
@@ -176,8 +176,6 @@ namespace model {
    *  fan continuously. */
     bool setSupplyAirFanOperatingModeSchedule(Schedule& schedule);
 
-    void resetSupplyAirFanOperatingModeSchedule();
-
     //@}
     /** @name Other */
     //@{
@@ -197,7 +195,7 @@ namespace model {
     //@}
    protected:
     /// @cond
-    typedef detail::ZoneHVACPackagedTerminalAirConditioner_Impl ImplType;
+    using ImplType = detail::ZoneHVACPackagedTerminalAirConditioner_Impl;
 
     explicit ZoneHVACPackagedTerminalAirConditioner(std::shared_ptr<detail::ZoneHVACPackagedTerminalAirConditioner_Impl> impl);
 
@@ -212,10 +210,10 @@ namespace model {
   };
 
   /** \relates ZoneHVACPackagedTerminalAirConditioner*/
-  typedef boost::optional<ZoneHVACPackagedTerminalAirConditioner> OptionalZoneHVACPackagedTerminalAirConditioner;
+  using OptionalZoneHVACPackagedTerminalAirConditioner = boost::optional<ZoneHVACPackagedTerminalAirConditioner>;
 
   /** \relates ZoneHVACPackagedTerminalAirConditioner*/
-  typedef std::vector<ZoneHVACPackagedTerminalAirConditioner> ZoneHVACPackagedTerminalAirConditionerVector;
+  using ZoneHVACPackagedTerminalAirConditionerVector = std::vector<ZoneHVACPackagedTerminalAirConditioner>;
 
 }  // namespace model
 }  // namespace openstudio
diff --git a/src/model/ZoneHVACPackagedTerminalAirConditioner_Impl.hpp b/src/model/ZoneHVACPackagedTerminalAirConditioner_Impl.hpp
index 86fd0e8c319..9524483fd43 100644
--- a/src/model/ZoneHVACPackagedTerminalAirConditioner_Impl.hpp
+++ b/src/model/ZoneHVACPackagedTerminalAirConditioner_Impl.hpp
@@ -56,7 +56,7 @@ namespace model {
 
       ZoneHVACPackagedTerminalAirConditioner_Impl(const ZoneHVACPackagedTerminalAirConditioner_Impl& other, Model_Impl* model, bool keepHandle);
 
-      virtual ~ZoneHVACPackagedTerminalAirConditioner_Impl() {}
+      virtual ~ZoneHVACPackagedTerminalAirConditioner_Impl() = default;
 
       //@}
       /** @name Virtual Methods */
@@ -122,7 +122,7 @@ namespace model {
 
       bool isFanPlacementDefaulted() const;
 
-      boost::optional<Schedule> supplyAirFanOperatingModeSchedule() const;
+      Schedule supplyAirFanOperatingModeSchedule() const;
 
       boost::optional<double> autosizedSupplyAirFlowRateDuringCoolingOperation() const;
 
@@ -146,9 +146,9 @@ namespace model {
 
       bool setAvailabilitySchedule(Schedule& schedule);
 
-      bool setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType);
+      bool setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType);
 
-      bool setOutdoorAirMixerName(std::string outdoorAirMixerName);
+      bool setOutdoorAirMixerName(const std::string& outdoorAirMixerName);
 
       bool setSupplyAirFlowRateDuringCoolingOperation(boost::optional<double> supplyAirFlowRateDuringCoolingOperation);
 
@@ -184,14 +184,12 @@ namespace model {
 
       bool setCoolingCoil(HVACComponent& coolingCoil);
 
-      bool setFanPlacement(std::string fanPlacement);
+      bool setFanPlacement(const std::string& fanPlacement);
 
       void resetFanPlacement();
 
       bool setSupplyAirFanOperatingModeSchedule(Schedule& schedule);
 
-      void resetSupplyAirFanOperatingModeSchedule();
-
       //@}
       /** @name Other */
       //@{
@@ -206,6 +204,7 @@ namespace model {
       boost::optional<HVACComponent> optionalSupplyAirFan() const;
       boost::optional<HVACComponent> optionalHeatingCoil() const;
       boost::optional<HVACComponent> optionalCoolingCoil() const;
+      boost::optional<Schedule> optionalSupplyAirFanOperatingModeSchedule() const;
 
       boost::optional<ModelObject> availabilityScheduleAsModelObject() const;
       boost::optional<ModelObject> supplyAirFanAsModelObject() const;
diff --git a/src/model/ZoneHVACPackagedTerminalHeatPump.cpp b/src/model/ZoneHVACPackagedTerminalHeatPump.cpp
index 8979b27759f..486e400484a 100644
--- a/src/model/ZoneHVACPackagedTerminalHeatPump.cpp
+++ b/src/model/ZoneHVACPackagedTerminalHeatPump.cpp
@@ -67,19 +67,19 @@ namespace model {
       : ZoneHVACComponent_Impl(other, model, keepHandle) {}
 
     ModelObject ZoneHVACPackagedTerminalHeatPump_Impl::clone(Model model) const {
-      ZoneHVACPackagedTerminalHeatPump pthpClone = ZoneHVACComponent_Impl::clone(model).cast<ZoneHVACPackagedTerminalHeatPump>();
+      auto pthpClone = ZoneHVACComponent_Impl::clone(model).cast<ZoneHVACPackagedTerminalHeatPump>();
 
       //Schedule scheduleClone = this->availabilitySchedule().clone(model).cast<Schedule>();
 
-      HVACComponent supplyFanClone = this->supplyAirFan().clone(model).cast<HVACComponent>();
+      auto supplyFanClone = this->supplyAirFan().clone(model).cast<HVACComponent>();
 
       auto t_heatingCoil = heatingCoil();
-      HVACComponent heatingCoilClone = t_heatingCoil.clone(model).cast<HVACComponent>();
+      auto heatingCoilClone = t_heatingCoil.clone(model).cast<HVACComponent>();
 
       auto t_coolingCoil = coolingCoil();
-      HVACComponent coolingCoilClone = t_coolingCoil.clone(model).cast<HVACComponent>();
+      auto coolingCoilClone = t_coolingCoil.clone(model).cast<HVACComponent>();
 
-      HVACComponent supplementalHeatingCoilClone = this->supplementalHeatingCoil().clone(model).cast<HVACComponent>();
+      auto supplementalHeatingCoilClone = this->supplementalHeatingCoil().clone(model).cast<HVACComponent>();
 
       //pthpClone.setAvailabilitySchedule(scheduleClone);
 
@@ -128,12 +128,13 @@ namespace model {
     std::vector<ScheduleTypeKey> ZoneHVACPackagedTerminalHeatPump_Impl::getScheduleTypeKeys(const Schedule& schedule) const {
       std::vector<ScheduleTypeKey> result;
       UnsignedVector fieldIndices = getSourceIndices(schedule.handle());
-      UnsignedVector::const_iterator b(fieldIndices.begin()), e(fieldIndices.end());
+      UnsignedVector::const_iterator b(fieldIndices.begin());
+      UnsignedVector::const_iterator e(fieldIndices.end());
       if (std::find(b, e, OS_ZoneHVAC_PackagedTerminalHeatPumpFields::AvailabilityScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneHVACPackagedTerminalHeatPump", "Availability"));
+        result.emplace_back("ZoneHVACPackagedTerminalHeatPump", "Availability");
       }
       if (std::find(b, e, OS_ZoneHVAC_PackagedTerminalHeatPumpFields::SupplyAirFanOperatingModeScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("ZoneHVACPackagedTerminalHeatPump", "Supply Air Fan Operating Mode"));
+        result.emplace_back("ZoneHVACPackagedTerminalHeatPump", "Supply Air Fan Operating Mode");
       }
       return result;
     }
@@ -171,7 +172,7 @@ namespace model {
         LOG(Error, "Required availability schedule not set, using 'Always On' schedule");
         value = this->model().alwaysOnDiscreteSchedule();
         OS_ASSERT(value);
-        const_cast<ZoneHVACPackagedTerminalHeatPump_Impl*>(this)->setAvailabilitySchedule(*value);
+        const_cast<ZoneHVACPackagedTerminalHeatPump_Impl*>(this)->setAvailabilitySchedule(*value);  // NOLINT
         value = optionalAvailabilitySchedule();
       }
       OS_ASSERT(value);
@@ -360,9 +361,19 @@ namespace model {
       return isEmpty(OS_ZoneHVAC_PackagedTerminalHeatPumpFields::FanPlacement);
     }
 
-    boost::optional<Schedule> ZoneHVACPackagedTerminalHeatPump_Impl::supplyAirFanOperatingModeSchedule() const {
-      return getObject<ModelObject>().getModelObjectTarget<Schedule>(
-        OS_ZoneHVAC_PackagedTerminalHeatPumpFields::SupplyAirFanOperatingModeScheduleName);
+    Schedule ZoneHVACPackagedTerminalHeatPump_Impl::supplyAirFanOperatingModeSchedule() const {
+      boost::optional<Schedule> value = optionalSupplyAirFanOperatingModeSchedule();
+      if (!value) {
+        // it is an error if we get here, however we don't want to crash
+        // so we hook up to global always on schedule
+        LOG(Error, "Required availability schedule not set, using 'Always Off' schedule");
+        value = this->model().alwaysOffDiscreteSchedule();
+        OS_ASSERT(value);
+        const_cast<ZoneHVACPackagedTerminalHeatPump_Impl*>(this)->setSupplyAirFanOperatingModeSchedule(*value);  // NOLINT
+        value = optionalSupplyAirFanOperatingModeSchedule();
+      }
+      OS_ASSERT(value);
+      return value.get();
     }
 
     bool ZoneHVACPackagedTerminalHeatPump_Impl::setAvailabilitySchedule(Schedule& schedule) {
@@ -371,12 +382,12 @@ namespace model {
       return result;
     }
 
-    bool ZoneHVACPackagedTerminalHeatPump_Impl::setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType) {
+    bool ZoneHVACPackagedTerminalHeatPump_Impl::setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType) {
       bool result = setString(OS_ZoneHVAC_PackagedTerminalHeatPumpFields::OutdoorAirMixerObjectType, outdoorAirMixerObjectType);
       return result;
     }
 
-    bool ZoneHVACPackagedTerminalHeatPump_Impl::setOutdoorAirMixerName(std::string outdoorAirMixerName) {
+    bool ZoneHVACPackagedTerminalHeatPump_Impl::setOutdoorAirMixerName(const std::string& outdoorAirMixerName) {
       bool result = setString(OS_ZoneHVAC_PackagedTerminalHeatPumpFields::OutdoorAirMixerName, outdoorAirMixerName);
       OS_ASSERT(result);
       return result;
@@ -609,7 +620,7 @@ namespace model {
       OS_ASSERT(result);
     }
 
-    bool ZoneHVACPackagedTerminalHeatPump_Impl::setFanPlacement(std::string fanPlacement) {
+    bool ZoneHVACPackagedTerminalHeatPump_Impl::setFanPlacement(const std::string& fanPlacement) {
       bool result = setString(OS_ZoneHVAC_PackagedTerminalHeatPumpFields::FanPlacement, fanPlacement);
       return result;
     }
@@ -625,11 +636,6 @@ namespace model {
       return result;
     }
 
-    void ZoneHVACPackagedTerminalHeatPump_Impl::resetSupplyAirFanOperatingModeSchedule() {
-      bool result = setString(OS_ZoneHVAC_PackagedTerminalHeatPumpFields::SupplyAirFanOperatingModeScheduleName, "");
-      OS_ASSERT(result);
-    }
-
     boost::optional<Schedule> ZoneHVACPackagedTerminalHeatPump_Impl::optionalAvailabilitySchedule() const {
       return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_ZoneHVAC_PackagedTerminalHeatPumpFields::AvailabilityScheduleName);
     }
@@ -646,6 +652,11 @@ namespace model {
       return getObject<ModelObject>().getModelObjectTarget<HVACComponent>(OS_ZoneHVAC_PackagedTerminalHeatPumpFields::SupplementalHeatingCoilName);
     }
 
+    boost::optional<Schedule> ZoneHVACPackagedTerminalHeatPump_Impl::optionalSupplyAirFanOperatingModeSchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(
+        OS_ZoneHVAC_PackagedTerminalHeatPumpFields::SupplyAirFanOperatingModeScheduleName);
+    }
+
     boost::optional<ModelObject> ZoneHVACPackagedTerminalHeatPump_Impl::availabilityScheduleAsModelObject() const {
       OptionalModelObject result = availabilitySchedule();
       return result;
@@ -741,13 +752,9 @@ namespace model {
         if (intermediate) {
           Schedule schedule(*intermediate);
           return setSupplyAirFanOperatingModeSchedule(schedule);
-        } else {
-          return false;
         }
-      } else {
-        resetSupplyAirFanOperatingModeSchedule();
       }
-      return true;
+      return false;
     }
 
     boost::optional<double> ZoneHVACPackagedTerminalHeatPump_Impl::autosizedSupplyAirFlowRateDuringCoolingOperation() const {
@@ -844,6 +851,16 @@ namespace model {
     setHeatingCoil(heatingCoil);
     setCoolingCoil(coolingCoil);
     setSupplementalHeatingCoil(supplementalHeatingCoil);
+    // When Blank, E+ defaults to 0 (cycling). If it's a FanConstantVolume though, you must provide an always On or E+ will Fatal.
+    if (supplyAirFan.iddObjectType() == IddObjectType::OS_Fan_ConstantVolume) {
+      auto alwaysOn = model.alwaysOnDiscreteSchedule();
+      ok = setSupplyAirFanOperatingModeSchedule(alwaysOn);
+      OS_ASSERT(ok);
+    } else {
+      auto alwaysOff = model.alwaysOffDiscreteSchedule();
+      ok = setSupplyAirFanOperatingModeSchedule(alwaysOff);
+      OS_ASSERT(ok);
+    }
 
     autosizeSupplyAirFlowRateDuringCoolingOperation();
     autosizeSupplyAirFlowRateDuringHeatingOperation();
@@ -980,11 +997,11 @@ namespace model {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->isFanPlacementDefaulted();
   }
 
-  bool ZoneHVACPackagedTerminalHeatPump::setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType) {
+  bool ZoneHVACPackagedTerminalHeatPump::setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType) {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->setOutdoorAirMixerObjectType(outdoorAirMixerObjectType);
   }
 
-  bool ZoneHVACPackagedTerminalHeatPump::setOutdoorAirMixerName(std::string outdoorAirMixerName) {
+  bool ZoneHVACPackagedTerminalHeatPump::setOutdoorAirMixerName(const std::string& outdoorAirMixerName) {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->setOutdoorAirMixerName(outdoorAirMixerName);
   }
 
@@ -1097,7 +1114,7 @@ namespace model {
     getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->resetMaximumOutdoorDryBulbTemperatureforSupplementalHeaterOperation();
   }
 
-  bool ZoneHVACPackagedTerminalHeatPump::setFanPlacement(std::string fanPlacement) {
+  bool ZoneHVACPackagedTerminalHeatPump::setFanPlacement(const std::string& fanPlacement) {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->setFanPlacement(fanPlacement);
   }
 
@@ -1113,10 +1130,6 @@ namespace model {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->setSupplyAirFanOperatingModeSchedule(schedule);
   }
 
-  void ZoneHVACPackagedTerminalHeatPump::resetSupplyAirFanOperatingModeSchedule() {
-    getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->resetSupplyAirFanOperatingModeSchedule();
-  }
-
   bool ZoneHVACPackagedTerminalHeatPump::setSupplyAirFan(HVACComponent& hvacComponent) {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->setSupplyAirFan(hvacComponent);
   }
@@ -1137,7 +1150,7 @@ namespace model {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->availabilitySchedule();
   }
 
-  boost::optional<Schedule> ZoneHVACPackagedTerminalHeatPump::supplyAirFanOperatingModeSchedule() const {
+  Schedule ZoneHVACPackagedTerminalHeatPump::supplyAirFanOperatingModeSchedule() const {
     return getImpl<detail::ZoneHVACPackagedTerminalHeatPump_Impl>()->supplyAirFanOperatingModeSchedule();
   }
 
diff --git a/src/model/ZoneHVACPackagedTerminalHeatPump.hpp b/src/model/ZoneHVACPackagedTerminalHeatPump.hpp
index 9c01ef8540e..401a76e20ce 100644
--- a/src/model/ZoneHVACPackagedTerminalHeatPump.hpp
+++ b/src/model/ZoneHVACPackagedTerminalHeatPump.hpp
@@ -57,7 +57,7 @@ namespace model {
     ZoneHVACPackagedTerminalHeatPump(const Model& model, Schedule& availabilitySchedule, HVACComponent& supplyAirFan, HVACComponent& heatingCoil,
                                      HVACComponent& coolingCoil, HVACComponent& supplementalHeatingCoil);
 
-    virtual ~ZoneHVACPackagedTerminalHeatPump() {}
+    virtual ~ZoneHVACPackagedTerminalHeatPump() = default;
 
     //@}
 
@@ -145,7 +145,7 @@ namespace model {
 
     bool isFanPlacementDefaulted() const;
 
-    boost::optional<Schedule> supplyAirFanOperatingModeSchedule() const;
+    Schedule supplyAirFanOperatingModeSchedule() const;
 
     //@}
     /** @name Setters */
@@ -153,9 +153,9 @@ namespace model {
 
     bool setAvailabilitySchedule(Schedule& schedule);
 
-    bool setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType);
+    bool setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType);
 
-    bool setOutdoorAirMixerName(std::string outdoorAirMixerName);
+    bool setOutdoorAirMixerName(const std::string& outdoorAirMixerName);
 
     bool setSupplyAirFlowRateDuringCoolingOperation(double supplyAirFlowRateDuringCoolingOperation);
 
@@ -214,7 +214,7 @@ namespace model {
 
     void resetMaximumOutdoorDryBulbTemperatureforSupplementalHeaterOperation();
 
-    bool setFanPlacement(std::string fanPlacement);
+    bool setFanPlacement(const std::string& fanPlacement);
 
     void resetFanPlacement();
 
@@ -223,8 +223,6 @@ namespace model {
    *  fan continuously. */
     bool setSupplyAirFanOperatingModeSchedule(Schedule& schedule);
 
-    void resetSupplyAirFanOperatingModeSchedule();
-
     //@}
     /** @name Other */
     //@{
@@ -246,7 +244,7 @@ namespace model {
     //@}
    protected:
     /// @cond
-    typedef detail::ZoneHVACPackagedTerminalHeatPump_Impl ImplType;
+    using ImplType = detail::ZoneHVACPackagedTerminalHeatPump_Impl;
 
     explicit ZoneHVACPackagedTerminalHeatPump(std::shared_ptr<detail::ZoneHVACPackagedTerminalHeatPump_Impl> impl);
 
@@ -261,10 +259,10 @@ namespace model {
   };
 
   /** \relates ZoneHVACPackagedTerminalHeatPump*/
-  typedef boost::optional<ZoneHVACPackagedTerminalHeatPump> OptionalZoneHVACPackagedTerminalHeatPump;
+  using OptionalZoneHVACPackagedTerminalHeatPump = boost::optional<ZoneHVACPackagedTerminalHeatPump>;
 
   /** \relates ZoneHVACPackagedTerminalHeatPump*/
-  typedef std::vector<ZoneHVACPackagedTerminalHeatPump> ZoneHVACPackagedTerminalHeatPumpVector;
+  using ZoneHVACPackagedTerminalHeatPumpVector = std::vector<ZoneHVACPackagedTerminalHeatPump>;
 
 }  // namespace model
 }  // namespace openstudio
diff --git a/src/model/ZoneHVACPackagedTerminalHeatPump_Impl.hpp b/src/model/ZoneHVACPackagedTerminalHeatPump_Impl.hpp
index f24120de566..ecd38604e7d 100644
--- a/src/model/ZoneHVACPackagedTerminalHeatPump_Impl.hpp
+++ b/src/model/ZoneHVACPackagedTerminalHeatPump_Impl.hpp
@@ -57,7 +57,7 @@ namespace model {
 
       ZoneHVACPackagedTerminalHeatPump_Impl(const ZoneHVACPackagedTerminalHeatPump_Impl& other, Model_Impl* model, bool keepHandle);
 
-      virtual ~ZoneHVACPackagedTerminalHeatPump_Impl() {}
+      virtual ~ZoneHVACPackagedTerminalHeatPump_Impl() = default;
 
       //@}
       /** @name Virtual Methods */
@@ -143,7 +143,7 @@ namespace model {
 
       bool isFanPlacementDefaulted() const;
 
-      boost::optional<Schedule> supplyAirFanOperatingModeSchedule() const;
+      Schedule supplyAirFanOperatingModeSchedule() const;
 
       boost::optional<double> autosizedSupplyAirFlowRateDuringCoolingOperation() const;
 
@@ -169,9 +169,9 @@ namespace model {
 
       bool setAvailabilitySchedule(Schedule& schedule);
 
-      bool setOutdoorAirMixerObjectType(std::string outdoorAirMixerObjectType);
+      bool setOutdoorAirMixerObjectType(const std::string& outdoorAirMixerObjectType);
 
-      bool setOutdoorAirMixerName(std::string outdoorAirMixerName);
+      bool setOutdoorAirMixerName(const std::string& outdoorAirMixerName);
 
       bool setSupplyAirFlowRateDuringCoolingOperation(boost::optional<double> supplyAirFlowRateDuringCoolingOperation);
 
@@ -229,14 +229,12 @@ namespace model {
 
       void resetMaximumOutdoorDryBulbTemperatureforSupplementalHeaterOperation();
 
-      bool setFanPlacement(std::string fanPlacement);
+      bool setFanPlacement(const std::string& fanPlacement);
 
       void resetFanPlacement();
 
       bool setSupplyAirFanOperatingModeSchedule(Schedule& schedule);
 
-      void resetSupplyAirFanOperatingModeSchedule();
-
       //@}
       /** @name Other */
       //@{
@@ -252,6 +250,7 @@ namespace model {
       boost::optional<HVACComponent> optionalHeatingCoil() const;
       boost::optional<HVACComponent> optionalCoolingCoil() const;
       boost::optional<HVACComponent> optionalSupplementalHeatingCoil() const;
+      boost::optional<Schedule> optionalSupplyAirFanOperatingModeSchedule() const;
 
       boost::optional<ModelObject> availabilityScheduleAsModelObject() const;
       boost::optional<ModelObject> supplyAirFanAsModelObject() const;
diff --git a/src/osversion/VersionTranslator.cpp b/src/osversion/VersionTranslator.cpp
index 2e4a0eeea58..5e540275475 100644
--- a/src/osversion/VersionTranslator.cpp
+++ b/src/osversion/VersionTranslator.cpp
@@ -63,9 +63,11 @@
 
 #include <OpenStudio.hxx>
 
-#include <thread>
+#include <algorithm>
+#include <iterator>
 #include <map>
 #include <sstream>
+#include <thread>
 
 #include <boost/regex.hpp>
 #include <boost/lexical_cast.hpp>
@@ -6920,6 +6922,99 @@ namespace osversion {
     IdfFile targetIdf(idd_3_5_0.iddFile());
     ss << targetIdf.versionObject().get();
 
+    // ZoneHVAC:Packaged AirConditionner / HeatPump: prescan
+    // In E+ 22.1.0, you could have a Packaged system with a Fan:ConstantVolume and a blank schedule for Supply Air Fan Operating Mode Schedule Name
+    // and it would behave like a cycling fan (which it shouldn't have done). This is now disallowed in E+ 22.2.0, and you also cannot set a Schedule
+    // with 0 values with a Fan:ConstantVolume. So we must replace these fans with a Fan:SystemModel (that mimics a Fan:OnOff really, just planning
+    // for the future + avoid having to create 2 curves for FanOnOff)
+    std::vector<std::string> packagedFanCVHandleStrs;
+    {
+      std::vector<IdfObject> fanCVs = idf_3_4_0.getObjectsByType(idf_3_4_0.iddFile().getObject("OS:Fan:ConstantVolume").get());
+      std::vector<std::string> fanCVHandleStrs;
+      fanCVHandleStrs.reserve(fanCVs.size());
+      packagedFanCVHandleStrs.reserve(fanCVs.size());
+      std::transform(fanCVs.cbegin(), fanCVs.cend(), std::back_inserter(fanCVHandleStrs),
+                     [](const auto& idfObject) { return idfObject.getString(0).get(); });
+      for (const auto& ptac : idf_3_4_0.getObjectsByType(idf_3_4_0.iddFile().getObject("OS:ZoneHVAC:PackagedTerminalAirConditioner").get())) {
+        // If the Supply Air Fan Operating Mode Schedule is empty
+        if (ptac.isEmpty(17)) {
+          if (auto fanHandleStr_ = ptac.getString(13, false, true)) {
+            if (std::find(fanCVHandleStrs.cbegin(), fanCVHandleStrs.cend(), fanHandleStr_.get()) != fanCVHandleStrs.cend()) {
+              packagedFanCVHandleStrs.push_back(fanHandleStr_.get());
+            }
+          }
+        }
+      }
+      for (const auto& pthp : idf_3_4_0.getObjectsByType(idf_3_4_0.iddFile().getObject("OS:ZoneHVAC:PackagedTerminalHeatPump").get())) {
+        // If the Supply Air Fan Operating Mode Schedule is empty
+        if (pthp.isEmpty(23)) {
+          if (auto fanName_ = pthp.getString(13, false, true)) {
+            if (std::find(fanCVHandleStrs.cbegin(), fanCVHandleStrs.cend(), fanName_.get()) != fanCVHandleStrs.cend()) {
+              packagedFanCVHandleStrs.push_back(fanName_.get());
+            }
+          }
+        }
+      }
+    }
+
+    std::string alwaysOnDiscreteScheduleHandleStr;
+    std::string alwaysOffDiscreteScheduleHandleStr;
+
+    auto getOrCreateAlwaysDiscreteScheduleHandleStr = [this, &ss, &idf_3_4_0, &idd_3_5_0, &alwaysOnDiscreteScheduleHandleStr,
+                                                       &alwaysOffDiscreteScheduleHandleStr](bool isAlwaysOn) -> std::string {
+      auto& discreteSchHandleStr = isAlwaysOn ? alwaysOnDiscreteScheduleHandleStr : alwaysOffDiscreteScheduleHandleStr;
+      if (!discreteSchHandleStr.empty()) {
+        return discreteSchHandleStr;
+      }
+      std::string name = isAlwaysOn ? "Always On Discrete" : "Always Off Discrete";
+      double val = isAlwaysOn ? 1.0 : 0.0;
+      // Add an alwaysOnDiscreteSchedule if one does not already exist
+      for (const IdfObject& object : idf_3_4_0.getObjectsByType(idf_3_4_0.iddFile().getObject("OS:Schedule:Constant").get())) {
+        if (boost::optional<std::string> name_ = object.getString(1)) {
+          if (istringEqual(name_.get(), name)) {
+            if (boost::optional<double> value = object.getDouble(3)) {
+              if (equal<double>(value.get(), val)) {
+                discreteSchHandleStr = object.getString(0).get();
+                break;
+              }
+            }
+          }
+        }
+      }
+
+      if (discreteSchHandleStr.empty()) {
+        auto discreteSch = IdfObject(idd_3_5_0.getObject("OS:Schedule:Constant").get());
+
+        discreteSchHandleStr = toString(createUUID());
+        discreteSch.setString(0, discreteSchHandleStr);
+        discreteSch.setString(1, name);
+        discreteSch.setDouble(3, val);
+
+        IdfObject typeLimits(idd_3_5_0.getObject("OS:ScheduleTypeLimits").get());
+        typeLimits.setString(0, toString(createUUID()));
+        typeLimits.setString(1, name + " Limits");
+        typeLimits.setDouble(2, 0.0);
+        typeLimits.setDouble(3, 1.0);
+        typeLimits.setString(4, "Discrete");
+        typeLimits.setString(5, "Availability");
+
+        discreteSch.setString(2, typeLimits.getString(0).get());
+
+        ss << discreteSch;
+        ss << typeLimits;
+
+        // Register new objects
+        m_new.push_back(discreteSch);
+        m_new.push_back(typeLimits);
+      }
+
+      return discreteSchHandleStr;
+    };
+
+    if (!packagedFanCVHandleStrs.empty()) {
+      alwaysOffDiscreteScheduleHandleStr = getOrCreateAlwaysDiscreteScheduleHandleStr(false);
+    }  // End locating or creating alwaysOnDiscreteSchedule
+
     for (const IdfObject& object : idf_3_4_0.objects()) {
       auto iddname = object.iddObject().name();
 
@@ -7339,50 +7434,171 @@ namespace osversion {
         // ------------------------------------------------
         // * Availability Schedule * 2
 
-        auto iddObject = idd_3_5_0.getObject(iddname);
-        IdfObject newObject(iddObject.get());
+        if (!object.isEmpty(2)) {
+          ss << object;
+        } else {
 
-        if (object.isEmpty(2)) {
-          // Add an always on discrete schedule if one does not already exist
-          boost::optional<IdfObject> alwaysOnSchedule;
-          for (const IdfObject& object : idf_3_4_0.objects()) {
-            if (object.iddObject().name() == "OS:Schedule:Constant") {
-              if (boost::optional<std::string> name = object.getString(1)) {
-                if (istringEqual(name.get(), "Always On Discrete")) {
-                  if (boost::optional<double> value = object.getDouble(3)) {
-                    if (equal<double>(value.get(), 1.0)) {
-                      alwaysOnSchedule = object;
-                    }
-                  }
-                }
-              }
+          auto iddObject = idd_3_5_0.getObject(iddname);
+          IdfObject newObject(iddObject.get());
+          for (size_t i = 0; i < object.numFields(); ++i) {
+            if ((value = object.getString(i))) {
+              newObject.setString(i, value.get());
+            }
+          }
+          newObject.setString(2, getOrCreateAlwaysDiscreteScheduleHandleStr(true));
+
+          m_refactored.push_back(RefactoredObjectData(object, newObject));
+          ss << newObject;
+        }
+
+      } else if (iddname == "OS:ZoneHVAC:PackagedTerminalHeatPump") {
+
+        if (!object.isEmpty(23)) {
+          ss << object;
+
+        } else {
+          auto iddObject = idd_3_5_0.getObject(iddname);
+          IdfObject newObject(iddObject.get());
+
+          for (size_t i = 0; i < object.numFields(); ++i) {
+            if ((value = object.getString(i))) {
+              newObject.setString(i, value.get());
             }
           }
+          // alwaysOffDiscreteScheduleHandleStr has already been initialized above
+          newObject.setString(23, alwaysOffDiscreteScheduleHandleStr);
+
+          m_refactored.push_back(RefactoredObjectData(object, newObject));
+          ss << newObject;
+        }
+      } else if (iddname == "OS:ZoneHVAC:PackagedTerminalAirConditioner") {
+
+        if (!object.isEmpty(17)) {
+          ss << object;
+
+        } else {
+          auto iddObject = idd_3_5_0.getObject(iddname);
+          IdfObject newObject(iddObject.get());
 
-          if (!alwaysOnSchedule) {
-            alwaysOnSchedule = IdfObject(idd_3_5_0.getObject("OS:Schedule:Constant").get());
-            alwaysOnSchedule->setString(0, toString(createUUID()));
-            alwaysOnSchedule->setString(1, "Always On Discrete");
-            alwaysOnSchedule->setDouble(3, 1.0);
-            IdfObject typeLimits(idd_3_5_0.getObject("OS:ScheduleTypeLimits").get());
-            typeLimits.setString(0, toString(createUUID()));
-            typeLimits.setString(1, "Always On Discrete Limits");
-            typeLimits.setDouble(2, 0.0);
-            typeLimits.setDouble(3, 1.0);
-            typeLimits.setString(4, "Discrete");
-            typeLimits.setString(5, "Availability");
-            alwaysOnSchedule->setString(2, typeLimits.getString(0).get());
-            ss << alwaysOnSchedule.get();
-            ss << typeLimits;
-            m_new.push_back(alwaysOnSchedule.get());
-            m_new.push_back(typeLimits);
+          for (size_t i = 0; i < object.numFields(); ++i) {
+            if ((value = object.getString(i))) {
+              newObject.setString(i, value.get());
+            }
           }
+          // alwaysOffDiscreteScheduleHandleStr has already been initialized above
+          newObject.setString(17, alwaysOffDiscreteScheduleHandleStr);
 
-          newObject.setString(2, alwaysOnSchedule->getString(0).get());
+          m_refactored.push_back(RefactoredObjectData(object, newObject));
+          ss << newObject;
         }
 
-        m_refactored.push_back(RefactoredObjectData(object, newObject));
-        ss << newObject;
+      } else if (iddname == "OS:Fan:ConstantVolume") {
+        std::string fanHandleStr = object.getString(0).get();
+        if (std::find(packagedFanCVHandleStrs.cbegin(), packagedFanCVHandleStrs.cend(), fanHandleStr) == packagedFanCVHandleStrs.cend()) {
+          ss << object;
+        } else {
+          LOG(Warn, "Fan:ConstantVolume "
+                      << object.nameString()
+                      << " is used in a Packaged System (PTAC or PTHP) that does not have a Supply Air Fan Operating Mode Schedule. "
+                         "In 22.1.0 this would effectively, and mistakenly, function as a cycling fan, but this is now disallowed in E+ 22.2.0. "
+                         "In order to retain a similar functionality and energy usage, this will be replaced by a Fan:SystemModel "
+                         "with an always Off Schedule (cycling fan, similar to a Fan:OnOff)");
+          IdfObject newObject(idd_3_5_0.getObject("OS:Fan:SystemModel").get());
+          // Handle
+          if ((value = object.getString(0))) {
+            newObject.setString(0, value.get());
+          }
+
+          // Name
+          if ((value = object.getString(1))) {
+            newObject.setString(1, value.get());
+          }
+
+          // Availability Schedule Name
+          if ((value = object.getString(2))) {
+            newObject.setString(2, value.get());
+          }
+
+          // Note the use of "true" to return default. Fan:ConstantVolume has way more defaulted fields, while Fan:SystemModel adheres to the newer
+          // "make \required-field and hardcode in ctor" doctrine, AND has different defaults than Fan:ConstantVolume. So we do this to fulfil two
+          // goals: Put a value in all required-fields AND set the same values
+          // Fan Total Efficiency
+          if ((value = object.getString(3, true))) {
+            newObject.setString(15, value.get());
+          }
+
+          // Pressure Rise
+          if ((value = object.getString(4, true))) {
+            newObject.setString(8, value.get());
+          }
+
+          // Maximum Flow Rate
+          if ((value = object.getString(5, true))) {
+            newObject.setString(5, value.get());
+          }
+
+          // Motor Efficiency
+          if ((value = object.getString(6, true))) {
+            newObject.setString(9, value.get());
+          }
+
+          // Motor In Airstream Fraction
+          if ((value = object.getString(7, true))) {
+            newObject.setString(10, value.get());
+          }
+
+          // Nodes should be blank since it's inside a containing ZoneHVAC
+          // Air Inlet Node Name
+          if ((value = object.getString(8))) {
+            newObject.setString(3, value.get());
+          }
+
+          // Air Outlet Node Name
+          if ((value = object.getString(9))) {
+            newObject.setString(4, value.get());
+          }
+
+          // End-Use Subcategory
+          if ((value = object.getString(10, true))) {
+            newObject.setString(21, value.get());
+          }
+
+          // Speed Control Method
+          newObject.setString(6, "Discrete");
+
+          // Electric Power Minimum Flow Rate Fraction
+          newObject.setDouble(7, 0.0);
+
+          // Design Electric Power Consumption
+          newObject.setString(11, "Autosize");
+
+          // Design Power Sizing Method
+          newObject.setString(12, "TotalEfficiencyAndPressure");
+
+          // Electric Power Per Unit Flow Rate (not used given Power Sizing Method, but required-field & set in Ctor)
+          newObject.setDouble(13, 840.0);
+
+          // Electric Power Per Unit Flow Rate Per Unit Pressure (not used given Power Sizing Method, but required-field & set in Ctor)
+          newObject.setDouble(14, 1.66667);
+
+          // Electric Power Function of Flow Fraction Curve Name
+          newObject.setString(16, "");
+
+          // Night Ventilation Mode Pressure Rise
+          newObject.setString(17, "");
+
+          // Night Ventilation Mode Flow Fraction
+          newObject.setString(18, "");
+
+          // Motor Loss Zone Name
+          newObject.setString(19, "");
+
+          // Motor Loss Radiative Fraction
+          newObject.setDouble(20, 0.0);
+
+          m_refactored.push_back(RefactoredObjectData(object, newObject));
+          ss << newObject;
+        }
 
         // No-op
       } else {


Test patch:
diff --git a/src/model/test/ZoneHVACPackagedTerminalAirConditioner_GTest.cpp b/src/model/test/ZoneHVACPackagedTerminalAirConditioner_GTest.cpp
index 7c7ec66da00..e241ee6221a 100644
--- a/src/model/test/ZoneHVACPackagedTerminalAirConditioner_GTest.cpp
+++ b/src/model/test/ZoneHVACPackagedTerminalAirConditioner_GTest.cpp
@@ -55,6 +55,8 @@
 #include "../ZoneHVACPackagedTerminalAirConditioner.hpp"
 #include "../ZoneHVACPackagedTerminalAirConditioner_Impl.hpp"
 #include "../ScheduleCompact.hpp"
+#include "../FanOnOff.hpp"
+#include "../FanSystemModel.hpp"
 
 using namespace openstudio;
 
@@ -264,3 +266,23 @@ TEST_F(ModelFixture, ZoneHVACPackagedTerminalAirConditioner_CoilSystemCoolingDXH
 
   model::ZoneHVACPackagedTerminalAirConditioner ptac(m, s, fan, heatingCoil, coolingCoil);
 }
+
+TEST_F(ModelFixture, ZoneHVACPackagedTerminalAirConditioner_SupplyAirFanOpSch) {
+  model::Model m;
+  model::CoilHeatingWater heatingCoil(m);
+  model::CoilSystemCoolingDXHeatExchangerAssisted coolingCoil(m);
+  model::FanConstantVolume fanCV(m);
+  model::FanSystemModel fanSystemModel(m);
+  model::FanOnOff fanOnOff(m);
+  auto alwaysOn = m.alwaysOnDiscreteSchedule();
+  auto alwaysOff = m.alwaysOffDiscreteSchedule();
+
+  model::ZoneHVACPackagedTerminalAirConditioner ptacCV(m, alwaysOn, fanCV, heatingCoil, coolingCoil);
+  EXPECT_EQ(alwaysOn, ptacCV.supplyAirFanOperatingModeSchedule());
+
+  model::ZoneHVACPackagedTerminalAirConditioner ptacSystemModel(m, alwaysOn, fanSystemModel, heatingCoil, coolingCoil);
+  EXPECT_EQ(alwaysOff, ptacSystemModel.supplyAirFanOperatingModeSchedule());
+
+  model::ZoneHVACPackagedTerminalAirConditioner ptacOnOff(m, alwaysOn, fanOnOff, heatingCoil, coolingCoil);
+  EXPECT_EQ(alwaysOff, ptacOnOff.supplyAirFanOperatingModeSchedule());
+}
diff --git a/src/model/test/ZoneHVACPackagedTerminalHeatPump_GTest.cpp b/src/model/test/ZoneHVACPackagedTerminalHeatPump_GTest.cpp
index 2ec603faeeb..33614136921 100644
--- a/src/model/test/ZoneHVACPackagedTerminalHeatPump_GTest.cpp
+++ b/src/model/test/ZoneHVACPackagedTerminalHeatPump_GTest.cpp
@@ -55,6 +55,9 @@
 #include "../ZoneHVACPackagedTerminalHeatPump.hpp"
 #include "../ZoneHVACPackagedTerminalHeatPump_Impl.hpp"
 #include "../ScheduleCompact.hpp"
+#include "../FanOnOff.hpp"
+#include "../FanSystemModel.hpp"
+
 #include <utilities/idd/OS_Curve_Biquadratic_FieldEnums.hxx>
 
 using namespace openstudio;
@@ -310,3 +313,24 @@ TEST_F(ModelFixture, ZoneHVACPackagedTerminalHeatPump_CoilSystemCoolingDXHeatExc
 
   model::ZoneHVACPackagedTerminalHeatPump pthp(m, availabilitySchedule, fan, heatingCoil, coolingCoil, supplementalHeatingCoil);
 }
+
+TEST_F(ModelFixture, ZoneHVACPackagedTerminalHeatPump_SupplyAirFanOpSch) {
+  model::Model m;
+  model::CoilHeatingDXVariableSpeed heatingCoil(m);
+  model::CoilCoolingDXVariableSpeed coolingCoil(m);
+  model::CoilHeatingElectric supplementalHeatingCoil(m);
+  model::FanConstantVolume fanCV(m);
+  model::FanSystemModel fanSystemModel(m);
+  model::FanOnOff fanOnOff(m);
+  auto alwaysOn = m.alwaysOnDiscreteSchedule();
+  auto alwaysOff = m.alwaysOffDiscreteSchedule();
+
+  model::ZoneHVACPackagedTerminalHeatPump pthpCV(m, alwaysOn, fanCV, heatingCoil, coolingCoil, supplementalHeatingCoil);
+  EXPECT_EQ(alwaysOn, pthpCV.supplyAirFanOperatingModeSchedule());
+
+  model::ZoneHVACPackagedTerminalHeatPump pthpSystemModel(m, alwaysOn, fanSystemModel, heatingCoil, coolingCoil, supplementalHeatingCoil);
+  EXPECT_EQ(alwaysOff, pthpSystemModel.supplyAirFanOperatingModeSchedule());
+
+  model::ZoneHVACPackagedTerminalHeatPump pthpOnOff(m, alwaysOn, fanOnOff, heatingCoil, coolingCoil, supplementalHeatingCoil);
+  EXPECT_EQ(alwaysOff, pthpOnOff.supplyAirFanOperatingModeSchedule());
+}
diff --git a/src/osversion/test/3_5_0/test_vt_ZoneHVACPackaged.osm b/src/osversion/test/3_5_0/test_vt_ZoneHVACPackaged.osm
new file mode 100644
index 00000000000..4d85ab9ec78
--- /dev/null
+++ b/src/osversion/test/3_5_0/test_vt_ZoneHVACPackaged.osm
@@ -0,0 +1,360 @@
+
+OS:Version,
+  {f2bf6811-825f-4f83-af96-b22b0908f434}, !- Handle
+  3.4.0;                                  !- Version Identifier
+
+OS:Coil:Heating:DX:SingleSpeed,
+  {405cb32f-90c9-4c2c-b22a-ab246f71d243}, !- Handle
+  Coil Heating DX Single Speed 1,         !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  Autosize,                               !- Rated Total Heating Capacity {W}
+  5,                                      !- Rated COP {W/W}
+  Autosize,                               !- Rated Air Flow Rate {m3/s}
+  773.3,                                  !- Rated Supply Fan Power Per Volume Flow Rate {W/(m3/s)}
+  ,                                       !- Air Inlet Node Name
+  ,                                       !- Air Outlet Node Name
+  {9ccb40b4-6d59-4b04-adb4-161de6730946}, !- Total Heating Capacity Function of Temperature Curve Name
+  {f4080dc7-e573-461c-9b26-3f90ba7e1fdb}, !- Total Heating Capacity Function of Flow Fraction Curve Name
+  {cfcc34af-f893-4947-94b8-5ea9624b9256}, !- Energy Input Ratio Function of Temperature Curve Name
+  {9bdf7dad-cd90-400e-b291-4e62bce4cd3f}, !- Energy Input Ratio Function of Flow Fraction Curve Name
+  {60ae2897-36f4-4a0e-b581-fb4a6a941dec}, !- Part Load Fraction Correlation Curve Name
+  ,                                       !- Defrost Energy Input Ratio Function of Temperature Curve Name
+  ,                                       !- Minimum Outdoor Dry-Bulb Temperature for Compressor Operation {C}
+  ,                                       !- Maximum Outdoor Dry-Bulb Temperature for Defrost Operation {C}
+  ,                                       !- Crankcase Heater Capacity {W}
+  ,                                       !- Maximum Outdoor Dry-Bulb Temperature for Crankcase Heater Operation {C}
+  Resistive,                              !- Defrost Strategy
+  Timed,                                  !- Defrost Control
+  0.166667,                               !- Defrost Time Period Fraction
+  2000;                                   !- Resistive Defrost Heater Capacity {W}
+
+OS:Schedule:Constant,
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Handle
+  Always On Discrete,                     !- Name
+  {1a2f843f-ad5d-46a6-a190-83232b9c63f4}, !- Schedule Type Limits Name
+  1;                                      !- Value
+
+OS:ScheduleTypeLimits,
+  {1a2f843f-ad5d-46a6-a190-83232b9c63f4}, !- Handle
+  OnOff,                                  !- Name
+  0,                                      !- Lower Limit Value
+  1,                                      !- Upper Limit Value
+  Discrete,                               !- Numeric Type
+  Availability;                           !- Unit Type
+
+OS:Curve:Cubic,
+  {9ccb40b4-6d59-4b04-adb4-161de6730946}, !- Handle
+  Curve Cubic 1,                          !- Name
+  0.758746,                               !- Coefficient1 Constant
+  0.027626,                               !- Coefficient2 x
+  0.000148716,                            !- Coefficient3 x**2
+  3.4992e-06,                             !- Coefficient4 x**3
+  -20,                                    !- Minimum Value of x
+  20;                                     !- Maximum Value of x
+
+OS:Curve:Cubic,
+  {f4080dc7-e573-461c-9b26-3f90ba7e1fdb}, !- Handle
+  Curve Cubic 2,                          !- Name
+  0.84,                                   !- Coefficient1 Constant
+  0.16,                                   !- Coefficient2 x
+  0,                                      !- Coefficient3 x**2
+  0,                                      !- Coefficient4 x**3
+  0.5,                                    !- Minimum Value of x
+  1.5;                                    !- Maximum Value of x
+
+OS:Curve:Cubic,
+  {cfcc34af-f893-4947-94b8-5ea9624b9256}, !- Handle
+  Curve Cubic 3,                          !- Name
+  1.19248,                                !- Coefficient1 Constant
+  -0.0300438,                             !- Coefficient2 x
+  0.00103745,                             !- Coefficient3 x**2
+  -2.3328e-05,                            !- Coefficient4 x**3
+  -20,                                    !- Minimum Value of x
+  20;                                     !- Maximum Value of x
+
+OS:Curve:Quadratic,
+  {9bdf7dad-cd90-400e-b291-4e62bce4cd3f}, !- Handle
+  Curve Quadratic 1,                      !- Name
+  1.3824,                                 !- Coefficient1 Constant
+  -0.4336,                                !- Coefficient2 x
+  0.0512,                                 !- Coefficient3 x**2
+  0,                                      !- Minimum Value of x
+  1;                                      !- Maximum Value of x
+
+OS:Curve:Quadratic,
+  {60ae2897-36f4-4a0e-b581-fb4a6a941dec}, !- Handle
+  Curve Quadratic 2,                      !- Name
+  0.75,                                   !- Coefficient1 Constant
+  0.25,                                   !- Coefficient2 x
+  0,                                      !- Coefficient3 x**2
+  0,                                      !- Minimum Value of x
+  1;                                      !- Maximum Value of x
+
+OS:Coil:Cooling:DX:SingleSpeed,
+  {c3dc2852-18f2-4199-a93b-2da0c5ed3f64}, !- Handle
+  Coil Cooling DX Single Speed 1,         !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  autosize,                               !- Rated Total Cooling Capacity {W}
+  autosize,                               !- Rated Sensible Heat Ratio
+  3,                                      !- Rated COP {W/W}
+  autosize,                               !- Rated Air Flow Rate {m3/s}
+  773.3,                                  !- Rated Evaporator Fan Power Per Volume Flow Rate {W/(m3/s)}
+  ,                                       !- Air Inlet Node Name
+  ,                                       !- Air Outlet Node Name
+  {20cbc7d0-9047-47f4-b3cf-7ebce6dc86ed}, !- Total Cooling Capacity Function of Temperature Curve Name
+  {63462c10-09fc-4c5a-8312-bbe6e8b828bc}, !- Total Cooling Capacity Function of Flow Fraction Curve Name
+  {ef75106e-4029-43dc-a1ca-21dac784d198}, !- Energy Input Ratio Function of Temperature Curve Name
+  {192ff408-13b7-48ed-b324-45aef010bb3a}, !- Energy Input Ratio Function of Flow Fraction Curve Name
+  {2665cb2e-73c1-4ff6-b337-8f13740c0d91}, !- Part Load Fraction Correlation Curve Name
+  -25,                                    !- Minimum Outdoor Dry-Bulb Temperature for Compressor Operation {C}
+  ,                                       !- Nominal Time for Condensate Removal to Begin {s}
+  ,                                       !- Ratio of Initial Moisture Evaporation Rate and Steady State Latent Capacity {dimensionless}
+  ,                                       !- Maximum Cycling Rate {cycles/hr}
+  ,                                       !- Latent Capacity Time Constant {s}
+  ,                                       !- Condenser Air Inlet Node Name
+  AirCooled,                              !- Condenser Type
+  0,                                      !- Evaporative Condenser Effectiveness {dimensionless}
+  Autosize,                               !- Evaporative Condenser Air Flow Rate {m3/s}
+  Autosize,                               !- Evaporative Condenser Pump Rated Power Consumption {W}
+  0,                                      !- Crankcase Heater Capacity {W}
+  0,                                      !- Maximum Outdoor Dry-Bulb Temperature for Crankcase Heater Operation {C}
+  ,                                       !- Supply Water Storage Tank Name
+  ,                                       !- Condensate Collection Water Storage Tank Name
+  0,                                      !- Basin Heater Capacity {W/K}
+  10,                                     !- Basin Heater Setpoint Temperature {C}
+  ;                                       !- Basin Heater Operating Schedule Name
+
+OS:Curve:Biquadratic,
+  {20cbc7d0-9047-47f4-b3cf-7ebce6dc86ed}, !- Handle
+  Curve Biquadratic 1,                    !- Name
+  0.942587793,                            !- Coefficient1 Constant
+  0.009543347,                            !- Coefficient2 x
+  0.00068377,                             !- Coefficient3 x**2
+  -0.011042676,                           !- Coefficient4 y
+  5.249e-06,                              !- Coefficient5 y**2
+  -9.72e-06,                              !- Coefficient6 x*y
+  17,                                     !- Minimum Value of x
+  22,                                     !- Maximum Value of x
+  13,                                     !- Minimum Value of y
+  46;                                     !- Maximum Value of y
+
+OS:Curve:Quadratic,
+  {63462c10-09fc-4c5a-8312-bbe6e8b828bc}, !- Handle
+  Curve Quadratic 3,                      !- Name
+  0.8,                                    !- Coefficient1 Constant
+  0.2,                                    !- Coefficient2 x
+  0,                                      !- Coefficient3 x**2
+  0.5,                                    !- Minimum Value of x
+  1.5;                                    !- Maximum Value of x
+
+OS:Curve:Biquadratic,
+  {ef75106e-4029-43dc-a1ca-21dac784d198}, !- Handle
+  Curve Biquadratic 2,                    !- Name
+  0.342414409,                            !- Coefficient1 Constant
+  0.034885008,                            !- Coefficient2 x
+  -0.0006237,                             !- Coefficient3 x**2
+  0.004977216,                            !- Coefficient4 y
+  0.000437951,                            !- Coefficient5 y**2
+  -0.000728028,                           !- Coefficient6 x*y
+  17,                                     !- Minimum Value of x
+  22,                                     !- Maximum Value of x
+  13,                                     !- Minimum Value of y
+  46;                                     !- Maximum Value of y
+
+OS:Curve:Quadratic,
+  {192ff408-13b7-48ed-b324-45aef010bb3a}, !- Handle
+  Curve Quadratic 4,                      !- Name
+  1.1552,                                 !- Coefficient1 Constant
+  -0.1808,                                !- Coefficient2 x
+  0.0256,                                 !- Coefficient3 x**2
+  0.5,                                    !- Minimum Value of x
+  1.5;                                    !- Maximum Value of x
+
+OS:Curve:Quadratic,
+  {2665cb2e-73c1-4ff6-b337-8f13740c0d91}, !- Handle
+  Curve Quadratic 5,                      !- Name
+  0.85,                                   !- Coefficient1 Constant
+  0.15,                                   !- Coefficient2 x
+  0,                                      !- Coefficient3 x**2
+  0,                                      !- Minimum Value of x
+  1;                                      !- Maximum Value of x
+
+OS:Coil:Heating:Electric,
+  {e90e9d96-bf46-4b1e-9a99-5ea9da8c61f5}, !- Handle
+  Coil Heating Electric 1,                !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  ,                                       !- Efficiency
+  ,                                       !- Nominal Capacity {W}
+  ,                                       !- Air Inlet Node Name
+  ;                                       !- Air Outlet Node Name
+
+OS:Fan:ConstantVolume,
+  {6600a5aa-c5e7-4eb2-8d4c-5fcbf38c5dbb}, !- Handle
+  PTHP Fan,                               !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  ,                                       !- Fan Total Efficiency
+  ,                                       !- Pressure Rise {Pa}
+  AutoSize,                               !- Maximum Flow Rate {m3/s}
+  ,                                       !- Motor Efficiency
+  ,                                       !- Motor In Airstream Fraction
+  ,                                       !- Air Inlet Node Name
+  ,                                       !- Air Outlet Node Name
+  ;                                       !- End-Use Subcategory
+
+OS:ZoneHVAC:PackagedTerminalHeatPump,
+  {bddf0afb-148c-4e76-8d36-479a2e08a2ef}, !- Handle
+  Zone HVAC Packaged Terminal Heat Pump 1, !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  ,                                       !- Air Inlet Node Name
+  ,                                       !- Air Outlet Node Name
+  OutdoorAir:Mixer,                       !- Outdoor Air Mixer Object Type
+  ,                                       !- Outdoor Air Mixer Name
+  Autosize,                               !- Supply Air Flow Rate During Cooling Operation {m3/s}
+  Autosize,                               !- Supply Air Flow Rate During Heating Operation {m3/s}
+  Autosize,                               !- Supply Air Flow Rate When No Cooling or Heating is Needed {m3/s}
+  Autosize,                               !- Outdoor Air Flow Rate During Cooling Operation {m3/s}
+  Autosize,                               !- Outdoor Air Flow Rate During Heating Operation {m3/s}
+  Autosize,                               !- Outdoor Air Flow Rate When No Cooling or Heating is Needed {m3/s}
+  {6600a5aa-c5e7-4eb2-8d4c-5fcbf38c5dbb}, !- Supply Air Fan Name
+  {405cb32f-90c9-4c2c-b22a-ab246f71d243}, !- Heating Coil Name
+  ,                                       !- Heating Convergence Tolerance {dimensionless}
+  ,                                       !- Minimum Outdoor Dry-Bulb Temperature for Compressor Operation {C}
+  {c3dc2852-18f2-4199-a93b-2da0c5ed3f64}, !- Cooling Coil Name
+  ,                                       !- Cooling Convergence Tolerance {dimensionless}
+  {e90e9d96-bf46-4b1e-9a99-5ea9da8c61f5}, !- Supplemental Heating Coil Name
+  Autosize,                               !- Maximum Supply Air Temperature from Supplemental Heater {C}
+  ,                                       !- Maximum Outdoor Dry-Bulb Temperature for Supplemental Heater Operation {C}
+  ,                                       !- Fan Placement
+  ;                                       !- Supply Air Fan Operating Mode Schedule Name
+
+OS:Coil:Heating:Electric,
+  {06692db9-5485-4524-8135-fc0f2ac8c40d}, !- Handle
+  Coil Heating Electric 2,                !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  ,                                       !- Efficiency
+  ,                                       !- Nominal Capacity {W}
+  ,                                       !- Air Inlet Node Name
+  ;                                       !- Air Outlet Node Name
+
+OS:Coil:Cooling:DX:SingleSpeed,
+  {d8b44275-6385-4ddd-8890-47ff167c4652}, !- Handle
+  Coil Cooling DX Single Speed 2,         !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  autosize,                               !- Rated Total Cooling Capacity {W}
+  autosize,                               !- Rated Sensible Heat Ratio
+  3,                                      !- Rated COP {W/W}
+  autosize,                               !- Rated Air Flow Rate {m3/s}
+  773.3,                                  !- Rated Evaporator Fan Power Per Volume Flow Rate {W/(m3/s)}
+  ,                                       !- Air Inlet Node Name
+  ,                                       !- Air Outlet Node Name
+  {66fc8437-d62b-4abe-8971-0aca40d45c4a}, !- Total Cooling Capacity Function of Temperature Curve Name
+  {0fe5e1aa-db99-4aa1-947f-377e904875ec}, !- Total Cooling Capacity Function of Flow Fraction Curve Name
+  {c339821c-d155-4f36-865a-a55e1321dae5}, !- Energy Input Ratio Function of Temperature Curve Name
+  {15a0df0a-d826-4d8b-94b7-e2291ee426a5}, !- Energy Input Ratio Function of Flow Fraction Curve Name
+  {fe1f5765-b70e-4436-92a1-145ca6f84746}, !- Part Load Fraction Correlation Curve Name
+  -25,                                    !- Minimum Outdoor Dry-Bulb Temperature for Compressor Operation {C}
+  ,                                       !- Nominal Time for Condensate Removal to Begin {s}
+  ,                                       !- Ratio of Initial Moisture Evaporation Rate and Steady State Latent Capacity {dimensionless}
+  ,                                       !- Maximum Cycling Rate {cycles/hr}
+  ,                                       !- Latent Capacity Time Constant {s}
+  ,                                       !- Condenser Air Inlet Node Name
+  AirCooled,                              !- Condenser Type
+  0,                                      !- Evaporative Condenser Effectiveness {dimensionless}
+  Autosize,                               !- Evaporative Condenser Air Flow Rate {m3/s}
+  Autosize,                               !- Evaporative Condenser Pump Rated Power Consumption {W}
+  0,                                      !- Crankcase Heater Capacity {W}
+  0,                                      !- Maximum Outdoor Dry-Bulb Temperature for Crankcase Heater Operation {C}
+  ,                                       !- Supply Water Storage Tank Name
+  ,                                       !- Condensate Collection Water Storage Tank Name
+  0,                                      !- Basin Heater Capacity {W/K}
+  10,                                     !- Basin Heater Setpoint Temperature {C}
+  ;                                       !- Basin Heater Operating Schedule Name
+
+OS:Curve:Biquadratic,
+  {66fc8437-d62b-4abe-8971-0aca40d45c4a}, !- Handle
+  Curve Biquadratic 3,                    !- Name
+  0.942587793,                            !- Coefficient1 Constant
+  0.009543347,                            !- Coefficient2 x
+  0.00068377,                             !- Coefficient3 x**2
+  -0.011042676,                           !- Coefficient4 y
+  5.249e-06,                              !- Coefficient5 y**2
+  -9.72e-06,                              !- Coefficient6 x*y
+  17,                                     !- Minimum Value of x
+  22,                                     !- Maximum Value of x
+  13,                                     !- Minimum Value of y
+  46;                                     !- Maximum Value of y
+
+OS:Curve:Quadratic,
+  {0fe5e1aa-db99-4aa1-947f-377e904875ec}, !- Handle
+  Curve Quadratic 6,                      !- Name
+  0.8,                                    !- Coefficient1 Constant
+  0.2,                                    !- Coefficient2 x
+  0,                                      !- Coefficient3 x**2
+  0.5,                                    !- Minimum Value of x
+  1.5;                                    !- Maximum Value of x
+
+OS:Curve:Biquadratic,
+  {c339821c-d155-4f36-865a-a55e1321dae5}, !- Handle
+  Curve Biquadratic 4,                    !- Name
+  0.342414409,                            !- Coefficient1 Constant
+  0.034885008,                            !- Coefficient2 x
+  -0.0006237,                             !- Coefficient3 x**2
+  0.004977216,                            !- Coefficient4 y
+  0.000437951,                            !- Coefficient5 y**2
+  -0.000728028,                           !- Coefficient6 x*y
+  17,                                     !- Minimum Value of x
+  22,                                     !- Maximum Value of x
+  13,                                     !- Minimum Value of y
+  46;                                     !- Maximum Value of y
+
+OS:Curve:Quadratic,
+  {15a0df0a-d826-4d8b-94b7-e2291ee426a5}, !- Handle
+  Curve Quadratic 7,                      !- Name
+  1.1552,                                 !- Coefficient1 Constant
+  -0.1808,                                !- Coefficient2 x
+  0.0256,                                 !- Coefficient3 x**2
+  0.5,                                    !- Minimum Value of x
+  1.5;                                    !- Maximum Value of x
+
+OS:Curve:Quadratic,
+  {fe1f5765-b70e-4436-92a1-145ca6f84746}, !- Handle
+  Curve Quadratic 8,                      !- Name
+  0.85,                                   !- Coefficient1 Constant
+  0.15,                                   !- Coefficient2 x
+  0,                                      !- Coefficient3 x**2
+  0,                                      !- Minimum Value of x
+  1;                                      !- Maximum Value of x
+
+OS:Fan:ConstantVolume,
+  {048e8bf0-1f9b-40d4-a0b0-d787991f3d94}, !- Handle
+  PTAC Fan,                               !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  0.75,                                   !- Fan Total Efficiency
+  265,                                    !- Pressure Rise {Pa}
+  0.5,                                    !- Maximum Flow Rate {m3/s}
+  0.89,                                   !- Motor Efficiency
+  0.92,                                   !- Motor In Airstream Fraction
+  ,                                       !- Air Inlet Node Name
+  ,                                       !- Air Outlet Node Name
+  PTAC Fans;                              !- End-Use Subcategory
+
+OS:ZoneHVAC:PackagedTerminalAirConditioner,
+  {f1f8d2af-4f9a-4c72-ae93-dd11d9d6ab49}, !- Handle
+  Zone HVAC Packaged Terminal Air Conditioner 1, !- Name
+  {3f9d2cbc-47cd-4719-8f61-27cebb66d356}, !- Availability Schedule Name
+  ,                                       !- Air Inlet Node Name
+  ,                                       !- Air Outlet Node Name
+  OutdoorAir:Mixer,                       !- Outdoor Air Mixer Object Type
+  ,                                       !- Outdoor Air Mixer Name
+  Autosize,                               !- Supply Air Flow Rate During Cooling Operation {m3/s}
+  Autosize,                               !- Supply Air Flow Rate During Heating Operation {m3/s}
+  Autosize,                               !- Supply Air Flow Rate When No Cooling or Heating is Needed {m3/s}
+  Autosize,                               !- Outdoor Air Flow Rate During Cooling Operation {m3/s}
+  Autosize,                               !- Outdoor Air Flow Rate During Heating Operation {m3/s}
+  Autosize,                               !- Outdoor Air Flow Rate When No Cooling or Heating is Needed {m3/s}
+  {048e8bf0-1f9b-40d4-a0b0-d787991f3d94}, !- Supply Air Fan Name
+  {06692db9-5485-4524-8135-fc0f2ac8c40d}, !- Heating Coil Name
+  {d8b44275-6385-4ddd-8890-47ff167c4652}, !- Cooling Coil Name
+  DrawThrough,                            !- Fan Placement
+  ;                                       !- Supply Air Fan Operating Mode Schedule Name
+
diff --git a/src/osversion/test/3_5_0/test_vt_ZoneHVACPackaged.rb b/src/osversion/test/3_5_0/test_vt_ZoneHVACPackaged.rb
new file mode 100644
index 00000000000..4b86101b614
--- /dev/null
+++ b/src/osversion/test/3_5_0/test_vt_ZoneHVACPackaged.rb
@@ -0,0 +1,31 @@
+#require '/usr/local/openstudio-3.4.0/Ruby/openstudio'
+
+include OpenStudio::Model
+
+m = Model.new
+
+pthp_hc = CoilHeatingDXSingleSpeed.new(m)
+pthp_cc = CoilCoolingDXSingleSpeed.new(m)
+pthp_suppHC = CoilHeatingElectric.new(m)
+pthp_fan = FanConstantVolume.new(m)
+pthp_fan.setName("PTHP Fan")
+pthp = ZoneHVACPackagedTerminalHeatPump.new(m, m.alwaysOnDiscreteSchedule, pthp_fan, pthp_hc, pthp_cc, pthp_suppHC)
+pthp.resetSupplyAirFanOperatingModeSchedule
+
+ptac_hc = CoilHeatingElectric.new(m)
+ptac_cc = CoilCoolingDXSingleSpeed.new(m)
+ptac_fan = FanConstantVolume.new(m)
+ptac_fan.setName("PTAC Fan")
+ptac = ZoneHVACPackagedTerminalAirConditioner.new(m, m.alwaysOnDiscreteSchedule, ptac_fan, ptac_hc, ptac_cc)
+ptac.resetSupplyAirFanOperatingModeSchedule
+
+# Leaving the pthp fan will all defaults, harcoding this one so we test both
+# cases and ensure the resulting Fan:SystemModel is correct
+ptac_fan.setFanTotalEfficiency(0.75)
+ptac_fan.setPressureRise(265)
+ptac_fan.setMaximumFlowRate(0.5)
+ptac_fan.setMotorEfficiency(0.89)
+ptac_fan.setMotorInAirstreamFraction(0.92)
+ptac_fan.setEndUseSubcategory("PTAC Fans")
+
+m.save('test_vt_ZoneHVACPackaged.osm', true)
diff --git a/src/osversion/test/VersionTranslator_GTest.cpp b/src/osversion/test/VersionTranslator_GTest.cpp
index 8d6ce36e644..c8193edeb45 100644
--- a/src/osversion/test/VersionTranslator_GTest.cpp
+++ b/src/osversion/test/VersionTranslator_GTest.cpp
@@ -53,6 +53,7 @@
 #include "../../utilities/idf/IdfExtensibleGroup.hpp"
 #include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
 
+#include <utilities/idd/OS_Fan_SystemModel_FieldEnums.hxx>
 #include <utilities/idd/OS_Connection_FieldEnums.hxx>
 #include <utilities/idd/OS_Version_FieldEnums.hxx>
 #include <utilities/idd/IddEnums.hxx>
@@ -2228,3 +2229,93 @@ TEST_F(OSVersionFixture, update_3_4_0_to_3_5_0_CoilHeatingGasMultiStage) {
   ASSERT_TRUE(coil.getTarget(2));
   EXPECT_EQ("Always On Discrete", coil.getTarget(2)->nameString());
 }
+
+TEST_F(OSVersionFixture, update_3_4_0_to_3_5_0_ZoneHVACPackaged) {
+  openstudio::path path = resourcesPath() / toPath("osversion/3_5_0/test_vt_ZoneHVACPackaged.osm");
+  osversion::VersionTranslator vt;
+  boost::optional<model::Model> model = vt.loadModel(path);
+  ASSERT_TRUE(model) << "Failed to load " << path;
+
+  openstudio::path outPath = resourcesPath() / toPath("osversion/3_3_0/test_vt_ZoneHVACPackaged_updated.osm");
+  model->save(outPath, true);
+
+  EXPECT_EQ(2, model->getObjectsByType("OS:Fan:SystemModel").size());
+  {
+    std::vector<WorkspaceObject> ptacs = model->getObjectsByType("OS:ZoneHVAC:PackagedTerminalAirConditioner");
+    ASSERT_EQ(1u, ptacs.size());
+    auto& ptac = ptacs.front();
+
+    // Check the Supply Air Fan Operating Mode Schedule
+    ASSERT_TRUE(ptac.getTarget(17));
+    WorkspaceObject fanOpSch = ptac.getTarget(17).get();
+    EXPECT_EQ("Always Off Discrete", fanOpSch.nameString());
+    EXPECT_EQ(0.0, fanOpSch.getDouble(3).get());
+
+    // Check the Fan, converted from Fan:ConstantVolume to Fan:SystemModel
+    ASSERT_TRUE(ptac.getTarget(13));
+    WorkspaceObject fan = ptac.getTarget(13).get();
+
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_Fan_SystemModel), fan.iddObject().type());
+    EXPECT_EQ("PTAC Fan", fan.getString(OS_Fan_SystemModelFields::Name).get());
+    EXPECT_EQ("Always On Discrete", fan.getString(OS_Fan_SystemModelFields::AvailabilityScheduleName).get());
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::AirInletNodeName));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::AirOutletNodeName));
+    EXPECT_EQ(0.5, fan.getDouble(OS_Fan_SystemModelFields::DesignMaximumAirFlowRate).get());
+    EXPECT_EQ("Discrete", fan.getString(OS_Fan_SystemModelFields::SpeedControlMethod).get());
+    EXPECT_EQ(0.0, fan.getDouble(OS_Fan_SystemModelFields::ElectricPowerMinimumFlowRateFraction).get());
+    EXPECT_EQ(265.0, fan.getDouble(OS_Fan_SystemModelFields::DesignPressureRise).get());
+    EXPECT_EQ(0.89, fan.getDouble(OS_Fan_SystemModelFields::MotorEfficiency).get());
+    EXPECT_EQ(0.92, fan.getDouble(OS_Fan_SystemModelFields::MotorInAirStreamFraction).get());
+    EXPECT_EQ("Autosize", fan.getString(OS_Fan_SystemModelFields::DesignElectricPowerConsumption).get());
+    EXPECT_EQ("TotalEfficiencyAndPressure", fan.getString(OS_Fan_SystemModelFields::DesignPowerSizingMethod).get());
+    EXPECT_EQ(840.0, fan.getDouble(OS_Fan_SystemModelFields::ElectricPowerPerUnitFlowRate).get());
+    EXPECT_EQ(1.66667, fan.getDouble(OS_Fan_SystemModelFields::ElectricPowerPerUnitFlowRatePerUnitPressure).get());
+    EXPECT_EQ(0.75, fan.getDouble(OS_Fan_SystemModelFields::FanTotalEfficiency).get());
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::ElectricPowerFunctionofFlowFractionCurveName));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::NightVentilationModePressureRise));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::NightVentilationModeFlowFraction));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::MotorLossZoneName));
+    EXPECT_EQ(0.0, fan.getDouble(OS_Fan_SystemModelFields::MotorLossRadiativeFraction).get());
+    EXPECT_EQ("PTAC Fans", fan.getString(OS_Fan_SystemModelFields::EndUseSubcategory).get());
+  }
+
+  {
+    std::vector<WorkspaceObject> pthps = model->getObjectsByType("OS:ZoneHVAC:PackagedTerminalHeatPump");
+    ASSERT_EQ(1u, pthps.size());
+    auto& pthp = pthps.front();
+
+    // Check the Supply Air Fan Operating Mode Schedule
+    ASSERT_TRUE(pthp.getTarget(23));
+    WorkspaceObject fanOpSch = pthp.getTarget(23).get();
+    EXPECT_EQ("Always Off Discrete", fanOpSch.nameString());
+    EXPECT_EQ(0.0, fanOpSch.getDouble(3).get());
+
+    // Check the Fan, converted from Fan:ConstantVolume to Fan:SystemModel
+    ASSERT_TRUE(pthp.getTarget(13));
+    WorkspaceObject fan = pthp.getTarget(13).get();
+
+    // This one is all defaulted, ensure we get the SAME values
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_Fan_SystemModel), fan.iddObject().type());
+    EXPECT_EQ("PTHP Fan", fan.getString(OS_Fan_SystemModelFields::Name).get());
+    EXPECT_EQ("Always On Discrete", fan.getString(OS_Fan_SystemModelFields::AvailabilityScheduleName).get());
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::AirInletNodeName));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::AirOutletNodeName));
+    EXPECT_EQ("AutoSize", fan.getString(OS_Fan_SystemModelFields::DesignMaximumAirFlowRate).get());
+    EXPECT_EQ("Discrete", fan.getString(OS_Fan_SystemModelFields::SpeedControlMethod).get());
+    EXPECT_EQ(0.0, fan.getDouble(OS_Fan_SystemModelFields::ElectricPowerMinimumFlowRateFraction).get());
+    EXPECT_EQ(250.0, fan.getDouble(OS_Fan_SystemModelFields::DesignPressureRise).get());
+    EXPECT_EQ(0.9, fan.getDouble(OS_Fan_SystemModelFields::MotorEfficiency).get());
+    EXPECT_EQ(1.0, fan.getDouble(OS_Fan_SystemModelFields::MotorInAirStreamFraction).get());
+    EXPECT_EQ("Autosize", fan.getString(OS_Fan_SystemModelFields::DesignElectricPowerConsumption).get());
+    EXPECT_EQ("TotalEfficiencyAndPressure", fan.getString(OS_Fan_SystemModelFields::DesignPowerSizingMethod).get());
+    EXPECT_EQ(840.0, fan.getDouble(OS_Fan_SystemModelFields::ElectricPowerPerUnitFlowRate).get());
+    EXPECT_EQ(1.66667, fan.getDouble(OS_Fan_SystemModelFields::ElectricPowerPerUnitFlowRatePerUnitPressure).get());
+    EXPECT_EQ(0.7, fan.getDouble(OS_Fan_SystemModelFields::FanTotalEfficiency).get());
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::ElectricPowerFunctionofFlowFractionCurveName));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::NightVentilationModePressureRise));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::NightVentilationModeFlowFraction));
+    EXPECT_TRUE(fan.isEmpty(OS_Fan_SystemModelFields::MotorLossZoneName));
+    EXPECT_EQ(0.0, fan.getDouble(OS_Fan_SystemModelFields::MotorLossRadiativeFraction).get());
+    EXPECT_EQ("General", fan.getString(OS_Fan_SystemModelFields::EndUseSubcategory).get());
+  }
+}