Documentation
Geometry CSV
Describe building geometry and services in CSV so they can be combined with thermal data into Home Energy Model (HEM:FHS) inputs. The format helps you author and check models faster and lets other tools produce compatible files.
- Contract version
- 0.2.1
- HEM:FHS source
- communitiesuk/epb-hem-wrapper-fhs
- HEM:FHS version
- v1.0.0a7 (alpha_prerelease)
Quickstart
Merge combines your geometry CSV with a defaults JSON file to produce HEM:FHS input JSON: values you set in the CSV are merged into that baseline, then the result is validated (schema and FHS preflight)—the same pipeline as the browser merge tool on this page. Omitting a CSV section usually means that part of the model is not supplied from the file; do not assume missing sections are silently reconstructed from defaults.
- Start from an example file if one is close to your dwelling.
- Add or remove sections until the CSV describes the geometry and systems you need.
- Check the matching Section reference card for column names, required fields, and allowed values.
Defaults template: defaults_template.json is the baseline JSON used for merge on this site and in the bundled examples.
Examples
Working snippet
Full enough metadata, zone, fabric, and systems for merge + validation in the browser tool.
Metadata,,,,,,,,,,,,,
GlobalOrientationOffset,0,,,,,,,,,,,,,
SchemaProfile,input_fhs,,,,,,,,,,,,,
DefaultsPath,input/defaults/defaults_template.json,,,,,,,,,,,,,
DefaultAssemblyWall,,,,,,,,,,,,,,
DefaultAssemblyRoof,,,,,,,,,,,,,,
DefaultAssemblyGroundFloor,,,,,,,,,,,,,,
DefaultThermalBridging,0.2,,,,,,,,,,,,,
NumberOfBedrooms,1,,,,,,,,,,,,,
NumberOfWetRooms,1,,,,,,,,,,,,,
GroundFloorArea,78.89,,,,,,,,,,,,,
HeatingControlType,SeparateTempControl,,,,,,,,,,,,,
PartGcompliance,TRUE,,,,,,,,,,,,,
ComplianceValidationEnabled,TRUE,,,,,,,,,,,,,
ScenariosBaseModelEnabled,FALSE,,,,,,,,,,,,,
AirPermeability_test_pressure,Standard,,,,,,,,,,,,,
AirPermeability_test_result,4,,,,,,,,,,,,,
BuildingLength,11.08,,,,,,,,,,,,,
BuildingWidth,7.12,,,,,,,,,,,,,
NumberOfHabitableRooms,1,,,,,,,,,,,,,
NumberOfHotTappedRooms,1,,,,,,,,,,,,,
NumberOfUtilityRooms,0,,,,,,,,,,,,,
NumberOfBathrooms,1,,,,,,,,,,,,,
NumberOfSanitaryAccommodations,0,,,,,,,,,,,,,
,,,,,,,,,,,,
Zone,,,,,,,,,,,,,
Name,Type,volume,floor_area,height,simplified thermal bridging,livingroom_area,restofdwelling_area
Zone 1,Zone,189.34,78.89,2.4,TRUE,50,28.89
Exposed Elements,,,,,,,,,,,,,,
Name,Zone,Type,area,pitch,width,height,orientation360,base_height,is_unheated_pitched_roof,is_external_door,parent_element,coords,extra_json
Wall (S),Zone 1,BuildingElementOpaque,22.97,,11.08,2.4,180,0,FALSE,FALSE,,"-6.340,-4.660,0.000|4.740,-4.660,0.000","{}"
Wall (E),Zone 1,BuildingElementOpaque,17.09,,7.12,2.4,90,0,FALSE,FALSE,,"4.740,-4.660,0.000|4.740,2.460,0.000",
Wall (N),Zone 1,BuildingElementOpaque,22.97,,11.08,2.4,0,0,FALSE,FALSE,,"4.740,2.460,0.000|-6.340,2.460,0.000",
Wall (W),Zone 1,BuildingElementOpaque,13.47,,7.12,2.4,270,0,FALSE,FALSE,,"-6.340,2.460,0.000|-6.340,-4.660,0.000",
Wall,Zone 1,BuildingElementOpaque,78.89,0,8.88,8.88,0,2.4,FALSE,FALSE,,"-6.340,2.460,1.000|4.740,2.460,1.000|4.740,-4.660,1.000|-6.340,-4.660,1.000","{}"
Window Elements,,,,,,,,,,,,,,
Name,Zone,Type,area,pitch,width,height,orientation360,base_height,linked_wall,frame_area_fraction,free_area_height,mid_height,max_window_open_area,coords,extra_json
Window,Zone 1,BuildingElementTransparent,3.62,,3.02,1.2,0,0.2,Wall (N),0.1,1,0.8,1.2,"0.820,2.460,0.000|-2.200,2.460,0.000","{}"
Window 1,Zone 1,BuildingElementTransparent,3.62,,3.02,1.2,270,0.2,Wall (W),0.1,1,0.8,1.2,"-6.340,0.610,0.000|-6.340,-2.410,0.000","{}"
Window 1 1,Zone 1,BuildingElementTransparent,3.62,,3.02,1.2,180,0.2,Wall (S),0.1,1,0.8,1.2,"-1.910,-4.660,0.000|1.110,-4.660,0.000","{}"
Lighting,,,,,,,,,,,,,
Name,Zone,efficacy,count,power,coords,extra_json
Light,Zone 1,80,1,10,"-0.960,0.500,0.000","{}"
Ground Elements,,,,,,,,,,,,,
Name,Zone,Type,area,width,height,perimeter,floor_type,depth_basement_floor,thickness_walls,parent_element,coords,extra_json
Floor,Zone 1,BuildingElementGround,78.89,0,0,36.4,Slab_no_edge_insulation,,,,"-6.340,-4.660,0.000|4.740,-4.660,0.000|4.740,2.460,0.000|-6.340,2.460,0.000","{}"
Ventilation Systems,,,,,,,,,,,,
Name,Type,length,duct_type,parent_element,mid_height_air_flow_path,area_cm2,orientation360,pitch,vent_type,coords,extra_json
Background Vent 1,Vents,,,,1.5,120,180,90,,"0.820,2.460,0.000","{}"
Background Vent 2,Vents,,,,1.5,120,270,90,,"-6.340,0.610,0.000","{}"
Background Vent 3,Vents,,,,1.5,120,0,90,,"-1.910,-4.660,0.000","{}"
Background Vent 4,Vents,,,,1.5,120,90,90,,"4.740,-2.000,0.000","{}"
Intermittent MEV,MechanicalVentilation,,,,,,,,Intermittent MEV,"2.800,1.560,0.000","{""design_outdoor_air_flow_rate"":270}"
Appliances,,,,
Name,Type,appliancekey,coords
Fridge,Appliance,Fridge,"3.280,-0.540,0.000"
Hot Water Outlets,,,,,,,,
Name,Type,subcategory,flowrate,size,rated_power,allow_low_flowrate,coords
Tap,HotWaterDemand,OtherWaterUseDetails,8,,,,"1.120,-0.120,0.000"
Systems,,,,,,,
Name,Zone,Type,subcategory,system_preset,coords,extra_json
Storage Tank,Zone 1,System,HotWaterSource,immersion_cylinder,"-2.360,-1.600,0.000","{""HotWaterSource"":{""hw cylinder"":{""type"":""StorageTank"",""volume"":80,""daily_losses"":1.68,""init_temp"":55,""ColdWaterSource"":""mains water"",""HeatSource"":{""immersion"":{""type"":""ImmersionHeater"",""power"":3,""EnergySupply"":""mains elec"",""heater_position"":0.1,""thermostat_position"":0.33}}}},""_system_source"":""presets""}"
Section reference
Each block below is one CSV section with a field table. Required? says how much you must supply; a tag next to a field means merge behaviour differs from the usual path (same column name can mean different things by section—e.g. coords).
Related columns (extra cross-field rules) only appear when the contract lists them for that section. How rows become part of the model appears when output_mapping.merge_mode and/or merge_glossary_backrefs are set (see geometry_csv_contract.json). Many sections document merge behaviour only in the field table or under Types and row variants—that is normal, not an omission.
Column tags
- Outside merged modelNot used in the usual dwelling-model merge for this field or section.
Required?
- RequiredMust be correct for this dwelling.
- Required whenOnly when the row matches the rule (grey “when …” text).
- OptionalCan be blank if defaults or other cells supply the value.
- Layout onlyFor plans and layout—not compliance numbers on its own.
Grey when … lines mark conditional columns. Merge semantics: merge_effect_definitions in geometry_csv_contract.json.
Metadata
Project-wide settings rows (Key, Value): schema profile, defaults file paths, compliance inputs, and editor/workflow flags that the conversion step reads before merging geometry into the dwelling JSON.
Metadata keys
Each row is Key + Value (two columns). Tags under each key name follow the merge legend in Section reference (Outside merged model).
| Key | Format | Required? |
|---|---|---|
| AirPermeability_test_pressure | Enum | Required |
| AirPermeability_test_result | Number | Required |
| AirPermeability_ventilation_zone_height | Number | Required |
| BuildingLength | Number | Required |
| BuildingWidth | Number | Required |
| General_build_type | Enum | Required |
| General_storeys_in_dwelling | Integer | Required |
| GroundFloorArea | Number | Required |
| HeatingControlType | Enum | Required |
| KitchenExtractorHoodExternal | Boolean | Required |
| Location | String | Required |
| NumberOfBathrooms | Integer | Required |
| NumberOfBedrooms | Integer | Required |
| NumberOfHabitableRooms | Integer | Required |
| NumberOfHotTappedRooms | Integer | Required |
| NumberOfSanitaryAccommodations | Integer | Required |
| NumberOfUtilityRooms | Integer | Required |
| NumberOfWetRooms | Integer | Required |
| PartGcompliance | Boolean | Required |
| PartO_active_cooling_required | Boolean | Required |
| SchemaProfileOutside merged model | String | Required |
| Ventilation_altitude | Number | Required |
| Ventilation_noise_nuisance | Boolean | Required |
| Ventilation_shield_class | Enum | Required |
| Ventilation_terrain_class | Enum | Required |
| AirPermeability_env_areaOutside merged model | String | Optional |
| ComplianceValidationEnabledOutside merged model | String | Optional |
| DefaultAssemblyGroundFloorOutside merged model | String | Optional |
| DefaultAssemblyRoofOutside merged model | String | Optional |
| DefaultAssemblyWallOutside merged model | String | Optional |
| DefaultsPathOutside merged model | Path | Optional |
| DefaultThermalBridging | Number | Optional |
| General_storeys_in_building | Integer | Optional |
| GuideOverlayOutside merged model | String | Optional |
| JunctionPsiDefaultsPathOutside merged model | String | Optional |
| SapDirOutside merged model | String | Optional |
| SapPdfPathOutside merged model | String | Optional |
| SapXmlPathOutside merged model | String | Optional |
| ScenariosBaseModelEnabledOutside merged model | String | Optional |
| GlobalOrientationOffsetOutside merged model | String | Layout only |
Notes
- The Metadata keys table lists every key. In generated docs, Outside merged model matches merge_effect values other than affects_calculation_input (including workflow_only for editor/tooling keys and not_in_calculation_input for paths and canvas-only values); those keys are not merged into dwelling_calculation_input_json as ordinary dwelling fabric/system inputs. Keys with affects_calculation_input participate where each authoring_note describes. Per-key merge_effect and populate_requirement in this contract are authoritative.
Zone
Thermal zones and zone-level attributes.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| TypeOutside merged model | String | Required |
| floor_area | Number | Required |
| livingroom_area | Number | Required |
| restofdwelling_area | Number | Required |
| height | Number | Required when when Used for volume derivation; whether height remains on the merged zone object depends on output mode (FHS may strip). include this column header |
| simplified thermal bridging | String | Optional |
| volume | Number | Optional include this column header |
How rows become part of the model
Zone rows create or update each `Zone.<zone name>` object at the dwelling JSON root before fabric and system merges. (merge_zone_rows_into_root_zone_map)
Exposed Elements
External opaque/roof/door building elements by zone.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| coords | Coords (x,y,z) | Required |
| height | Number | Required |
| orientation360 | Number | Required |
| pitch | Number | Required |
| width | Number | Required |
| Zone | String | Required |
| is_external_door | Boolean | Required when when door or opaque branch semantics apply (see fields_by_type.BuildingElementOpaque). |
| is_unheated_pitched_roof | Boolean | Required when when opaque roof semantics apply (see fields_by_type.BuildingElementOpaque). |
| parent_elementOutside merged model | String | Required when |
| area | Number | Optional include this column header |
| base_height | Number | Optional |
| extra_json | JSON object | Optional |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns.
- BuildingElementOpaque — areal_heat_capacity, colour, mass_distribution_class, thermal_resistance_construction, u_value
How rows become part of the model
Rows merge under `Zone.<Zone>.BuildingElement.<Name>` on the schema branch for each row Type (opaque surfaces, glazing, ground-contact slabs). The Zone column selects the parent zone; Name keys the element. (merge_building_element_rows_under_zone)
Window Elements
Transparent elements by zone. Glazing rows merge along the transparent path (e.g. U-value and related glazing fields). Mass-category fields such as `areal_heat_capacity` do not apply as they do for opaque or ground fabric—do not expect them to populate regulated glazing inputs.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| base_height | Number | Required |
| coords | Coords (x,y,z) | Required |
| frame_area_fraction | Number | Required |
| free_area_height | Number | Required |
| height | Number | Required |
| max_window_open_area | Number | Required |
| orientation360 | Number | Required |
| pitch | Number | Required |
| width | Number | Required |
| Zone | String | Required |
| linked_wallOutside merged model | String | Required when |
| extra_json | JSON object | Optional |
| mid_heightOutside merged model | Number | Optional |
| areaOutside merged model | Number | Layout only include this column header |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns.
- BuildingElementTransparent — controls, delta_r, depth, distance, g_value, mid_height_air_flow_path, security_risk, shading, trans_red, treatment, window_part_list
How rows become part of the model
Rows merge under `Zone.<Zone>.BuildingElement.<Name>` on the schema branch for each row Type (opaque surfaces, glazing, ground-contact slabs). The Zone column selects the parent zone; Name keys the element. (merge_building_element_rows_under_zone)
Ground Elements
Ground-contact elements by zone.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | String | Required |
| area | Number | Required |
| coords | Coords (x,y,z) | Required |
| floor_type | String | Required |
| perimeter | Number | Required |
| Zone | String | Required |
| depth_basement_floor | Number | Required when when Basement or below-grade ground variants where schema exposes depth_basement_floor. |
| thickness_walls | String | Required when when Basement or enclosing-wall variants where schema exposes thickness_walls. |
| extra_json | JSON object | Optional |
| height | Number | Optional |
| width | Number | Optional |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns.
- BuildingElementGround — area_per_perimeter_vent, areal_heat_capacity, edge_insulation, edge_thermal_resistance, height_basement_walls, height_upper_surface, mass_distribution_class, psi_wall_floor_junc, shield_fact_location, thermal_resist_insul, thermal_resist_walls_base, thermal_resistance_floor_construction, thermal_transm_envi_base, thermal_transm_walls, u_value
How rows become part of the model
Rows merge under `Zone.<Zone>.BuildingElement.<Name>` on the schema branch for each row Type (opaque surfaces, glazing, ground-contact slabs). The Zone column selects the parent zone; Name keys the element. (merge_building_element_rows_under_zone)
Non-Exposed Elements
Adjacent/non-exposed elements by zone.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| coords | Coords (x,y,z) | Required |
| height | Number | Required |
| pitch | Number | Required |
| width | Number | Required |
| Zone | String | Required |
| area | Number | Optional include this column header |
| extra_json | JSON object | Optional |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns.
- BuildingElementAdjacentConditionedSpace — areal_heat_capacity, mass_distribution_class, thermal_resistance_construction, u_value
- BuildingElementAdjacentUnconditionedSpace_Simple — areal_heat_capacity, mass_distribution_class, thermal_resistance_construction, thermal_resistance_unconditioned_space, u_value
- BuildingElementPartyWall — areal_heat_capacity, mass_distribution_class, thermal_resistance_construction
How rows become part of the model
Rows merge under `Zone.<Zone>.BuildingElement.<Name>` on the schema branch for each row Type (opaque surfaces, glazing, ground-contact slabs). The Zone column selects the parent zone; Name keys the element. (merge_building_element_rows_under_zone)
Thermal Bridging Elements
Linear/point thermal bridge entries by zone.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| coordsOutside merged model | Coords (x,y,z) | Required |
| Zone | String | Required |
| heat_transfer_coeff | Number | Required when when Type = ThermalBridgePoint |
| length | Number | Required when when Type = ThermalBridgeLinear |
| linear_thermal_transmittance | Number | Required when when Type = ThermalBridgeLinear |
| extra_json | JSON object | Optional |
Notes
- Rows are skipped for zones where Zone.simplified thermal bridging is true; merged keys are filtered to valid thermal-bridge properties (coords are not copied onto merged TB objects).
Types and row variants
- linear bridge — Triggered when Type = ThermalBridgeLinear. Often uses `length`, `linear_thermal_transmittance`, `coords`
- point bridge — Triggered when Type = ThermalBridgePoint. Often uses `heat_transfer_coeff`, `coords`
How rows become part of the model
Rows merge into `Zone.<Zone>.ThermalBridging.<Name>` (skipped for zones where simplified thermal bridging applies). (merge_thermal_bridge_rows_into_zone_thermal_bridging)
Window Shading
Shading objects linked to window elements.
Merged dwelling JSON path: Zone.<Zone>.BuildingElement.<linked_window>.shading
Fields
| Field | Format | Required? |
|---|---|---|
| NameOutside merged model | String | Required |
| Type | Enum | Required |
| linked_window | String | Required |
| Zone | String | Required |
| depth | Number | Required when when Type is overhang, sidefinright, sidefinleft, or reveal (see row_contract branches). |
| height | Number | Required when when Type is object (CSV), mapped to obstacle in JSON (see row_contract branch object). |
| distance | Number | Optional |
| extra_jsonOutside merged model | JSON object | Optional |
| transparency | String | Optional |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
Notes
- Rows reference windows via linked_window and are attached to the matching window in-zone.
Related columns
- linked_window → Window Elements (matches window Name within the same Zone)
How rows become part of the model
Attach shading payload to an existing window element in the named zone. (attach_to_existing_window_in_zone)
Lighting
Zone lighting entries used to build Lighting objects.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| count | Number | Required |
| efficacy | String | Required |
| power | Number | Required |
| Zone | String | Required |
| extra_json | JSON object | Optional |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
How rows become part of the model
Lighting rows populate the `Lighting` object on each referenced zone (counts, efficacy, power). (merge_lighting_rows_into_zone_lighting)
Ventilation Systems
Ventilation rows for vents, mechanical systems, and ductwork links.
Merged dwelling JSON path: InfiltrationVentilation
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| area_cm2 | Number | Optional when Type = Vents |
| duct_type | String | Optional when Type = MechanicalVentilationDuctwork |
| extra_json | JSON object | Optional |
| length | Number | Optional when Type = MechanicalVentilationDuctwork |
| mid_height_air_flow_path | Number | Optional when Type = Vents |
| orientation360 | Number | Optional when Type = Vents |
| parent_element | String | Optional when Type = MechanicalVentilationDuctwork |
| pitch | Number | Optional when Type = Vents |
| vent_type | String | Optional when Type = MechanicalVentilation |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns.
- MechanicalVentilation — EnergySupply, SFP, SFP_in_use_factor, cross_section_shape, design_outdoor_air_flow_rate, design_zone_cooling_covered_by_mech_vent, design_zone_heating_covered_by_mech_vent, duct_type, ductwork, external_diameter_mm, insulation_thermal_conductivity, insulation_thickness_mm, internal_diameter_mm, length, measured_air_flow_rate, measured_fan_power, mid_height_air_flow_path, mvhr_eff, mvhr_location, orientation360, pitch, position_exhaust, position_intake, reflective
Types and row variants
- vents — Triggered when Type = Vents. Often uses `mid_height_air_flow_path`, `area_cm2`, `orientation360`, `pitch`
- mechanical — Triggered when Type = MechanicalVentilation. Often uses `vent_type`, `extra_json`
- ductwork — Triggered when Type = MechanicalVentilationDuctwork. Often uses `length`, `duct_type`, `parent_element`, `extra_json`
How rows become part of the model
Merge into a defaults-backed root object (for example InfiltrationVentilation). (merge_into_defaults_backed_root_section)
Water Pipework
DHW pipework segments: one CSV row per segment. `pipework_type` selects primary cylinder runs versus distribution; author primary cylinder piping here instead of only via Systems `extra_json`.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| extra_json | JSON object | Optional |
| length | Number | Optional |
| location | String | Optional |
| pipework_type | Enum | Optional |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
Notes
- Primary segments append in file order to `HotWaterSource.<store>.primary_pipework[]`. Distribution segments append in file order to `HotWaterDemand.Distribution`. Blank or non-`primary` values count as distribution.
- Store keys (e.g. `hw cylinder`) come from the merged model. The same primary_pipework result is applied to each tank-shaped hot-water store; combi boilers do not consume these rows.
Types and row variants
- primary — Triggered when pipework_type = primary
- distribution — Triggered when pipework_type = distribution
How rows become part of the model
Split Water Pipework rows by pipework_type: primary→each object appended in order to primary_pipework[]; other values (including missing/empty) →distribution, each object appended in order to HotWaterDemand.Distribution. Seeds each new segment from the first defaults-backed segment in defaults_template when present. (append_water_pipework_rows_to_model)
After building the primary array, conversion writes the same JSON array on every HotWaterSource entry whose type is StorageTank or the legacy `hw cylinder` value. CombiBoiler entries are not written from this section. (replicate_primary_pipework_across_tank_shaped_hws)
Wet Emitters
Wet emitter records (e.g. radiators/UFH/fancoils) by zone.
Merged dwelling JSON path: SpaceHeatSystem.<Zone and subcategory derived name>
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| subcategory | Enum | Required |
| Zone | String | Required |
| area | Number | Optional |
| extra_json | JSON object | Optional |
| unit_number | Number | Optional |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns. Extra lines below only name keys that row types add on top of the Type list.
- WetEmitter — c, n, frac_convective, equivalent_specific_thermal_mass, system_performance_factor, n_units, fancoil_test_data
Related columns
- subcategory — determines emitter/system branch and resulting WetDistribution naming
Types and row variants
- radiator — Triggered when subcategory = radiator
- ufh — Triggered when subcategory = ufh
- fancoil — Triggered when subcategory = fancoil
How rows become part of the model
Group Wet Emitters rows, rebuild wet distribution systems from aggregates. (aggregate_rows_by_zone_and_subcategory_then_replace_existing_wet_distribution_systems)
Appliances
Appliance key records used for appliance demand mapping.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| appliancekey | Enum | Required |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
Hot Water Outlets
Hot water outlet demand records.
Merged dwelling JSON path: HotWaterDemand
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| subcategory | Enum | Required |
| allow_low_flowrate | Number | Optional |
| flowrate | Number | Optional |
| rated_power | Number | Optional |
| size | String | Optional |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns.
- HotWaterDemand — ColdWaterSource, EnergySupply, HotWaterSource, type
Types and row variants
- mixer shower — Triggered when subcategory = MixerShower. Often uses `type`, `flowrate`, `allow_low_flowrate`
- instant elec shower — Triggered when subcategory = InstantElecShower. Often uses `type`, `rated_power`
- bath — Triggered when subcategory = Bath. Often uses `size`
- other — Triggered when subcategory = OtherWaterUseDetails. Often uses `flowrate`
How rows become part of the model
Overlay HotWaterDemand fragments from defaults-backed structure. (merge_into_existing_defaults_backed_hot_water_demand)
Context Shading
Site/context shading geometry entries.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Optional |
| distance | Number | Optional |
| end_angle | String | Optional |
| height | Number | Optional |
| parent_elementOutside merged model | String | Optional |
| shading_type | Enum | Optional |
| start_angle | String | Optional |
| coordsOutside merged model | Coords (x,y,z) | Layout only |
Notes
- Dwelling-model conversion accepts shading_type first, then falls back to Type. It accepts both underscore and spaced angle headers; coords and parent_element are roundtrip-only.
How rows become part of the model
Rows contribute obstacle shading geometry into `ExternalConditions.shading_segments` (10° horizon buckets). (merge_context_shading_into_external_conditions_segments)
On-Site Generation
On-site generation systems (e.g. PV) records.
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required |
| Type | Enum | Required |
| generation_type | Enum | Required |
| base_height | Number | Optional |
| coords | Coords (x,y,z) | Optional |
| extra_json | JSON object | Optional |
| orientation360 | Number | Optional |
| peak_power | Number | Optional |
| pitch | Number | Optional |
extra_json keys by Type
Extra fields allowed in the row's extra_json cell, besides the normal columns.
- OnSiteGeneration — EnergySupply, depth, distance, inverter_is_inside, inverter_peak_power_ac, inverter_peak_power_dc, inverter_type, shading, ventilation_strategy
How rows become part of the model
Rows become named entries under root `OnSiteGeneration` (currently PhotovoltaicSystem payloads). (merge_onsite_generation_named_entries)
Systems
System composition rows for heating/hot water/cooling components.
Merged dwelling JSON path: multiple_root_sections
Fields
| Field | Format | Required? |
|---|---|---|
| Name | String | Required when when Required for named SpaceHeatSystem / WetDistribution rows and System fragment rows (see row_kinds); optional for ElectricBattery-only rows. include this column header |
| Type | String | Required when when Effective dispatch uses Type, else system_type, else subcategory; at least one must identify the row or merge errors/skips apply. include this column header |
| extra_json | JSON object | Required when when Payload for System and ElectricBattery paths; may be minimal when template defaults fill the merged object. |
| subcategory | String | Required when when Required when Type is System (PCDB-style); can participate in legacy dispatch when Type/system_type empty. |
| Zone | String | Required when when Required where merge assigns zone-linked systems (e.g. named_space_heat_system, some System subcategories); inert for other branches. |
| system_presetOutside merged model | String | Optional when Type = System and dispatch_fallback_order in Type, system_type, subcategory |
| system_type | String | Optional when Type = ElectricBattery and legacy_subcategory = ElectricBattery and dispatch_fallback_order in Type, system_type, subcategory; Type in SpaceHeatSystem, WetDistribution and dispatch_fallback_order in Type, system_type, subcategory; Type = System and dispatch_fallback_order in Type, system_type, subcategory |
| coordsOutside merged model | Coords (x,y,z) | Layout only when Type = ElectricBattery and legacy_subcategory = ElectricBattery and dispatch_fallback_order in Type, system_type, subcategory; Type in SpaceHeatSystem, WetDistribution and dispatch_fallback_order in Type, system_type, subcategory; Type = System and dispatch_fallback_order in Type, system_type, subcategory |
Notes
- System rows are dispatched by Type/system_type (or subcategory fallback for Type=System). Builder merge also reads zone_reference (see references) on some branches alongside Zone. FHS: `HotWaterSource` must be authored (Systems row and/or `HeatSourceWet` `extra_json` composite); the defaults template is not a substitute.
Related columns
- zone_reference → Zone (used by some system branches when assigning zone-linked output)
Types and row variants
- electric battery — Triggered when Type = ElectricBattery and legacy_subcategory = ElectricBattery and dispatch_fallback_order in Type, system_type, subcategory
- named space heat system — Triggered when Type in SpaceHeatSystem, WetDistribution and dispatch_fallback_order in Type, system_type, subcategory
- system fragment — Triggered when Type = System and dispatch_fallback_order in Type, system_type, subcategory
How rows become part of the model
Systems section: route row by Type/system_type/subcategory-specific handlers (see systems_dispatch). (branch_specific_by_Type_system_type_and_subcategory)
Contract appendix
The authoritative contract is the JSON file geometry_csv_contract.json. Below is a human-readable copy of the important parts: error codes, which section headers the importer accepts, how Systems rows are routed into the model, and notes on extra_json fields—so you can read them here instead of digging through the raw JSON.
Minimum file & section titles
Include Metadata as needed, at least one Zone row with Name, and the fabric/system sections your dwelling needs. Skip sections only if your defaults_template already supplies a complete dwelling_calculation_input_json for your workflow—otherwise conversion fails or the model is incomplete.
Titles the importer accepts
Test Section is for tests only—omit it in real files.
- Metadata
- Exposed Elements
- Window Elements
- Ground Elements
- Non-Exposed Elements
- Thermal Bridging Elements
- Zone
- Window Shading
- Lighting
- Ventilation Systems
- Water Pipework
- Wet Emitters
- Appliances
- Hot Water Outlets
- Context Shading
- On-Site Generation
- Systems
Error codes
The same numeric label (for example E001) can mean different failures in the CSV parsing stage (PARSER_*) versus conversion (BUILDER_*); always use subsystem prefix plus message text. The conversion step reuses several numeric codes (notably E027–E033, E038–E040) for unrelated failures—rely on the full error message, not the code alone.
While reading the CSV
| Label | Meaning |
|---|---|
| PARSER_E001 | Unknown section name (not in parser allowed list). |
| PARSER_E002 | Duplicate section header in the same file. |
| PARSER_E003 | Data row appears before the first section header. |
| PARSER_E004 | Row has more columns than the section header with non-empty trailing cells (data would be truncated). |
| PARSER_E006 | Required column missing for this section (does not satisfy parser-required headers for the section). |
| PARSER_E007 | Duplicate Name within the same section. |
| PARSER_E008 | Zone column references a zone name that is not defined on any prior Zone row (forward reference). |
| PARSER_E009 | Ventilation Systems row with Type MechanicalVentilation has vent_type outside the allowed enum. |
While building the dwelling model
| Label | Meaning |
|---|---|
| BUILDER_E001 | Schema JSON file path does not exist. |
| BUILDER_E002 | Failed to read schema file from disk. |
| BUILDER_E003 | Schema file is not valid JSON. |
| BUILDER_E004 | Defaults JSON file path does not exist. |
| BUILDER_E005 | Failed to read defaults file from disk. |
| BUILDER_E006 | Defaults file is not valid JSON. |
| BUILDER_E007 | Zone section row missing required Name. |
| BUILDER_E008 | Fabric element row (Exposed/Window/Ground/Non-Exposed) missing Name. |
| BUILDER_E009 | Fabric element row missing Zone. |
| BUILDER_E010 | Fabric element row missing Type. |
| BUILDER_E011 | Fabric element references a zone not present in merged Zone map. |
| BUILDER_E012 | Thermal Bridging Elements row missing Name. |
| BUILDER_E013 | Thermal Bridging Elements row missing Zone. |
| BUILDER_E014 | Thermal Bridging Elements row missing Type. |
| BUILDER_E015 | Thermal bridging references an undefined zone. |
| BUILDER_E016 | Lighting row missing Zone. |
| BUILDER_E017 | Lighting references an undefined zone. |
| BUILDER_E018 | Appliances row missing appliancekey. |
| BUILDER_E019 | Hot Water Outlets row missing Name. |
| BUILDER_E020 | Hot Water Outlets row missing subcategory, or Ventilation Systems row missing Name. |
| BUILDER_E021 | Hot Water Outlets row missing Type, or Ventilation Systems row missing Type, or related ventilation merge validation. |
| BUILDER_E023 | Combustion Appliances row missing Name. |
| BUILDER_E024 | Wet Emitters row missing Zone. |
| BUILDER_E025 | Wet Emitters row missing subcategory, or JSON Schema compilation failure. |
| BUILDER_E026 | Merge/build structural errors not covered by per-row codes (e.g. merged root not an object, invalid SpaceHeatSystem FanCoil n_units). Read message. |
| BUILDER_E027 | Generic missing-field or missing-default-fragment error used in many branches (read message). |
| BUILDER_E028 | Unknown thermal bridge Type, or no zones in CSV when consolidation requires them. |
| BUILDER_E029 | Window Shading missing Zone, or JSON shape not an object when expected. |
| BUILDER_E030 | Window Shading missing linked_window, or OnSiteGeneration not an object. |
| BUILDER_E031 | Window shading references undefined zone, or Lighting/count validation, or On-Site Generation missing Name, or zone rename failure. |
| BUILDER_E032 | Linked window not found in zone, or invalid numeric parse in leaks path, or On-Site Generation missing generation_type. |
| BUILDER_E033 | Invalid numeric conversion for leaks metadata, or unsupported On-Site Generation generation_type. |
| BUILDER_E034 | Expected JSON object at merge root or branch. |
| BUILDER_E035 | Systems row missing Type/system_type/subcategory discriminator. |
| BUILDER_E036 | EnergySupply exists but is not a JSON object. |
| BUILDER_E037 | Named EnergySupply entry is not a JSON object. |
| BUILDER_E038 | EnergySupply aggregate not an object, or SpaceHeatSystem row missing Name. |
| BUILDER_E039 | SpaceHeatSystem container is not a JSON object. |
| BUILDER_E040 | MechanicalVentilation missing required schema field, ductwork on non-MVHR, or Systems element missing Name. |
| BUILDER_E041 | HeatSourceWet merge target is not a JSON object. |
| BUILDER_E042 | HotWaterSource merge target is not a JSON object, or intermediate JSON not an object. |
| BUILDER_E043 | SpaceCoolSystem merge target is not a JSON object. |
| BUILDER_E044 | Wet Emitters in FHS mode require an explicit HeatSourceWet Systems row. |
| BUILDER_E045 | Defaults template missing WetDistribution SpaceHeatSystem template when Wet Emitters need it. |
| BUILDER_E046 | Merged JSON failed JSON Schema validation against input_fhs.schema.json (post-merge). Each row lists jsonschema message and instance path; read message. |
| BUILDER_E999 | Filesystem or serialization failure writing batch outputs (not a CSV shape error). |
Systems routing
- Read Type, then if empty read system_type, then if empty read subcategory—the first non-empty value routes the Systems row.
- Contract row_kinds map Type values to payload_contract and target_path for nested FHS fragments.
- For PCDB-style inner payloads chosen by subcategory, the outer row must still route as System; otherwise the row may be skipped without a parser error.
- ElectricBattery nests under EnergySupply.<extra_json.EnergySupply or mains elec>.ElectricBattery.
Routing diagram (Mermaid)
flowchart TD
R[System CSV row] --> D{First non-empty among Type, system_type, subcategory}
D -->|ElectricBattery| EB[Merge into EnergySupply named in extra_json]
D -->|SpaceHeatSystem / WetDistribution| SHS[SpaceHeatSystem wet distribution branch]
D -->|HeatSourceWet / HotWaterSource / …| WK[Use row_kinds + fields_by_type from contract]
D -->|unknown / unroutable| SK[May be skipped silently depending on branch]extra_json
extra_json carries fields beyond plain CSV columns; values merge after column cells using the usual precedence (CSV > extra_json > defaults unless the section says otherwise).
Use section field tables first; use row_kinds / payload_contract when the contract splits behaviour by branch.
Treat extra_json as strict JSON when exchanging files between tools. Browser import may drop invalid cells; other tooling may keep them as plain text—do not rely on the same error handling everywhere.
Which keys are allowed where
- building_elements
- Advanced keys mirror the FHS element subschemas, excluding columns already modeled as standard CSV headers and excluding fields the app UI deliberately hides (e.g. HotWaterSource primary_pipework is authored from the `Water Pipework` section, not the System “Hot water” advanced panel in the FHS web app).
- ventilation_systems
- Advanced keys mirror the FHS-backed editor surface for Vents, MechanicalVentilation, and ductwork rows.
- combustion_appliances
- Advanced keys mirror the public FHS-backed UI/schema surface (not ad-hoc conversion allowlists).
- water_pipework
- On each `WaterPipework` row, extra_json overlays the WaterPipework item schema (same keys you could place on a primary_pipework[] element) after CSV column overlay; the section split primary vs distribution is by pipework_type (see that section's notes).
- wet_emitters
- Advanced keys mirror the public FHS-backed UI/schema surface; the app may still adjust some emitter fields outside raw extra_json editing.
- systems
- Use row_kinds and payload_contract in this file; field paths follow the FHS-backed editor surface with branch coverage inferred from defaults and shipped presets.
Test CSV merge in your browser
Runs locally using the same engine as Vulcan — your data stays on this device.