Repository: NREL/OpenStudio (language: python)

Issue:
Wrap E+ 22.2 `People`:  new `Ankle Level Air Velocity Schedule Name`, `Cold Stress Temperature Threshold`, and `Heat Stress Temperature Threshold` fields

Wrap E+ 22.2 `People`:  new `Ankle Level Air Velocity Schedule Name`, `Cold Stress Temperature Threshold`, and `Heat Stress Temperature Threshold` fields



Discussion / hints:

Fix patch:
diff --git a/resources/energyplus/ProposedEnergy+.idd b/resources/energyplus/ProposedEnergy+.idd
index 46842f52c52..0047118a5bc 100644
--- a/resources/energyplus/ProposedEnergy+.idd
+++ b/resources/energyplus/ProposedEnergy+.idd
@@ -11313,9 +11313,7 @@ People,
    \memo If a ZoneList, SpaceList, or a Zone comprised of more than one Space is specified
    \memo then this definition applies to all applicable spaces, and each instance will
    \memo be named with the Space Name plus this Object Name.
-   \extensible:1
    \min-fields 10
-   \max-fields 27
   A1 , \field Name
        \required-field
        \type alpha
@@ -11422,33 +11420,92 @@ People,
        \object-list ScheduleNames
        \note units in the schedule are m/s
        \note optional (only required for runs of thermal comfort models: Fanger, Pierce, KSU, CoolingEffectASH55 and AnkleDraftASH55)
-  A14, \field Ankle Level Air Velocity Schedule Name
+  A14, \field Thermal Comfort Model 1 Type
+       \type choice
+       \key Fanger
+       \key Pierce
+       \key KSU
+       \key AdaptiveASH55
+       \key AdaptiveCEN15251
+       \key CoolingEffectASH55
+       \key AnkleDraftASH55
+       \note optional (only needed for people thermal comfort results reporting)
+  A15, \field Thermal Comfort Model 2 Type
+       \type choice
+       \key Fanger
+       \key Pierce
+       \key KSU
+       \key AdaptiveASH55
+       \key AdaptiveCEN15251
+       \key CoolingEffectASH55
+       \key AnkleDraftASH55
+       \note optional (second type of thermal comfort model and results reporting)
+  A16, \field Thermal Comfort Model 3 Type
+       \type choice
+       \key Fanger
+       \key Pierce
+       \key KSU
+       \key AdaptiveASH55
+       \key AdaptiveCEN15251
+       \key CoolingEffectASH55
+       \key AnkleDraftASH55
+       \note optional (third thermal comfort model and report type)
+  A17, \field Thermal Comfort Model 4 Type
+       \type choice
+       \key Fanger
+       \key Pierce
+       \key KSU
+       \key AdaptiveASH55
+       \key AdaptiveCEN15251
+       \key CoolingEffectASH55
+       \key AnkleDraftASH55
+       \note optional (fourth thermal comfort model and report type)
+  A18, \field Thermal Comfort Model 5 Type
+       \type choice
+       \key Fanger
+       \key Pierce
+       \key KSU
+       \key AdaptiveASH55
+       \key AdaptiveCEN15251
+       \key CoolingEffectASH55
+       \key AnkleDraftASH55
+       \note optional (fifth thermal comfort model and report type)
+  A19, \field Thermal Comfort Model 6 Type
+       \type choice
+       \key Fanger
+       \key Pierce
+       \key KSU
+       \key AdaptiveASH55
+       \key AdaptiveCEN15251
+       \key CoolingEffectASH55
+       \key AnkleDraftASH55
+       \note optional (sixth thermal comfort model and report type)
+  A20, \field Thermal Comfort Model 7 Type
+       \type choice
+       \key Fanger
+       \key Pierce
+       \key KSU
+       \key AdaptiveASH55
+       \key AdaptiveCEN15251
+       \key CoolingEffectASH55
+       \key AnkleDraftASH55
+       \note optional (seventh thermal comfort model and report type)
+  A21, \field Ankle Level Air Velocity Schedule Name
        \type object-list
        \object-list ScheduleNames
        \note units in the schedule are m/s
        \note this is the schedule of the air speed at the 0.1 m above the floor
        \note optional (only required for runs of thermal comfort models AnkleDraftASH55)
-  N7 , \field Cold Stress Temperature Threshold
+  N7,  \field Cold Stress Temperature Threshold
        \type real
        \units C
        \note this is the indoor safe temperature threshold for cold stress
        \default 15.56
-  N8 , \field Heat Stress Temperature Threshold
+  N8;  \field Heat Stress Temperature Threshold
        \type real
        \units C
        \note this is the indoor safe temperature threshold for heat stress
        \default 30
-  A15; \field Thermal Comfort Model 1 Type
-       \type choice
-       \key Fanger
-       \key Pierce
-       \key KSU
-       \key AdaptiveASH55
-       \key AdaptiveCEN15251
-       \key CoolingEffectASH55
-       \key AnkleDraftASH55
-       \note optional (only needed for people thermal comfort results reporting)
-       \begin-extensible
 
 ComfortViewFactorAngles,
        \memo Used to specify radiant view factors for thermal comfort calculations.
@@ -22262,7 +22319,7 @@ Sizing:Zone,
       \note Use SupplyAirHumidityRatio to enter the humidity ratio when zone dehumidification
       \note is required. The supply air humidity ratio should be less than the zone humidity
       \note ratio at the zone thermostat and humidistat set point condition.
-      \note Use HumidityRatioDifference to enter the difference in humidity ratio from the 
+      \note Use HumidityRatioDifference to enter the difference in humidity ratio from the
       \note zone thermostat and humidistat set point condition.
       \type choice
       \key SupplyAirHumidityRatio
@@ -22281,7 +22338,7 @@ Sizing:Zone,
       \note Zone Latent Cooling Design Supply Air Humidity Ratio Input Method = HumidityRatioDifference.
       \note This input is a positive value and defines the difference between the zone humidity
       \note ratio at the thermostat and humidistat set point condition and the supply air
-      \note humidity ratio entering the zone. 
+      \note humidity ratio entering the zone.
       \minimum> 0.0
       \type real
       \default 0.005
@@ -22290,7 +22347,7 @@ Sizing:Zone,
       \note Use SupplyAirHumidityRatio to enter the humidity ratio when zone humidification
       \note is required. The supply air humidity ratio should be greater than the zone humidity
       \note ratio at the zone thermostat and humidistat set point condition.
-      \note Use HumidityRatioDifference to enter the difference in humidity ratio from the 
+      \note Use HumidityRatioDifference to enter the difference in humidity ratio from the
       \note zone thermostat and humidistat set point condition.
       \type choice
       \key SupplyAirHumidityRatio
@@ -22309,7 +22366,7 @@ Sizing:Zone,
       \note Heating Design Supply Air Humidity Ratio Input Method = HumidityRatioDifference.
       \note This input is a positive value and defines the difference between the zone humidity
       \note ratio at the thermostat and humidistat set point condition and the supply air
-      \note humidity ratio entering the zone. 
+      \note humidity ratio entering the zone.
       \minimum 0.0
       \type real
       \default 0.005
@@ -28504,13 +28561,13 @@ ZoneHVAC:AirDistributionUnit,
        \object-list DesignSpecificationAirTerminalSizingName
 
 ZoneHVAC:ExhaustControl,
-       \memo Defines a controlled exhaust flow from a zone which finally feeds into 
+       \memo Defines a controlled exhaust flow from a zone which finally feeds into
        \memo one of AirLoopHVAC:ZoneMixer's inlets, which are part of an AirLoopHVAC:ExhaustSystem.
   A1 , \field Name
        \required-field
   A2 , \field Availability Schedule Name
        \note Availability schedule name for this exhaust system. Schedule value > 0 means it is available.
-       \note If this field is blank, the exhaust system is always available. If the attached 
+       \note If this field is blank, the exhaust system is always available. If the attached
        \note AirLoopHVAC:ExhaustSystem is off, then the flow will be zero.
        \type object-list
        \object-list ScheduleNames
@@ -31998,7 +32055,7 @@ Coil:Heating:DX:SingleSpeed,
        \note should be between 0.00004027 m3/s and .00006041 m3/s per watt of rated heating capacity
   N4 , \field Rated Supply Fan Power Per Volume Flow Rate 2017
        \note Enter the supply fan power per air volume flow rate at the rated test conditions.
-       \note as defined in the 2017 version of ANSI/AHRI Standard 210/240. 
+       \note as defined in the 2017 version of ANSI/AHRI Standard 210/240.
        \note The test conditions vary external static pressure based on heating capacity.
        \note This value is only used to calculate Heating Seasonal Performance Factor(HSPF).
        \note This value is not used for modeling the supply (condenser) fan during simulations.
@@ -32009,7 +32066,7 @@ Coil:Heating:DX:SingleSpeed,
        \default 773.3
   N5 , \field Rated Supply Fan Power Per Volume Flow Rate 2023
        \note Enter the supply fan power per air volume flow rate at the rated test conditions.
-       \note as defined in the 2017 version of ANSI/AHRI Standard 210/240. 
+       \note as defined in the 2017 version of ANSI/AHRI Standard 210/240.
        \note The test conditions vary external static pressure based on heating capacity.
        \note This value is only used to calculate Heating Seasonal Performance Factor(HSPF).
        \note This value is not used for modeling the supply (condenser) fan during simulations.
@@ -33237,7 +33294,7 @@ Coil:Heating:WaterToAirHeatPump:EquationFit,
    N7,  \field Ratio of Rated Heating Capacity to Rated Cooling Capacity
         \note Ratio of rated heating capacity to rated cooling capacity. This
         \note input is used to calculate the heating or cooling capacity when autosizing.
-        \note This input is only used if a companion cooling coil of the same type 
+        \note This input is only used if a companion cooling coil of the same type
         \note (Coil:Cooling:WaterToAirHeatPump:EquationFit) is used. This input is only
         \note used when a sizing run for the system which uses this object is requested
         \note and when the coil capacity is autosized.
diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd
index 3c6eba5cda9..313054175e8 100644
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -6842,10 +6842,26 @@ OS:People,
        \note optional (only required for thermal comfort runs)
        \type object-list
        \object-list ScheduleNames
-  N1; \field Multiplier
+  N1,  \field Multiplier
        \type real
        \minimum 0.0
        \default 1.0
+  A11, \field Ankle Level Air Velocity Schedule Name
+       \type object-list
+       \object-list ScheduleNames
+       \note units in the schedule are m/s
+       \note this is the schedule of the air speed at the 0.1 m above the floor
+       \note optional (only required for runs of thermal comfort models AnkleDraftASH55)
+  N2,  \field Cold Stress Temperature Threshold
+       \type real
+       \units C
+       \note this is the indoor safe temperature threshold for cold stress
+       \default 15.56
+  N3;  \field Heat Stress Temperature Threshold
+       \type real
+       \units C
+       \note this is the indoor safe temperature threshold for heat stress
+       \default 30
 
 OS:Lights,
        \min-fields 1
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslatePeople.cpp b/src/energyplus/ForwardTranslator/ForwardTranslatePeople.cpp
index e11cc2ff9f7..37bdfd81347 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslatePeople.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslatePeople.cpp
@@ -150,13 +150,35 @@ namespace energyplus {
       }
     }
 
-    for (int i = 0, n = definition.numThermalComfortModelTypes(); i < n; ++i) {
+    // As of 22.2.0, this is no longer possible to make this an extensible field
+    // because E+ added 3 regular fields at the end (eg: Ankle Level Velocity Schedule Name)
+    for (int i = 0, numComfortModelTypes = 0; i < definition.numThermalComfortModelTypes(); ++i) {
       OptionalString s = definition.getThermalComfortModelType(i);
       if (s) {
-        idfObject.pushExtensibleGroup(StringVector(1U, *s));
+        ++numComfortModelTypes;
+        if (numComfortModelTypes > 7) {
+          LOG(Warn, "For " << definition.briefDescription() << ", only 7 Thermal Confort Model Types are supported by EnergyPlus, number "
+                           << numComfortModelTypes << " [=" << *s << "] will be ignored.");
+        } else {
+          idfObject.setString(PeopleFields::ThermalComfortModel1Type + i, *s);
+        }
       }
     }
 
+    if (boost::optional<Schedule> schedule_ = modelObject.ankleLevelAirVelocitySchedule()) {
+      if (auto idf_schedule_ = translateAndMapModelObject(schedule_.get())) {
+        idfObject.setString(PeopleFields::AnkleLevelAirVelocityScheduleName, idf_schedule_->nameString());
+      }
+    }
+
+    if (!modelObject.isColdStressTemperatureThresholdDefaulted()) {
+      idfObject.setDouble(PeopleFields::ColdStressTemperatureThreshold, modelObject.coldStressTemperatureThreshold());
+    }
+
+    if (!modelObject.isHeatStressTemperatureThresholdDefaulted()) {
+      idfObject.setDouble(PeopleFields::HeatStressTemperatureThreshold, modelObject.heatStressTemperatureThreshold());
+    }
+
     return idfObject;
   }
 
diff --git a/src/energyplus/ReverseTranslator/ReverseTranslatePeople.cpp b/src/energyplus/ReverseTranslator/ReverseTranslatePeople.cpp
index 00f18efc5c8..24a0f9dabc2 100644
--- a/src/energyplus/ReverseTranslator/ReverseTranslatePeople.cpp
+++ b/src/energyplus/ReverseTranslator/ReverseTranslatePeople.cpp
@@ -131,12 +131,12 @@ namespace energyplus {
       LOG(Error, "SurfaceName_AngleFactorListName not currently imported");
     }
 
-    for (unsigned i = 0, n = workspaceObject.numExtensibleGroups(); i < n; ++i) {
-      IdfExtensibleGroup eg = workspaceObject.getExtensibleGroup(i);
-      OS_ASSERT(!eg.empty());
-      s = eg.getString(openstudio::PeopleExtensibleFields::ThermalComfortModelType);
+    // As of 22.2.0, this is no longer possible to make this an extensible field
+    // because E+ added 3 regular fields at the end (eg: Ankle Level Velocity Schedule Name)
+    for (unsigned i = PeopleFields::ThermalComfortModel1Type, k = 0; i <= PeopleFields::ThermalComfortModel7Type; ++i) {
+      s = workspaceObject.getString(i, false, true);
       if (s) {
-        definition.setThermalComfortModelType(i, *s);
+        definition.setThermalComfortModelType(k++, *s);
       }
     }
 
@@ -235,6 +235,31 @@ namespace energyplus {
       }
     }
 
+    target = workspaceObject.getTarget(openstudio::PeopleFields::AnkleLevelAirVelocityScheduleName);
+    if (target) {
+      OptionalModelObject modelObject = translateAndMapWorkspaceObject(*target);
+      if (modelObject) {
+        if (OptionalSchedule intermediate = modelObject->optionalCast<Schedule>()) {
+          Schedule schedule(*intermediate);
+          bool ok = people.setAnkleLevelAirVelocitySchedule(schedule);
+          if (!ok) {
+            LOG(Error, "Unable to set " << people.briefDescription() << "'s Ankle Level Air Velocity schedule to " << schedule.briefDescription()
+                                        << ", likely because of a ScheduleTypeLimits conflict.");
+          }
+        }
+      }
+    }
+
+    d = workspaceObject.getDouble(openstudio::PeopleFields::ColdStressTemperatureThreshold);
+    if (d) {
+      people.setColdStressTemperatureThreshold(*d);
+    }
+
+    d = workspaceObject.getDouble(openstudio::PeopleFields::HeatStressTemperatureThreshold);
+    if (d) {
+      people.setHeatStressTemperatureThreshold(*d);
+    }
+
     return people;
   }
 
diff --git a/src/energyplus/Test/People_GTest.cpp b/src/energyplus/Test/People_GTest.cpp
index e8c2fc9312a..cf56a5b1d17 100644
--- a/src/energyplus/Test/People_GTest.cpp
+++ b/src/energyplus/Test/People_GTest.cpp
@@ -40,6 +40,7 @@
 #include "../../model/People_Impl.hpp"
 #include "../../model/PeopleDefinition.hpp"
 #include "../../model/PeopleDefinition_Impl.hpp"
+#include "../../model/ScheduleConstant.hpp"
 
 #include <utilities/idd/People_FieldEnums.hxx>
 #include <utilities/idd/IddEnums.hxx>
@@ -53,21 +54,201 @@ using namespace openstudio::model;
 using namespace openstudio;
 
 TEST_F(EnergyPlusFixture, ForwardTranslator_People) {
-  Model model;
+  Model m;
 
-  ThermalZone zone(model);
+  ThermalZone zone(m);
 
-  Space space(model);
+  Space space(m);
   space.setThermalZone(zone);
 
-  PeopleDefinition def(model);
+  PeopleDefinition pd(m);
+  EXPECT_TRUE(pd.setSpaceFloorAreaperPerson(10.0));
+  EXPECT_TRUE(pd.setThermalComfortModelType(0, "Fanger"));
+  EXPECT_TRUE(pd.setThermalComfortModelType(1, "Pierce"));
+  EXPECT_TRUE(pd.setFractionRadiant(0.35));
+  EXPECT_TRUE(pd.setSensibleHeatFraction(0.2));
+  EXPECT_TRUE(pd.setCarbonDioxideGenerationRate(3.6e-8));
+  EXPECT_TRUE(pd.setEnableASHRAE55ComfortWarnings(true));
+  EXPECT_TRUE(pd.setMeanRadiantTemperatureCalculationType("SurfaceWeighted"));
 
-  People people(def);
-  people.setSpace(space);
+  People p(pd);
+  EXPECT_TRUE(p.setSpace(space));
+
+  {
+    ScheduleConstant numPeopleSch(m);
+    numPeopleSch.setName("NumberofPeopleSchedule");
+    numPeopleSch.setValue(1);
+    EXPECT_TRUE(p.setNumberofPeopleSchedule(numPeopleSch));
+  }
+  {
+    ScheduleConstant activitySch(m);
+    activitySch.setName("ActivitySchedule");
+    activitySch.setValue(131.8);
+    EXPECT_TRUE(p.setActivityLevelSchedule(activitySch));
+  }
+  {
+
+    ScheduleConstant workEffSch(m);
+    workEffSch.setName("WorkEfficiencySchedule");
+    workEffSch.setValue(0.0);
+    EXPECT_TRUE(p.setWorkEfficiencySchedule(workEffSch));
+  }
+  {
+    ScheduleConstant cloSch(m);
+    cloSch.setName("ClothingInsulationSchedule");
+    cloSch.setValue(1.0);
+    EXPECT_TRUE(p.setClothingInsulationSchedule(cloSch));
+  }
+
+  {
+    ScheduleConstant airSch(m);
+    airSch.setName("AirVelocitySchedule");
+    airSch.setValue(0.137);
+    EXPECT_TRUE(p.setAirVelocitySchedule(airSch));
+  }
+
+  EXPECT_TRUE(p.setMultiplier(2));
+  {
+    ScheduleConstant ankleSch(m);
+    ankleSch.setName("AnkleLevelAirVelocitySchedule");
+    ankleSch.setValue(0.127);
+    EXPECT_TRUE(p.setAnkleLevelAirVelocitySchedule(ankleSch));
+  }
+
+  EXPECT_TRUE(p.setColdStressTemperatureThreshold(15.0));
+  EXPECT_TRUE(p.setHeatStressTemperatureThreshold(31.0));
 
   ForwardTranslator ft;
-  Workspace workspace = ft.translateModel(model);
+  Workspace workspace = ft.translateModel(m);
 
   std::vector<WorkspaceObject> peopleObjects = workspace.getObjectsByType(IddObjectType::People);
   ASSERT_EQ(1u, peopleObjects.size());
+
+  auto& peopleObject = peopleObjects.front();
+
+  EXPECT_EQ("Space 1", peopleObject.getString(PeopleFields::ZoneorZoneListorSpaceorSpaceListName).get());
+  EXPECT_EQ("NumberofPeopleSchedule", peopleObject.getString(PeopleFields::NumberofPeopleScheduleName).get());
+  EXPECT_EQ("Area/Person", peopleObject.getString(PeopleFields::NumberofPeopleCalculationMethod).get());
+  EXPECT_TRUE(peopleObject.isEmpty(PeopleFields::NumberofPeople));
+  EXPECT_TRUE(peopleObject.isEmpty(PeopleFields::PeopleperFloorArea));
+  EXPECT_EQ(20.0, peopleObject.getDouble(PeopleFields::FloorAreaperPerson).get());
+  EXPECT_EQ(0.35, peopleObject.getDouble(PeopleFields::FractionRadiant).get());
+  EXPECT_EQ(0.2, peopleObject.getDouble(PeopleFields::SensibleHeatFraction).get());
+  EXPECT_EQ("ActivitySchedule", peopleObject.getString(PeopleFields::ActivityLevelScheduleName).get());
+  EXPECT_EQ(3.6e-08, peopleObject.getDouble(PeopleFields::CarbonDioxideGenerationRate).get());
+  EXPECT_EQ("Yes", peopleObject.getString(PeopleFields::EnableASHRAE55ComfortWarnings).get());
+  EXPECT_EQ("SurfaceWeighted", peopleObject.getString(PeopleFields::MeanRadiantTemperatureCalculationType).get());
+  EXPECT_TRUE(peopleObject.isEmpty(PeopleFields::SurfaceName_AngleFactorListName));
+  EXPECT_EQ("WorkEfficiencySchedule", peopleObject.getString(PeopleFields::WorkEfficiencyScheduleName).get());
+
+  EXPECT_TRUE(peopleObject.isEmpty(PeopleFields::ClothingInsulationCalculationMethod));
+  EXPECT_EQ("ClothingInsulationSchedule", peopleObject.getString(PeopleFields::ClothingInsulationCalculationMethod, true).get());
+  EXPECT_TRUE(peopleObject.isEmpty(PeopleFields::ClothingInsulationCalculationMethodScheduleName));
+
+  EXPECT_EQ("ClothingInsulationSchedule", peopleObject.getString(PeopleFields::ClothingInsulationScheduleName).get());
+  EXPECT_EQ("AirVelocitySchedule", peopleObject.getString(PeopleFields::AirVelocityScheduleName).get());
+  EXPECT_EQ("Fanger", peopleObject.getString(PeopleFields::ThermalComfortModel1Type).get());
+  EXPECT_EQ("Pierce", peopleObject.getString(PeopleFields::ThermalComfortModel2Type).get());
+
+  EXPECT_EQ("AnkleLevelAirVelocitySchedule", peopleObject.getString(PeopleFields::AnkleLevelAirVelocityScheduleName).get());
+  EXPECT_EQ(15.0, peopleObject.getDouble(PeopleFields::ColdStressTemperatureThreshold).get());
+  EXPECT_EQ(31.0, peopleObject.getDouble(PeopleFields::HeatStressTemperatureThreshold).get());
+}
+
+TEST_F(EnergyPlusFixture, ReverseTranslator_People) {
+
+  ReverseTranslator rt;
+
+  Workspace w(StrictnessLevel::Minimal, IddFileType::EnergyPlus);
+
+  OptionalWorkspaceObject _i_zone = w.addObject(IdfObject(IddObjectType::Zone));
+  ASSERT_TRUE(_i_zone);
+  _i_zone->setName("Space1");
+
+  OptionalWorkspaceObject _i_people = w.addObject(IdfObject(IddObjectType::People));
+  ASSERT_TRUE(_i_people);
+
+  EXPECT_TRUE(_i_people->setPointer(PeopleFields::ZoneorZoneListorSpaceorSpaceListName, _i_zone->handle()));
+
+  auto assignSchedule = [&_i_people, &w](unsigned index, const std::string& scheduleName) {
+    OptionalWorkspaceObject _i_sch = w.addObject(IdfObject(IddObjectType::Schedule_Constant));
+    ASSERT_TRUE(_i_sch);
+
+    _i_sch->setName(scheduleName);
+    EXPECT_TRUE(_i_people->setPointer(index, _i_sch->handle()));
+  };
+
+  assignSchedule(PeopleFields::NumberofPeopleScheduleName, "NumberofPeopleSchedule");
+  EXPECT_TRUE(_i_people->setString(PeopleFields::NumberofPeopleCalculationMethod, "Area/Person"));
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::NumberofPeople, 0.0));
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::PeopleperFloorArea, 0.0));
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::FloorAreaperPerson, 10.0));
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::FractionRadiant, 0.35));
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::SensibleHeatFraction, 0.2));
+
+  assignSchedule(PeopleFields::ActivityLevelScheduleName, "ActivitySchedule");
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::CarbonDioxideGenerationRate, 3.6e-08));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::EnableASHRAE55ComfortWarnings, "Yes"));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::MeanRadiantTemperatureCalculationType, "SurfaceWeighted"));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::SurfaceName_AngleFactorListName, ""));
+  assignSchedule(PeopleFields::WorkEfficiencyScheduleName, "WorkEfficiencySchedule");
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ClothingInsulationCalculationMethod, ""));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ClothingInsulationCalculationMethodScheduleName, ""));
+  assignSchedule(PeopleFields::ClothingInsulationScheduleName, "ClothingInsulationSchedule");
+  assignSchedule(PeopleFields::AirVelocityScheduleName, "AirVelocitySchedule");
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ThermalComfortModel1Type, "Fanger"));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ThermalComfortModel2Type, "Pierce"));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ThermalComfortModel3Type, ""));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ThermalComfortModel4Type, ""));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ThermalComfortModel5Type, ""));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ThermalComfortModel6Type, ""));
+  EXPECT_TRUE(_i_people->setString(PeopleFields::ThermalComfortModel7Type, ""));
+  assignSchedule(PeopleFields::AnkleLevelAirVelocityScheduleName, "AnkleLevelAirVelocitySchedule");
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::ColdStressTemperatureThreshold, 15.0));
+  EXPECT_TRUE(_i_people->setDouble(PeopleFields::HeatStressTemperatureThreshold, 31.0));
+
+  Model m = rt.translateWorkspace(w);
+
+  ASSERT_EQ(1, m.getConcreteModelObjects<People>().size());
+  ASSERT_EQ(1, m.getConcreteModelObjects<PeopleDefinition>().size());
+
+  auto p = m.getConcreteModelObjects<People>()[0];
+
+  EXPECT_EQ("People 1", p.nameString());
+  ASSERT_TRUE(p.space());
+  ASSERT_TRUE(p.numberofPeopleSchedule());
+  EXPECT_EQ("NumberofPeopleSchedule", p.numberofPeopleSchedule()->nameString());
+  ASSERT_TRUE(p.activityLevelSchedule());
+  EXPECT_EQ("ActivitySchedule", p.activityLevelSchedule()->nameString());
+  ASSERT_TRUE(p.workEfficiencySchedule());
+  EXPECT_EQ("WorkEfficiencySchedule", p.workEfficiencySchedule()->nameString());
+  ASSERT_TRUE(p.clothingInsulationSchedule());
+  EXPECT_EQ("ClothingInsulationSchedule", p.clothingInsulationSchedule()->nameString());
+  ASSERT_TRUE(p.airVelocitySchedule());
+  EXPECT_EQ("AirVelocitySchedule", p.airVelocitySchedule()->nameString());
+
+  // E+ object doesn't have one
+  EXPECT_EQ(1.0, p.multiplier());
+  ASSERT_TRUE(p.ankleLevelAirVelocitySchedule());
+  EXPECT_EQ("AnkleLevelAirVelocitySchedule", p.ankleLevelAirVelocitySchedule()->nameString());
+  EXPECT_EQ(15.0, p.coldStressTemperatureThreshold());
+  EXPECT_EQ(31.0, p.heatStressTemperatureThreshold());
+
+  auto pd = p.definition().cast<PeopleDefinition>();
+  EXPECT_EQ("Area/Person", pd.numberofPeopleCalculationMethod());
+  EXPECT_FALSE(pd.numberofPeople());
+  EXPECT_FALSE(pd.peopleperSpaceFloorArea());
+  ASSERT_TRUE(pd.spaceFloorAreaperPerson());
+  EXPECT_EQ(10.0, pd.spaceFloorAreaperPerson().get());
+  EXPECT_EQ(0.35, pd.fractionRadiant());
+  ASSERT_TRUE(pd.sensibleHeatFraction());
+  EXPECT_EQ(0.2, pd.sensibleHeatFraction().get());
+  EXPECT_EQ(3.6e-08, pd.carbonDioxideGenerationRate());
+  EXPECT_TRUE(pd.enableASHRAE55ComfortWarnings());
+  EXPECT_EQ("SurfaceWeighted", pd.meanRadiantTemperatureCalculationType());
+
+  ASSERT_EQ(2, pd.numThermalComfortModelTypes());
+  ASSERT_EQ(2, pd.numExtensibleGroups());
+  EXPECT_EQ("Fanger", pd.getThermalComfortModelType(0).get());
+  EXPECT_EQ("Pierce", pd.getThermalComfortModelType(1).get());
 }
diff --git a/src/model/People.cpp b/src/model/People.cpp
index 30a3db842ba..3042074a40f 100644
--- a/src/model/People.cpp
+++ b/src/model/People.cpp
@@ -116,21 +116,25 @@ namespace model {
     std::vector<ScheduleTypeKey> People_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_PeopleFields::NumberofPeopleScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("People", "Number of People"));
+        result.emplace_back("People", "Number of People");
       }
       if (std::find(b, e, OS_PeopleFields::ActivityLevelScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("People", "Activity Level"));
+        result.emplace_back("People", "Activity Level");
       }
       if (std::find(b, e, OS_PeopleFields::WorkEfficiencyScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("People", "Work Efficiency"));
+        result.emplace_back("People", "Work Efficiency");
       }
       if (std::find(b, e, OS_PeopleFields::ClothingInsulationScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("People", "Clothing Insulation"));
+        result.emplace_back("People", "Clothing Insulation");
       }
       if (std::find(b, e, OS_PeopleFields::AirVelocityScheduleName) != e) {
-        result.push_back(ScheduleTypeKey("People", "Air Velocity"));
+        result.emplace_back("People", "Air Velocity");
+      }
+      if (std::find(b, e, OS_PeopleFields::AnkleLevelAirVelocityScheduleName) != e) {
+        result.emplace_back("People", "Ankle Level Air Velocity");
       }
       return result;
     }
@@ -187,23 +191,30 @@ namespace model {
         result = false;
       }
 
-      // optional in EnergyPlus
-      schedule = this->workEfficiencySchedule();
-      if (schedule) {
-        this->setWorkEfficiencySchedule(*schedule);
-      }
+      // None of these are part of the DefaultScheduleSet
+      // // optional in EnergyPlus
+      // schedule = this->workEfficiencySchedule();
+      // if (schedule) {
+      //   this->setWorkEfficiencySchedule(*schedule);
+      // }
 
-      // optional in EnergyPlus
-      schedule = this->clothingInsulationSchedule();
-      if (schedule) {
-        this->setClothingInsulationSchedule(*schedule);
-      }
+      // // optional in EnergyPlus
+      // schedule = this->clothingInsulationSchedule();
+      // if (schedule) {
+      //   this->setClothingInsulationSchedule(*schedule);
+      // }
 
-      // optional in EnergyPlus
-      schedule = this->airVelocitySchedule();
-      if (schedule) {
-        this->setAirVelocitySchedule(*schedule);
-      }
+      // // optional in EnergyPlus
+      // schedule = this->airVelocitySchedule();
+      // if (schedule) {
+      //   this->setAirVelocitySchedule(*schedule);
+      // }
+
+      // // optional in EnergyPlus
+      // schedule = this->ankleLevelAirVelocitySchedule();
+      // if (schedule) {
+      //   this->setAnkleLevelAirVelocitySchedule(*schedule);
+      // }
 
       return result;
     }
@@ -540,6 +551,83 @@ namespace model {
       return types;
     }
 
+    boost::optional<Schedule> People_Impl::ankleLevelAirVelocitySchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_PeopleFields::AnkleLevelAirVelocityScheduleName);
+    }
+
+    bool People_Impl::setAnkleLevelAirVelocitySchedule(Schedule& schedule) {
+      return setSchedule(OS_PeopleFields::AnkleLevelAirVelocityScheduleName, "People", "Air Velocity", schedule);
+    }
+
+    void People_Impl::resetAnkleLevelAirVelocitySchedule() {
+      bool result = setString(OS_PeopleFields::AnkleLevelAirVelocityScheduleName, "");
+      OS_ASSERT(result);
+    }
+
+    boost::optional<ModelObject> People_Impl::ankleLevelAirVelocityScheduleAsModelObject() const {
+      OptionalModelObject result;
+      OptionalSchedule intermediate = ankleLevelAirVelocitySchedule();
+      if (intermediate) {
+        result = *intermediate;
+      }
+      return result;
+    }
+
+    bool People_Impl::setAnkleLevelAirVelocityScheduleAsModelObject(const boost::optional<ModelObject>& modelObject) {
+      if (modelObject) {
+        OptionalSchedule intermediate = modelObject->optionalCast<Schedule>();
+        if (intermediate) {
+          Schedule schedule(*intermediate);
+          return setAnkleLevelAirVelocitySchedule(schedule);
+        } else {
+          return false;
+        }
+      } else {
+        resetAnkleLevelAirVelocitySchedule();
+      }
+      return true;
+    }
+
+    double People_Impl::coldStressTemperatureThreshold() const {
+      boost::optional<double> value = getDouble(OS_PeopleFields::ColdStressTemperatureThreshold, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    bool People_Impl::isColdStressTemperatureThresholdDefaulted() const {
+      return isEmpty(OS_PeopleFields::ColdStressTemperatureThreshold);
+    }
+
+    bool People_Impl::setColdStressTemperatureThreshold(double coldStressTemperatureThreshold) {
+      bool result = setDouble(OS_PeopleFields::ColdStressTemperatureThreshold, coldStressTemperatureThreshold);
+      return result;
+    }
+
+    void People_Impl::resetColdStressTemperatureThreshold() {
+      bool result = setString(OS_PeopleFields::ColdStressTemperatureThreshold, "");
+      OS_ASSERT(result);
+    }
+
+    double People_Impl::heatStressTemperatureThreshold() const {
+      boost::optional<double> value = getDouble(OS_PeopleFields::HeatStressTemperatureThreshold, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    bool People_Impl::isHeatStressTemperatureThresholdDefaulted() const {
+      return isEmpty(OS_PeopleFields::HeatStressTemperatureThreshold);
+    }
+
+    bool People_Impl::setHeatStressTemperatureThreshold(double heatStressTemperatureThreshold) {
+      bool result = setDouble(OS_PeopleFields::HeatStressTemperatureThreshold, heatStressTemperatureThreshold);
+      return result;
+    }
+
+    void People_Impl::resetHeatStressTemperatureThreshold() {
+      bool result = setString(OS_PeopleFields::HeatStressTemperatureThreshold, "");
+      OS_ASSERT(result);
+    }
+
   }  // namespace detail
 
   People::People(const PeopleDefinition& peopleDefinition) : SpaceLoadInstance(People::iddObjectType(), peopleDefinition) {
@@ -662,6 +750,50 @@ namespace model {
     return getImpl<detail::People_Impl>()->getFloorAreaPerPerson(floorArea);
   }
 
+  boost::optional<Schedule> People::ankleLevelAirVelocitySchedule() const {
+    return getImpl<detail::People_Impl>()->ankleLevelAirVelocitySchedule();
+  }
+
+  bool People::setAnkleLevelAirVelocitySchedule(Schedule& schedule) {
+    return getImpl<detail::People_Impl>()->setAnkleLevelAirVelocitySchedule(schedule);
+  }
+
+  void People::resetAnkleLevelAirVelocitySchedule() {
+    getImpl<detail::People_Impl>()->resetAnkleLevelAirVelocitySchedule();
+  }
+
+  double People::coldStressTemperatureThreshold() const {
+    return getImpl<detail::People_Impl>()->coldStressTemperatureThreshold();
+  }
+
+  bool People::isColdStressTemperatureThresholdDefaulted() const {
+    return getImpl<detail::People_Impl>()->isColdStressTemperatureThresholdDefaulted();
+  }
+
+  bool People::setColdStressTemperatureThreshold(double coldStressTemperatureThreshold) {
+    return getImpl<detail::People_Impl>()->setColdStressTemperatureThreshold(coldStressTemperatureThreshold);
+  }
+
+  void People::resetColdStressTemperatureThreshold() {
+    getImpl<detail::People_Impl>()->resetColdStressTemperatureThreshold();
+  }
+
+  double People::heatStressTemperatureThreshold() const {
+    return getImpl<detail::People_Impl>()->heatStressTemperatureThreshold();
+  }
+
+  bool People::isHeatStressTemperatureThresholdDefaulted() const {
+    return getImpl<detail::People_Impl>()->isHeatStressTemperatureThresholdDefaulted();
+  }
+
+  bool People::setHeatStressTemperatureThreshold(double heatStressTemperatureThreshold) {
+    return getImpl<detail::People_Impl>()->setHeatStressTemperatureThreshold(heatStressTemperatureThreshold);
+  }
+
+  void People::resetHeatStressTemperatureThreshold() {
+    getImpl<detail::People_Impl>()->resetHeatStressTemperatureThreshold();
+  }
+
   /// @cond
   People::People(std::shared_ptr<detail::People_Impl> impl) : SpaceLoadInstance(std::move(impl)) {}
   /// @endcond
diff --git a/src/model/People.hpp b/src/model/People.hpp
index b023104f750..49e3ed8129f 100644
--- a/src/model/People.hpp
+++ b/src/model/People.hpp
@@ -91,6 +91,15 @@ namespace model {
     /** Returns the air velocity schedule. */
     boost::optional<Schedule> airVelocitySchedule() const;
 
+    /** Returns the ankle-level air velocity schedule. */
+    boost::optional<Schedule> ankleLevelAirVelocitySchedule() const;
+
+    double coldStressTemperatureThreshold() const;
+    bool isColdStressTemperatureThresholdDefaulted() const;
+
+    double heatStressTemperatureThreshold() const;
+    bool isHeatStressTemperatureThresholdDefaulted() const;
+
     //@}
     /** @name Setters */
     //@{
@@ -142,6 +151,15 @@ namespace model {
 
     void resetMultiplier();
 
+    bool setAnkleLevelAirVelocitySchedule(Schedule& schedule);
+    void resetAnkleLevelAirVelocitySchedule();
+
+    bool setColdStressTemperatureThreshold(double coldStressTemperatureThreshold);
+    void resetColdStressTemperatureThreshold();
+
+    bool setHeatStressTemperatureThreshold(double heatStressTemperatureThreshold);
+    void resetHeatStressTemperatureThreshold();
+
     //@}
     /** @name Other */
     //@{
@@ -170,7 +188,7 @@ namespace model {
     //@}
    protected:
     /// @cond
-    typedef detail::People_Impl ImplType;
+    using ImplType = detail::People_Impl;
 
     friend class Model;
     friend class openstudio::IdfObject;
@@ -183,10 +201,10 @@ namespace model {
   };
 
   /** \relates People*/
-  typedef boost::optional<People> OptionalPeople;
+  using OptionalPeople = boost::optional<People>;
 
   /** \relates People*/
-  typedef std::vector<People> PeopleVector;
+  using PeopleVector = std::vector<People>;
 
 }  // namespace model
 }  // namespace openstudio
diff --git a/src/model/People_Impl.hpp b/src/model/People_Impl.hpp
index 4b5af81d1e0..41a1e07e5e3 100644
--- a/src/model/People_Impl.hpp
+++ b/src/model/People_Impl.hpp
@@ -109,6 +109,14 @@ namespace model {
       /** Returns the air velocity schedule. */
       boost::optional<Schedule> airVelocitySchedule() const;
 
+      boost::optional<Schedule> ankleLevelAirVelocitySchedule() const;
+
+      double coldStressTemperatureThreshold() const;
+      bool isColdStressTemperatureThresholdDefaulted() const;
+
+      double heatStressTemperatureThreshold() const;
+      bool isHeatStressTemperatureThresholdDefaulted() const;
+
       //@}
       /** @name Setters */
       //@{
@@ -155,6 +163,15 @@ namespace model {
 
       void resetMultiplier();
 
+      bool setAnkleLevelAirVelocitySchedule(Schedule& schedule);
+      void resetAnkleLevelAirVelocitySchedule();
+
+      bool setColdStressTemperatureThreshold(double coldStressTemperatureThreshold);
+      void resetColdStressTemperatureThreshold();
+
+      bool setHeatStressTemperatureThreshold(double heatStressTemperatureThreshold);
+      void resetHeatStressTemperatureThreshold();
+
       //@}
       /** @name Other */
       //@{
@@ -195,6 +212,7 @@ namespace model {
       boost::optional<ModelObject> workEfficiencyScheduleAsModelObject() const;
       boost::optional<ModelObject> clothingInsulationScheduleAsModelObject() const;
       boost::optional<ModelObject> airVelocityScheduleAsModelObject() const;
+      boost::optional<ModelObject> ankleLevelAirVelocityScheduleAsModelObject() const;
 
       bool setPeopleDefinitionAsModelObject(const boost::optional<ModelObject>& modelObject);
       bool setNumberofPeopleScheduleAsModelObject(const boost::optional<ModelObject>& modelObject);
@@ -202,6 +220,7 @@ namespace model {
       bool setWorkEfficiencyScheduleAsModelObject(const boost::optional<ModelObject>& modelObject);
       bool setClothingInsulationScheduleAsModelObject(const boost::optional<ModelObject>& modelObject);
       bool setAirVelocityScheduleAsModelObject(const boost::optional<ModelObject>& modelObject);
+      bool setAnkleLevelAirVelocityScheduleAsModelObject(const boost::optional<ModelObject>& modelObject);
     };
 
   }  // namespace detail
diff --git a/src/model/ScheduleTypeRegistry.cpp b/src/model/ScheduleTypeRegistry.cpp
index 062633e04fe..1859c0843d2 100644
--- a/src/model/ScheduleTypeRegistry.cpp
+++ b/src/model/ScheduleTypeRegistry.cpp
@@ -327,6 +327,7 @@ namespace model {
       {"People", "Work Efficiency", "workEfficiencySchedule", true, "", 0.0, 1.0},
       {"People", "Clothing Insulation", "clothingInsulationSchedule", true, "ClothingInsulation", 0.0, OptionalDouble()},
       {"People", "Air Velocity", "airVelocitySchedule", true, "Velocity", 0.0, OptionalDouble()},
+      {"People", "Ankle Level Air Velocity", "ankleLevelAirVelocitySchedule", true, "Velocity", 0.0, OptionalDouble()},
       {"PhotovoltaicPerformanceSimple", "Efficiency", "efficiencySchedule", true, "", 0.0, 1.0},
       {"PlantComponentTemperatureSource", "Source Temperature", "sourceTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},
       {"PipeIndoor", "Ambient Temperature", "ambientTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()},


Test patch:
diff --git a/src/epjson/test/epJSONFixture.cpp b/src/epjson/test/epJSONFixture.cpp
index 548284a6e7f..0d794c2eba1 100644
--- a/src/epjson/test/epJSONFixture.cpp
+++ b/src/epjson/test/epJSONFixture.cpp
@@ -29,17 +29,21 @@
 
 #include "epJSONFixture.hpp"
 
-#include "../../model/Model.hpp"
-#include "../../model/Component.hpp"
-#include "../../model/Construction.hpp"
-#include "../../model/Construction_Impl.hpp"
+#include "../epJSONTranslator.hpp"
 
-#include <utilities/idd/IddFactory.hxx>
+#include "../../utilities/core/ApplicationPathHelpers.hpp"
 #include "../../utilities/core/Compare.hpp"
+#include "../../utilities/core/Filesystem.hpp"
+#include "../../utilities/core/PathHelpers.hpp"
+#include "../../utilities/idf/IdfFile.hpp"
 
 #include <resources.hxx>
 #include <OpenStudio.hxx>
 
+#include <fmt/format.h>
+#include <json/json.h>
+#include <cstdlib>
+
 using namespace openstudio;
 
 void epJSONFixture::SetUp() {}
@@ -57,5 +61,158 @@ void epJSONFixture::TearDownTestSuite() {
   logFile->disable();
 }
 
+void epJSONFixture::makeDoubles(Json::Value& value) {
+  if (value.isNumeric()) {
+    value = value.asDouble();
+  } else {
+    for (auto& child : value) {
+      makeDoubles(child);
+    }
+  }
+}
+
+void epJSONFixture::compareJSONS(const Json::Value& lhs, const Json::Value& rhs) {
+  auto doubledLhs = lhs;
+  makeDoubles(doubledLhs);
+
+  auto doubledRhs = rhs;
+  makeDoubles(doubledRhs);
+
+  if (doubledLhs != doubledRhs) {
+    compareJSONS_detailed(doubledLhs, doubledRhs, "root");
+    constexpr bool debug = false;
+    if constexpr (debug) {
+      {
+        std::ofstream file_id;
+        file_id.open("lhs.json");
+        Json::StreamWriterBuilder builder;
+        builder["commentStyle"] = "None";
+        builder["indentation"] = "   ";
+        std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
+        std::ofstream outputFileStream("/tmp/lhs.json");
+        writer->write(doubledLhs, &outputFileStream);
+      }
+      {
+        std::ofstream file_id;
+        file_id.open("lhs.json");
+        Json::StreamWriterBuilder builder;
+        builder["commentStyle"] = "None";
+        builder["indentation"] = "   ";
+        std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
+        std::ofstream outputFileStream("/tmp/rhs.json");
+        writer->write(doubledRhs, &outputFileStream);
+      }
+    }
+  }
+  ASSERT_TRUE(doubledLhs == doubledRhs);
+}
+
+void epJSONFixture::compareJSONS_detailed(Json::Value& lhs, Json::Value& rhs, const std::string& currentPath) {
+  if (lhs.isNumeric()) {
+    ASSERT_TRUE(rhs.isNumeric()) << "At " << currentPath << ", lhs is a numeric while rhs is not";
+    ASSERT_EQ(lhs.asDouble(), rhs.asDouble()) << "At " << currentPath;
+  } else if (lhs.isString()) {
+    ASSERT_EQ(lhs, rhs) << "At " << currentPath;
+  } else if (lhs.isArray()) {
+    ASSERT_TRUE(rhs.isArray()) << "At " << currentPath << ", lhs is an arrary while rhs is not";
+    ASSERT_EQ(lhs.size(), rhs.size()) << "At " << currentPath << ", size of array object differs";
+    if (lhs != rhs) {
+      for (int i = 0; i < static_cast<int>(lhs.size()); ++i) {
+        compareJSONS_detailed(lhs[i], rhs[i], currentPath + "[" + std::to_string(i) + "]");
+      }
+      ASSERT_TRUE(false) << "Terminating due to array mismatch.";
+    }
+  } else {
+    ASSERT_TRUE(lhs.isObject()) << "At " << currentPath << ", lhs is not an object? did I miss a type? [developer error]";
+    ASSERT_TRUE(rhs.isObject()) << "At " << currentPath << ", lhs is an object but rhs is not.";
+    if (lhs != rhs) {
+      std::vector<std::string> lhsMemberNames = lhs.getMemberNames();
+      std::vector<std::string> rhsMemberNames = rhs.getMemberNames();
+      if (lhsMemberNames != rhsMemberNames) {
+        for (auto& s : lhsMemberNames) {
+          EXPECT_TRUE(rhs.isMember(s)) << "At " << currentPath << ", rhs is missing member named '" << s << "'";
+        }
+        for (auto& s : rhsMemberNames) {
+          EXPECT_TRUE(lhs.isMember(s)) << "At " << currentPath << ", lhs is missing member named '" << s << "'";
+        }
+        ASSERT_TRUE(false) << "At " << currentPath << ", Terminating due to member name mismatch.";
+      }
+
+      for (auto& s : lhsMemberNames) {
+        compareJSONS_detailed(lhs[s], rhs[s], currentPath + ">" + s);
+      }
+      ASSERT_TRUE(false) << "At " << currentPath << ", Terminating due to object mismatch.";
+    }
+  }
+}
+
+openstudio::path setupIdftoEPJSONTest(const openstudio::path& location) {
+  const auto basename = openstudio::toPath(openstudio::filesystem::basename(location));
+  const auto working_directory = openstudio::filesystem::complete(openstudio::toPath("epjson_tests") / basename);
+  auto idf_path = working_directory / openstudio::toPath("eplus.idf");
+  openstudio::filesystem::create_directories(working_directory);
+  openstudio::filesystem::copy_file(location, idf_path, openstudio::filesystem::copy_option::overwrite_if_exists);
+  return idf_path;
+}
+
+Json::Value translateIdfToEPJSONWithEP(const openstudio::path& location) {
+
+  // In case for some reason the energyplus CLI cannot do the conversion, we do not want to pick up an old artifact
+  const auto epJSONFile = openstudio::setFileExtension(location, "epJSON", true);
+  if (openstudio::filesystem::exists(epJSONFile)) {
+    openstudio::filesystem::remove(epJSONFile);
+  }
+
+  std::string cmd = fmt::format("{} --output-directory {} --convert-only {}", openstudio::toString(openstudio::getEnergyPlusExecutable().native()),
+                                openstudio::toString(location.parent_path().native()), openstudio::toString(location.native()));
+
+  [[maybe_unused]] auto result = std::system(cmd.c_str());
+  if (!openstudio::filesystem::exists(epJSONFile)) {
+    throw openstudio::Exception(
+      fmt::format("Error during the E+ CLI call, epJSON wasn't created at {}\ncmd = '{}'\n", openstudio::toString(epJSONFile), cmd));
+  }
+
+  auto root = openstudio::epJSON::loadJSON(epJSONFile);
+
+  const auto outputLocation = location.parent_path() / openstudio::toPath("eplus-rewritten.epJSON");
+  std::ofstream ofs(openstudio::toString(outputLocation), std::ofstream::trunc);
+  ofs << root.toStyledString() << '\n';
+
+  return root;
+}
+
+openstudio::path epJSONFixture::completeIDFPath(const openstudio::path& idf) {
+  return openstudio::getEnergyPlusDirectory() / openstudio::toPath("ExampleFiles") / idf;
+}
+
+Json::Value translateIdfToEPJSONWithOS(const openstudio::path& location) {
+  auto idf = openstudio::IdfFile::load(location);
+
+  if (!idf) {
+    return Json::Value{};
+  }
+
+  auto result = openstudio::epJSON::toJSON(*idf);
+
+  const auto outputLocation = location.parent_path() / openstudio::toPath("os.epJSON");
+  std::ofstream ofs(openstudio::toString(outputLocation), std::ofstream::trunc);
+  ofs << result.toStyledString() << '\n';
+  return result;
+}
+
+std::pair<Json::Value, Json::Value> epJSONFixture::doEPJSONTranslations(const std::string& idfname) {
+
+  const auto setupIdf = setupIdftoEPJSONTest(epJSONFixture::completeIDFPath(openstudio::toString(idfname)));
+  return {translateIdfToEPJSONWithEP(setupIdf), translateIdfToEPJSONWithOS(setupIdf)};
+}
+
+void epJSONFixture::compareEPJSONTranslations(const std::string& idfname) {
+  const auto [epTranslation, osTranslation] = epJSONFixture::doEPJSONTranslations("WCE_DoubleClear_BSDF.idf");
+  ASSERT_TRUE(epTranslation) << "E+ translation failed for " << idfname;
+  ASSERT_TRUE(osTranslation) << "OS translation failed for " << idfname;
+
+  compareJSONS(epTranslation, osTranslation);
+}
+
 // static variables
 boost::optional<openstudio::FileLogSink> epJSONFixture::logFile;
diff --git a/src/epjson/test/epJSONFixture.hpp b/src/epjson/test/epJSONFixture.hpp
index d297bbf69e5..d5e55944ee3 100644
--- a/src/epjson/test/epJSONFixture.hpp
+++ b/src/epjson/test/epJSONFixture.hpp
@@ -37,8 +37,17 @@
 
 #include <boost/optional.hpp>
 
+namespace Json {
+class Value;
+}
+
 class epJSONFixture : public ::testing::Test
 {
+ public:
+  void compareEPJSONTranslations(const std::string& idfname);
+  static openstudio::path completeIDFPath(const openstudio::path& idf);
+  static void makeDoubles(Json::Value& value);
+
  protected:
   /// initialize for each test
   virtual void SetUp() override;
@@ -56,6 +65,13 @@ class epJSONFixture : public ::testing::Test
 
   // static variables
   static boost::optional<openstudio::FileLogSink> logFile;
+
+ private:
+  void compareJSONS(const Json::Value& lhs, const Json::Value& rhs);
+
+  void compareJSONS_detailed(Json::Value& lhs, Json::Value& rhs, const std::string& currentPath);
+
+  static std::pair<Json::Value, Json::Value> doEPJSONTranslations(const std::string& idfname);
 };
 
 #endif  // EPJSON_TEST_EPJSONFIXTURE_HPP
diff --git a/src/epjson/test/epJSONTranslator_GTest.cpp b/src/epjson/test/epJSONTranslator_GTest.cpp
index 03c2c145df5..b780a633427 100644
--- a/src/epjson/test/epJSONTranslator_GTest.cpp
+++ b/src/epjson/test/epJSONTranslator_GTest.cpp
@@ -55,345 +55,129 @@
 #include <resources.hxx>
 #include <algorithm>
 
-openstudio::path setupIdftoEPJSONTest(const openstudio::path& location) {
-  const auto basename = openstudio::toPath(openstudio::filesystem::basename(location));
-  const auto working_directory = openstudio::filesystem::complete(openstudio::toPath("epjson_tests") / basename);
-  const auto idf_path = working_directory / openstudio::toPath("eplus.idf");
-  openstudio::filesystem::create_directories(working_directory);
-  openstudio::filesystem::copy_file(location, idf_path, openstudio::filesystem::copy_option::overwrite_if_exists);
-  return idf_path;
-}
-
-Json::Value translateIdfToEPJSONWithEP(const openstudio::path& location) {
-
-  // In case for some reason the energyplus CLI cannot do the conversion, we do not want to pick up an old artifact
-  const auto epJSONFile = openstudio::setFileExtension(location, "epJSON", true);
-  if (openstudio::filesystem::exists(epJSONFile)) {
-    openstudio::filesystem::remove(epJSONFile);
-  }
-
-  std::string cmd = fmt::format("{} --output-directory {} --convert-only {}", openstudio::toString(openstudio::getEnergyPlusExecutable().native()),
-                                openstudio::toString(location.parent_path().native()), openstudio::toString(location.native()));
-
-  [[maybe_unused]] auto result = std::system(cmd.c_str());
-  if (!openstudio::filesystem::exists(epJSONFile)) {
-    throw openstudio::Exception(
-      fmt::format("Error during the E+ CLI call, epJSON wasn't created at {}\ncmd = '{}'\n", openstudio::toString(epJSONFile), cmd));
-  }
-
-  const auto root = openstudio::epJSON::loadJSON(epJSONFile);
-
-  const auto outputLocation = location.parent_path() / openstudio::toPath("eplus-rewritten.epJSON");
-  std::ofstream ofs(openstudio::toString(outputLocation), std::ofstream::trunc);
-  ofs << root.toStyledString() << '\n';
-
-  return root;
-}
-
-openstudio::path completeIDFPath(const openstudio::path& idf) {
-  return openstudio::getEnergyPlusDirectory() / openstudio::toPath("ExampleFiles") / idf;
-}
-
-Json::Value translateIdfToEPJSONWithOS(const openstudio::path& location) {
-  auto idf = openstudio::IdfFile::load(location);
-
-  if (!idf) {
-    return Json::Value{};
-  }
-
-  auto result = openstudio::epJSON::toJSON(*idf);
-
-  const auto outputLocation = location.parent_path() / openstudio::toPath("os.epJSON");
-  std::ofstream ofs(openstudio::toString(outputLocation), std::ofstream::trunc);
-  ofs << result.toStyledString() << '\n';
-  return result;
-}
-
-void makeDoubles(Json::Value& value) {
-  if (value.isNumeric()) {
-    value = value.asDouble();
-  } else {
-    for (auto& child : value) {
-      makeDoubles(child);
-    }
-  }
-}
-
-bool equal(const Json::Value& lhs, const Json::Value& rhs) {
-  auto doubledLhs = lhs;
-  makeDoubles(doubledLhs);
-
-  auto doubledRhs = rhs;
-  makeDoubles(doubledRhs);
-
-  return doubledLhs == doubledRhs;
-}
-
-std::pair<Json::Value, Json::Value> doEPJSONTranslations(const std::string& idfname) {
-
-  const auto setupIdf = setupIdftoEPJSONTest(completeIDFPath(openstudio::toString(idfname)));
-  return {translateIdfToEPJSONWithEP(setupIdf), translateIdfToEPJSONWithOS(setupIdf)};
-}
-
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgMediumOfficeNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgMediumOfficeNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgMediumOfficeNew2004_Chicago.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_1ZoneEvapCooler) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("1ZoneEvapCooler.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("1ZoneEvapCooler.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_WCE_DoubleClear_BSDF) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("WCE_DoubleClear_BSDF.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("WCE_DoubleClear_BSDF.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_SupermarketSecondary) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("SupermarketSecondary.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("SupermarketSecondary.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_5ZoneWaterLoopHeatPump) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("5ZoneWaterLoopHeatPump.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("5ZoneWaterLoopHeatPump.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ZoneCoupledGroundHTSlabInGrade) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ZoneCoupledGroundHTSlabInGrade.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ZoneCoupledGroundHTSlabInGrade.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_CmplxGlz_MeasuredDeflectionAndShading) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("CmplxGlz_MeasuredDeflectionAndShading.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("CmplxGlz_MeasuredDeflectionAndShading.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_HospitalLowEnergy) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("HospitalLowEnergy.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("HospitalLowEnergy.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_HospitalBaselineReheatReportEMS) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("HospitalBaselineReheatReportEMS.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("HospitalBaselineReheatReportEMS.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_HospitalBaseline) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("HospitalBaseline.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("HospitalBaseline.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_CmplxGlz_Daylighting_SouthVB45deg) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("CmplxGlz_Daylighting_SouthVB45deg.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("CmplxGlz_Daylighting_SouthVB45deg.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_CmplxGlz_Daylighting_SouthVerticalVB45deg) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("CmplxGlz_Daylighting_SouthVerticalVB45deg.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("CmplxGlz_Daylighting_SouthVerticalVB45deg.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_CmplxGlz_InShadeGasMix) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("CmplxGlz_InShadeGasMix.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("CmplxGlz_InShadeGasMix.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_CmplxGlz_SchedSurfGains) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("CmplxGlz_SchedSurfGains.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("CmplxGlz_SchedSurfGains.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_ApartmentHighRise_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_ApartmentHighRise_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_ApartmentHighRise_STD2019_Denver.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_OutPatientHealthCare_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_OutPatientHealthCare_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_OutPatientHealthCare_STD2019_Denver.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgOutPatientNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgOutPatientNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgOutPatientNew2004_Chicago.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_SchoolSecondary_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_SchoolSecondary_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_SchoolSecondary_STD2019_Denver.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_Hospital_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_Hospital_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_Hospital_STD2019_Denver.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_UnitaryHybridAC_DedicatedOutsideAir) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("UnitaryHybridAC_DedicatedOutsideAir.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("UnitaryHybridAC_DedicatedOutsideAir.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgSmallHotelNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgSmallHotelNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgSmallHotelNew2004_Chicago.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgHospitalNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgHospitalNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgHospitalNew2004_Chicago.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgSecondarySchoolNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgSecondarySchoolNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgSecondarySchoolNew2004_Chicago.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_SchoolPrimary_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_SchoolPrimary_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_SchoolPrimary_STD2019_Denver.idf");
 }
 
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_OfficeLarge_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_OfficeLarge_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_OfficeLarge_STD2019_Denver.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_HotelLarge_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_HotelLarge_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_HotelLarge_STD2019_Denver.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_ApartmentMidRise_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_ApartmentMidRise_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_ApartmentMidRise_STD2019_Denver.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgMidriseApartmentNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgMidriseApartmentNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgMidriseApartmentNew2004_Chicago.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefrigeratedWarehouse) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefrigeratedWarehouse.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefrigeratedWarehouse.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_CmplxGlz_SmOff_IntExtShading) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("CmplxGlz_SmOff_IntExtShading.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("CmplxGlz_SmOff_IntExtShading.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgPrimarySchoolNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgPrimarySchoolNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgPrimarySchoolNew2004_Chicago.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_ASHRAE901_RetailStripmall_STD2019_Denver) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("ASHRAE901_RetailStripmall_STD2019_Denver.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("ASHRAE901_RetailStripmall_STD2019_Denver.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_RefBldgLargeHotelNew2004_Chicago) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("RefBldgLargeHotelNew2004_Chicago.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("RefBldgLargeHotelNew2004_Chicago.idf");
 }
 TEST_F(epJSONFixture, TranslateIDFToEPJSON_EMSDemandManager_LargeOffice) {
-  const auto [epTranslation, osTranslation] = doEPJSONTranslations("EMSDemandManager_LargeOffice.idf");
-  ASSERT_TRUE(epTranslation);
-  ASSERT_TRUE(osTranslation);
-
-  EXPECT_TRUE(equal(epTranslation, osTranslation));
+  compareEPJSONTranslations("EMSDemandManager_LargeOffice.idf");
 }
 
 TEST_F(epJSONFixture, toStringUilityWorksAsExpected) {
 
-  const auto location = completeIDFPath("RefBldgMediumOfficeNew2004_Chicago.idf");
+  const auto location = epJSONFixture::completeIDFPath("RefBldgMediumOfficeNew2004_Chicago.idf");
   auto idf = openstudio::IdfFile::load(location);
 
   ASSERT_TRUE(idf);