Program JSON 6.1.0¶
The separate backend DOE reference catalog preserves source identities and declared departures. Its packet format is not atlas program JSON; this component's schema and illustrative defaults are unchanged.
Status: Provisional (ADR-018 and ADR-027, realisation pending the owner's reading) · Since: version 1.2.0 (D-126); program JSON 5.4.0 of atlas release 2.0.0 since D-172, of release 2.1.0 since version 1.6.0 (D-198), and of release 2.2.0 and later labels since version 1.7.0 (D-205); program JSON 6.0.0 of atlas release 2.3.0 since version 1.9.0, and 5.4.0 no longer (D-239, ADR-038); program JSON 6.1.0 of atlas release 2.4.0 since version 1.10.0, and 6.0.0 no longer (D-253, ADR-040); the editor window and the LF checksum since D-128, on
mainsince D-207 · Component: Program JSON (PJson), panel 1 Program, GUIDabb0aeb9-5a9f-40ff-b3a1-bb125cfb675c· Code:Lod.ProgramJson(src/Lod.ProgramJson/) · Design notes: 2026-10-07-program-json.md, 2026-10-28-atlas-release-2-0-0.md
Purpose¶
The Energy Archetype Atlas publishes a program as program JSON: one JSON object with the program's loads, its controls, and every schedule they use. Atlas release 2.4.0 (a delta on 2.3.0 with the PNNL prototype corrections of the review's round 2: the unoccupied lighting cap, the PNNL office lighting power, the 90.1 elevators and kitchen reach-ins; the atlas's ADR 0060 to ADR 0062; D-253) exports program JSON 6.1.0, which is 6.0.0 with the optional lighting_controls.unoccupied_lighting_cap (releases 2.0.0 to 2.2.0 exported 5.4.0 and release 2.3.0 6.0.0, which BEMGen reads no more), in two versions: source, the actual values only, with what the source does not record left null, and filled, with the atlas's fillings and approved defaults applied (policy atlas-program-defaults-2.0.0) and the hot water as draw-only water heating. IECC programs and code-prototype dwellings export the source version only. Program JSON turns the text of either version into a ready-to-use program preset: the preset goes into any preset input of a plan generator, into Any Program Preset, or into Mix Programs, and is converted by Convert2BEM and Convert2IDF like any other.
The solve reads only the JSON text: no file path and no network. The component's atlas window (below, D-208) uses the network after a click, from the atlas site only, and stores the text it downloads. A program JSON 5.4.0 of releases 2.0.0 to 2.2.0 or 6.0.0 of release 2.3.0 is refused with the advice to take it again from release 2.4.0, and the refusal names the program's release 2.4.0 ID through the atlas's ID maps, embedded byte for byte (AtlasIdMap, D-240, D-254): a 2.2.0 ID through id-map-2.3.0.json and then id-map-2.4.0.json, a 2.3.0 ID through id-map-2.4.0.json; the retired program JSON 2.0.0 (D-126) is refused too. The embedded schema is the atlas's 6.1.0 schema, revision 1 (atlas 7c543f5d, with its $comment revision marker), whose source.release is the pattern ^atlas-release-\d+\.\d+\.\d+$. The contract's schema, twelve release exports, and the recorded atlas site's three store files (Fixtures/site/, D-208) are the only atlas program files in the repository; tests/Lod.ProgramJson.Tests/Fixtures/README.md gives their origin and manifest.json their SHA-256.
Lod.ProgramJson is a plain library (net7.0, like the plugin) that depends on Lod.Core and the JSON Schema library; it never references RhinoCommon or Grasshopper (AGENTS.md). The component is a thin adaptor (GLOBAL.md architecture rule 3).
The contract¶
The schema is program-json-v6.schema.json (Draft 2020-12), one oneOf of a source and a filled branch, embedded byte for byte in src/Lod.ProgramJson/Schemas/. The members BEMGen reads:
| Member | Content | In BEMGen |
|---|---|---|
schema_version, kind, version |
"6.1.0", "program", source or filled |
checked before the schema; anything else is ProgramJsonVersionNotSupported |
id, name, scope |
the release program ID (re-derived in release 2.0.0, ADR 0046 §8 of the atlas, for 1,550 programs again in 2.3.0, its ADR 0053 §5, and for 2,301 again in 2.4.0, its ADR 0060 §4), the name, program or whole_dwelling |
recorded in the preset's Source; the name is the default preset name |
source |
program_id (equal to id), release (atlas-release-2.4.0), building_type, template, source_family, evidence (6.0.0: every row the document cites, in one shape: id, source_project, source_revision, source_locator <file>#<locator>, source_file_sha256, source_url, original_value, original_unit, transformation, extraction_date, interpretation_note) |
recorded in Source; the evidence is kept in the document, and the daylighting code rule reads the space type from the locator after # |
loads |
demand_id, type, end_use, value (null when the source does not record it), unit, basis, schedule_id; for power loads heat_fractions (lighting: radiant, visible, return air; equipment: radiant, latent, lost); for hot water target_temperature_schedule_id and inlet_temperature.method; optionally value_by_climate, schedule_id_by_climate, activity_schedule_id (occupancy), applies_with, host_zone, role |
mapped to LoadDefinitions (below) |
water_heating (filled) |
draw-only water heating: each demand's draw (a hot-water load), its heater stages, delivery_policy, and ownership |
a draw owned per_program_instance is a hot-water load; one owned instantiate_once is left out. Each per-program demand's stages (fuel, conversion efficiency, outlet) get a Left Out line: BEMGen reports the thermal demand (D-215). A cap_at_source_outlet delivery (the atlas's ADR 0018) caps the draw's target hour by hour at the last stage's outlet, the heat EnergyPlus's stand-alone WaterUse:Equipment gives with the outlet as its hot water supply temperature; Overrides records the requested target and its unmet hours (D-214) |
a load's fuel |
the fuel of gas or electric equipment | written as given; a fuel the object does not meter (GasEquipment natural gas, ElectricEquipment electricity), such as propane, is the warning ProgramJsonFuelNotApplied and a Left Out line (D-215) |
controls |
heating_enabled, cooling_enabled (null when not recorded), the setpoint schedules of the sides that are on, activity_schedule_id, the people fractions, conditioning_state, the humidity setpoint schedules |
mapped to the Thermostat and the PeopleProperties; humidity setpoints are left out |
lighting_controls |
status (known or unknown), daylighting (or null, a source zero), daylighting_by_climate, source, evidence, standards_rule (D-233) |
the program's DaylightingControls and their code rule (below) |
lighting_controls.unoccupied_lighting_cap (6.1.0, every known block) |
rule (PNNL's per-zone EMS rule, or null, a source zero whose reason is in nulls) and evidence_id |
the program's UnoccupiedLightingCap (below, D-256) |
building_service_ids |
the building services whose source objects sit in the program's zones (since release 2.4.0 also the 90.1 elevators and kitchen reach-ins) | listed in Left Out, not added (D-126, D-258) |
fenestration |
WWR N/E/S/W and the roof skylight ratio of the program's zones (null: no exterior surface that way), value_by_climate; or, for a dwelling whose ratios depend on its context (6.0.0), variants, each with its applies_with |
the preset's glazing when the inputs give none; a variant is selected by Context as the dwelling's loads are, and without the context (or with not exactly one variant applying) the JSON's fenestration is not used, with the warning ProgramJsonFenestrationContextRequired (D-239) |
a load's status, reason; nulls (6.0.0) |
whether a value is known, unknown (with its reason) or filled; the meaning of every other null by JSON pointer |
read past: a null value is handled as before (ProgramJsonValueNotRecorded); the reasons stay in the document |
lighting_controls.daylighting_origin (6.0.0, optional) |
where the daylighting fractions come from (prototype_zone_set or reference_model_scan) |
kept in the document |
dwelling |
the dwelling family, construction set, climate, vintage (5.3 minor) | recorded in Source |
schedules |
schedules by ID, each a ruleset (dated rules by day type) or annual |
the referenced ones are expanded to 8760 values |
shared_services, building_service_ids, water_use_gains, source_calendar |
services shared by several zones, building services, hot-water zone gains, the source's holiday rules and daylight saving | left out, each listed in Left Out: the holiday rules only when the calendar given has no holidays, with the warning ProgramJsonBindingNotMet when required_bindings holds holiday_calendar; a recorded daylight_saving always, since the IDF runs on standard time (D-216, D-217). Daylighting controls get a Left Out line too: carried, never written (D-174) |
source.release |
the atlas release | a release other than 2.4.0 is read with the remark ProgramJsonReleaseNotKnown (D-217, D-239, D-253) |
default_policy_id, metadata.resolutions, metadata.absent_demands (6.0.0) |
the policy, the atlas's recorded resolutions, and the demands the filled version takes out as known zeros (with their filling, such as accepted_absence) |
the counts are recorded in Source (DefaultPolicy); the resolutions, then the absent demands, are the Assumptions output (D-239) |
required_bindings |
what a consumer must bind | kept in the document; BEMGen binds the calendar through Year and Holidays, the weather file through Convert2IDF, and the rest through its geometry |
How a JSON becomes a preset¶
ProgramJsonImporter.Import(json, options) runs these steps in order; each stops the read when it finds an error, and every diagnostic carries the JSON Pointer (RFC 6901) of the node it concerns, or the name of the input for an option.
- Strict parse (
StrictJson): UTF-8 JSON, no byte-order mark, no non-finite number, no repeated member name (ProgramJsonDuplicateKey), at most 64 MiB and nesting depth 64 (ProgramJsonLimits). The checksum (Json.Sha256) is the SHA-256 of the atlas's canonical JSON of the document (D-226,CanonicalJson, atlas ADR 0057): members sorted by code point, no whitespace between tokens, strings in UTF-8 with only quote, backslash, and control characters escaped, numbers as Python'sjsonwrites them (an integer as written, any other number asreprof its double:100.0,1e-05), no trailing newline. It is the site'sjson_sha256: the site's store text, the release's pretty-printed file, a CR LF panel (Grasshopper stores text with CR LF), and a re-indented copy of one JSON have one checksum, whichpython -m scripts.site_store FILEin the atlas prints. It is not the file's own SHA-256:Fixtures/atlas/manifest.jsonpins the fixtures' bytes, as written on Windows with CR LF, while the reader gives the canonical checksum. D-128 and D-207 hashed the text with LF line endings, which differed between the site's text and the release's file. - Version:
kindmust beprogramandschema_version6.1.0(ProgramJsonVersionNotSupportedotherwise, before the schema; the message names the retired 2.0.0 when the text has anexport_mode). - Schema: validation against the embedded schema (JsonSchema.Net, Draft 2020-12). Every violation is reported with its instance path and keyword; the root
oneOfreports both branches, so one wrong value can give several lines. - Resolution: the loads are selected for the climate zone and the construction context (below), then the contract's checks and the expansion of every referenced schedule.
- Mapping to a
ProgramPresetand the overrides of the component's inputs.
Checks¶
| Code | Severity | When |
|---|---|---|
ProgramJsonInputTooLarge, ProgramJsonInvalidJson, ProgramJsonDuplicateKey |
Error | The text exceeds the size bound; is empty, has a byte-order mark, a syntax error, a non-finite number, or too deep nesting; or has a repeated member name |
ProgramJsonVersionNotSupported |
Error | Not program JSON 6.1.0, such as the 5.4.0 of releases 2.0.0 to 2.2.0 or the 6.0.0 of release 2.3.0 (the message names the program's release 2.4.0 ID, D-240, D-254) or the retired 2.0.0 |
ProgramJsonFenestrationContextRequired |
Warning | A dwelling's window ratios depend on a context the options do not give, or not exactly one variant applies; the JSON's fenestration is not used (D-239) |
ProgramJsonSchemaViolation |
Error | A rule of the schema is broken |
ProgramJsonScheduleIdMismatch |
Error | A schedule's key in schedules differs from its id |
ProgramJsonUnresolvedReference |
Error | A schedule ID used by an instantiated load or the controls is not a key of schedules |
ProgramJsonScheduleUnitMismatch |
Error | A schedule's unit does not fit its role: load schedules 1, setpoints and water targets degC, activity W/person. Units are never converted |
ProgramJsonInvalidDate |
Error | A rule's MM-DD date is not a date of any year |
ProgramJsonAnnualLength, ProgramJsonAnnualYearMismatch |
Error | An annual schedule does not hold the hours of its own year, or its year differs from the calendar's Year |
ProgramJsonHeatFractionsExceedOne |
Error | A load's heat fractions sum to more than 1 (within ToleranceSettings.AbsoluteFraction) |
ProgramJsonDuplicateDemandConflict, ProgramJsonDuplicateDemandMerged |
Error, Warning | Two instantiated loads share a demand_id and differ, or are identical (kept once) |
ProgramJsonDayNotCovered, ProgramJsonDesignDayNotCovered, ProgramJsonFebruary29Adjusted |
Error, Warning, Warning | As for every ruleset (below) |
ProgramJsonSetpointsCross |
Error | With both sides on and both setpoints given, heating exceeds cooling in some hour by more than AbsoluteSchedule |
ProgramJsonContextRequired |
Error | A load has applies_with on a context key the options do not give (such as foundation for a dwelling); the message lists the values the document uses |
ProgramJsonClimateZoneNotGiven |
Warning | A load value or schedule, the window ratios, or the daylighting differ by climate, and no climate zone was given; the general value is used, one warning per kind, and the program's climate is unresolved, which Convert2IDF refuses (D-213) |
ProgramJsonValueNotRecorded |
Warning | The source version leaves a value null: a load without a value, schedule, or water target (left out), a heat fraction (taken as 0, convective), a control switch (the side is off), a setpoint of a side that is on (the side is off), a people property (BEMGen's default), an unresolved hot-water inlet (BEMGen's 10 °C) |
ProgramJsonNotInstantiated |
Warning | An exterior lighting load (no zone), a water heating demand owned once per building, humidity setpoints, or hot-water zone gains: listed in Left Out |
ProgramJsonSharedServicesLeftOut |
Warning | The JSON has shared services |
ProgramJsonDuplicateOverrideLoad, ProgramJsonOverrideUnused |
Error, Warning | As before (overrides, below) |
The preset's own rules then apply, with the Core codes of validation.md and the JSON Pointer or input name as the subject: crossed setpoints, a schedule value outside its kind's range, an unreadable calendar, a WWR outside [0, 1) (WindowToWallRatio; an atlas ratio of 1 is refused this way), DaylightingValue, and the others. A heat fraction a load type does not have is refused by the 5.4 schema itself.
Climate zone and construction context¶
- Climate zone (Climate Zone input,
ProgramJsonImportOptions.ClimateZone, the contract's^[0-8][A-C]$, upper-cased):value_by_climate,schedule_id_by_climate,fenestration.value_by_climate, andlighting_controls.daylighting_by_climatetake the zone's entry when there is one, otherwise the general value. Without a zone the general value is used, withProgramJsonClimateZoneNotGivenwhen the document has entries; the document lists what was not resolved (ProgramJsonDocument.UnresolvedClimate), and the preset's source records it asClimateZone.Unresolved(<program ID>: the <what> (<zones>) differ by climate zone), so that Convert2IDF refuses the program (D-213, convert2idf.md). A load taken fromvalue_by_climateis marked in the document (ProgramJsonLoad.ValueByClimate), and the zone is recorded asClimateZonein the preset's source. - Construction context (Context input,
key=valueitems,ProgramJsonImportOptions.Context): a load withapplies_with(such as the IECC dwellings'{"foundation": ["crawlspace", "slab", "unheatedbsmt"]}) is instantiated when every condition's key has a context value among its values, and left out (listed in Left Out) otherwise. A key without a context value isProgramJsonContextRequired: keeping both variants would double the load and dropping both would lose it. The context is recorded asContext.
Expanding schedules¶
Unchanged from D-126: every referenced schedule becomes 8760 hourly values on the calendar of the Year input (default 2007, whose 1 January is a Monday) and the Holidays input; a leap year is an error. Rulesets use last_specific_match_else_last_default in array order; a holiday matches Hol only, else the last Default rule; 02-29 boundaries move to 1 March or 28 February with a warning; design days (WntrDsn, SmrDsn) are checked and not used, each schedule with design-day rules gets one Left Out line, and the IDF gives design days the Sunday profile. The filled release-2.0.0 schedules resolve holidays as Sundays where the source had no holiday rule (the atlas's accepted-holiday-sunday, recorded in metadata.resolutions), so the D-126 gap 2 (holidays falling to the prepended Default rule) no longer shows for the medium office's lighting and equipment.
Mapping to the program model¶
| Contract | BEMGen |
|---|---|
type occupancy, lighting, electric_equipment, gas_equipment, hot_water |
LoadType.Occupancy, Lighting, ElectricEquipment, GasEquipment, DomesticHotWater; exterior_lighting is left out |
end_use, basis |
LoadDefinition.EndUse; LoadBasis.PerFloorArea, PerDwellingUnit, PerPerson, Absolute |
value (the climate zone's when given) |
unchanged, except hot water × 3600 (m³/s to m³/h) |
schedule_id (the climate zone's when given) |
the load's fraction schedule, named after the schedule's name (or id; two schedules of one name take name [id]) |
heat_fractions |
HeatFractions; a fraction the type does not carry is 0 |
target_temperature_schedule_id |
WaterTemperatures.Target |
inlet_temperature.method CorrelationFromWeatherFile |
WaterTemperatures.InletFromWeatherFile: EnergyPlus's water mains temperature from the weather file (Site:WaterMainsTemperature, below) |
inlet_temperature.method unresolved (source version) |
BEMGen's constant 10 °C inlet, with a warning |
water_heating[].draw owned per_program_instance |
a hot-water load like any other (its heater stages are systems and are not modelled; zone gains are excluded, as the contract says) |
controls switches and setpoints |
the Thermostat sides; a switch not recorded is off; both off is an unconditioned program |
controls.activity_schedule_id, or the occupancy load's |
PeopleProperties.Activity |
| people fractions | PeopleProperties.RadiantFraction, SensibleFraction; not recorded: PeopleProperties.Default's |
lighting_controls |
ZoneProgram.Daylighting and ZoneProgram.LightingCap (below) |
fenestration |
the preset's glazing (below) |
| the calendar | ZoneProgram.Calendar and ProgramPreset.Calendar |
Demands that add. Loads of one type, end use, and basis with different demand IDs are combined by the zone-merge rule (domain-model.md), recorded as Combined.k. A combined hot-water load on the weather file's mains stays on the mains.
Daylighting (atlas handoff item 1)¶
The daylighting fractions of release 2.0.0 are plain program values (atlas user decision L-1; the 1.5.0/1.6.0 flag geometry_dependent is gone and is not read). They are applied as given and never recomputed from BEMGen's geometry:
status: knownwith adaylightingblock (or the climate zone'sdaylighting_by_climateentry) becomesDaylightingControls:controlled_lighting_fraction,illuminance_setpoint_lux,control_type(continuous,stepped,continuous_off),method(split_flux,delight),minimum_input_power_fraction,minimum_light_output_fraction,stepped_control_steps;availabilityisall_days_except_design_days.daylighting: nullwithstatus: knownis a source zero, andstatus: unknownis not recorded: neither gives controls. A program withoutlighting_controls(a dwelling) has none.controlled_floor_fraction,controlled_zones, andzonesare provenance, recorded in theDaylightingsource parameter, never an instruction to split zones.- A mix (Mix Programs) and a merged zone take each member's controls by the aggregation rule of program-presets.md: the controlled lighting power is conserved.
- Code rule (D-233, ADR-037). The controls carry the
DaylightingCodeRulethat places them when Convert2IDF's Daylighting option is on: the program's template (source.template; 90.1-2010, or 90.1-2013, 2016, and 2019, which share openstudio-standards' rule, and since D-248 the IECC editions 2006 to 2021, which take the 90.1-2019 rule by the owner's decision; any other template has none), its openstudio-standards building and space type from the evidence entry whose locator isspace_types <building type> / <space type>(for the retail and apartment exceptions), thestandards_rule'sprimary_sidelit_nongeometry_fractionandsecondary_sidelit_nongeometry_fraction(ProgramJsonStandardsRule; applied only for OfficeWholeBuildingspace types, as openstudio-standards applies them, otherwise noted as not applied), and the import's climate zone. An IECC program has nospace_typesevidence and nostandards_rule(D-248): its space type is itsnamebefore_<source.building_type>(WholeBuilding - Sm Office_SmallOffice), its building typestandards_building_type, and its office whole-building fractions openstudio-standards 90.1-2019's (NinetyOneTwentyNineteenSpaceTypes,ashrae_90_1_2019.spc_typ.jsonatc8c1b9eb).daylighting_appropriate: falsegives no rule. The preset's source records the rule asDaylightingRule(ornone: …with the reason). - By default the controls are source data and provenance: Convert2IDF writes them only when its Daylighting input is on (D-233), placed by the code rule on BEMGen's own windows and skylights; with it off, the default, daylight dimming is held fixed as part of the lighting load (D-174's rule; convert2idf.md).
controlled_lighting_fractionis never what is written. The Left Out output says so. Convert2BEM does not output the controls.
Glazing¶
The preset's WWR per orientation and skylight ratio come, in this order, from: WWR North/East/South/West (WindowToWallRatios); the glazing fixture's entry (ProgramJsonImportOptions.Glazing, the stand-in of DOE geometry note A2, kept for its tests); WWR when connected; the JSON's fenestration for the climate zone, a null direction taking 0 (no exterior wall there, so no window), recorded as … (the JSON's fenestration, program_zone_surfaces[, climate zone z], evidence e; …); and 0.4 (default, not from the JSON). The skylight ratio likewise ends with the JSON's roof, then 0. The JSON's ratios are on the true outdoor wall of the program's source zones; a generator reproduces glazed area per orientation and program (DOE geometry note S5).
Overrides¶
The inputs after JSON complete or override the JSON. Unset, the JSON's value (or the stated default) is used.
| Input | Default | Effect |
|---|---|---|
| Name | the JSON's name |
the preset name; recorded as an override |
| Space Type | unset: an any-program preset | the space type the preset applies to |
| WWR | unset: the JSON's fenestration, else 0.4 |
the window-to-wall ratio in [0, 1) on all four orientations |
| Year, Holidays | 2007, none | the calendar |
| Loads | none | each load replaces every JSON load of its type and end use (ignoring case), whatever its basis, or is added; two of one type, end use, and basis are an error |
| Heating On, Cooling On | the JSON's switches (not recorded: off) | switch each side |
| Heating Setpoint, Cooling Setpoint | the JSON's schedules | replace the JSON's; used only when that side is on |
| Activity, People Radiant, People Sensible | the JSON's (not recorded: BEMGen's defaults) | the people properties |
| WWR North … WWR West, Skylight Ratio | unset | glazing per orientation and skylight ratio (DOE geometry note S5) |
| Climate Zone | unset: the general values, which Convert2IDF refuses for a program with values by climate (D-213) | the model's ASHRAE climate zone, such as 5B (D-172) |
| Context | unset | key=value items of the construction context, such as foundation=slab (D-172) |
Outputs: Preset; Source; Assumptions, the JSON's metadata.resolutions and then its metadata.absent_demands, one compact JSON text each; Overrides; and Left Out, every part of the document the program does not hold. Errors and warnings are runtime messages.
The editor window¶
Since D-128 the component has a strip at its bottom, Edit JSON… (Edit JSON… (wired) when JSON has a wire). A double-click on the strip opens a window, an Eto dialog modal over the Grasshopper editor, with one multi-line monospaced text field prefilled with the JSON input's stored text, and OK and Cancel (also Esc). It is an additional way to give the JSON input its value; every other input, output, and behaviour of the component is unchanged.
- OK runs
ProgramJsonTextCheck.Check(src/Lod.ProgramJson/ProgramJsonTextCheck.cs): - empty or white-space text clears the stored text;
- text the reader accepts (strict parsing, the version, the schema, the resolution, the contract's checks, and the expansion of every schedule) is stored, its warnings appearing on the component when it solves;
- text the reader rejects is not stored: the window stays open and lists the errors below the text field, one per line (
Error <code> [<JSON Pointer>]: <message>).
The check expands the schedules on the component's Year and Holidays when each holds what one solution reads (one year, one list) and they make a valid calendar, else on 2007 without holidays. It resolves the loads on the component's Climate Zone when that holds one text, and on its Context when every item is a key=value pair with no key repeated, else on none (D-207), so a JSON whose loads need a construction context is accepted when the component gives one, as the solve accepts it. A wired input is read from its last solution, an unwired one from its stored values. The overrides are not part of it; the solve checks them, as before.
- Storing (ProgramJsonComponent.SetJsonText) replaces the JSON input's persistent data with the text exactly as given, records one undo event (Edit JSON), and expires the JSON input, which expires the component downstream of it, so the component collects the new text when the solution runs. Expiring only the component would keep the input's old collected data, and the component would report that JSON failed to collect data. When JSON stores several texts (Set Multiple Text), the window shows the first and says that OK replaces all of them with its one text. The stored text is saved in the definition like any internalised value; Grasshopper gives it back with CR LF line endings, which the checksum ignores.
- A wire wins. As everywhere in Grasshopper, a wire into JSON takes precedence over the stored text, which is kept and used again when the wire is removed. The window then says so above the text field. Clear values on the input removes the stored text, as OK with an empty text field does.
The window cannot be shown in a headless Rhino, so scripts/rhino-smoke/specs/program-json-editor.py checks the layout of the strip and calls the two methods OK uses (CheckJsonText, SetJsonText); a person checks the window itself (smoke test).
The atlas window (D-208)¶
The strip Get from Atlas…, above Edit JSON…, opens on a double-click a window with an address box, a web view of the Energy Archetype Atlas site, a choice of the filled (default) or source version, a status line, Use this program, and Cancel. It replaces the clipboard of D-129: BEMGen reads the page's address, not the page or the clipboard.
- The site is fixed:
AtlasSite.Root,https://energy-atlas.github.io/archetype-atlas/. The window opens on its catalogue (catalogue/) and refuses any other address (scheme, host, port, or a path outside the root), from the address box, a link, or a link that opens a new window (which opens in the same view when it is on the site), with a status line naming it. - The index. When the window opens,
AtlasSite.ReadIndexAsyncreadssite-manifest.jsonand refuses an index markedpartial(AtlasSiteIndexPartial) or of another shape (AtlasSiteIndexInvalid: areleaseother thanX.Y.Z, a page that is not a path under the root ending in/, a store path other thanstore/<sha256>.json.gz, two programs on one page). The other keys of the index and of a program are ignored. The index is kept for the Rhino session while the site's cache time, 10 minutes, holds; a failed read is not kept. - A page's program.
AtlasSiteIndex.ProgramForPagegives the program whosepageis the address's path below the root, as the server reads it (not decoded, with case; a trailingindex.html, the query, and the fragment ignored). Use this program is enabled only then. Each program's page selects Filled, except a program the site publishes only as source (the IECC programs and the code-prototype dwellings, with the site'sfilled_reason), which selects Source and says why. - Use this program runs
ProgramJsonComponent.PickFromAtlasAsync:AtlasSite.DownloadAsyncfetches the store file of the chosen version and refuses it unless the SHA-256 of its gzip bytes is the hash in its path (AtlasSiteChecksumMismatch), it decompresses to UTF-8 JSON of at most 64 MiB (AtlasSiteProgramInvalid), itsidandversionare the program and the version (AtlasSiteProgramMismatch), and itssource.releaseis the index's (AtlasSiteReleaseMismatch); a filled version the site does not publish isAtlasSiteVersionNotPublished. Then the editor window's check (CheckJsonText, on the component's calendar, climate zone, and context) runs. When it stores the text, the window closes andSetJsonTextstores it with one undo event; otherwise the status line names the program, the version, the release, and what failed. - The stored text is the store file's content byte for byte, the site's canonical JSON (compact, no line breaks). Source's
Json.Sha256is its canonical checksum, the same as that of the pretty-printed file of the release zipatlas-release-2.4.0.zip(D-226). Source also names the store file,Json.Store(<site root>store/<sha256>.json.gz (atlas site release 2.4.0)): the component keeps the last pick's store path, release, and checksum (saved in the definition) and records it only while the JSON's canonical checksum is the pick's, so an edited or rewired text with other content does not claim it. The DOE maps and the atlas mixes record each key's or member's store file the same way, against the checksum of its text as downloaded, kept in the stored text (the map's format 1.1.0; a map stored as 1.0.0 by 1.8.0 reads and records no store file). A map or mix stored from site release 2.3.0 or earlier holds program JSON 6.0.0 or 5.4.0 and that release's IDs, so the solve refuses it as a whole (AtlasSiteStoredBeforeRelease, D-240, D-254), naming each moved program's release 2.4.0 ID throughAtlasIdMap; Get from Atlas… stores it again from the site's release. - Offline. A refused connection, a timeout (30 s per request), and an HTTP error are messages on the status line (
AtlasSiteUnreachable,AtlasSiteTimedOut,AtlasSiteHttpError), never exceptions. The solve never uses the network, so a stored program solves offline. - Rate limits and parallel downloads (D-225). An answer of HTTP 429 or 5xx is asked for once more after a pause (2 s, or the server's
Retry-Afterwhen longer, at most 10 s); a 429 after that isAtlasSiteRateLimited, whose message says the site's host limits this network's downloads and to wait a few minutes. One site fetches at most four store files at once (AtlasSite.MaxConcurrentDownloads), so the DOE maps and the atlas mixes queue their programs. Every handler of the atlas windows catches any other exception into the status line. - Code.
Lod.ProgramJson.Site:AtlasSite,AtlasSiteIndex,AtlasProgramPicker(the window's logic without the window), the transportsHttpAtlasSiteTransport(the window's) andFileAtlasSiteTransport(recorded files, for the tests and the smoke check). The window isProgramJsonAtlasDialog. Pasting stays possible through Edit JSON….
The headless smoke check runs the window's two calls (AtlasPageStatusAsync, PickFromAtlasAsync, then SetJsonText) on the recorded site files of tests/Lod.ProgramJson.Tests/Fixtures/site/; a person checks the window itself (smoke test).
Atlas program maps and generation files (D-209)¶
Since the atlas's ADR 0052 the site's index gives each program its folder, mode, space_type, and building_type, and a top-level folders with each folder's sorted programs and its generation store file or null (ADR-036). AtlasSiteIndex reads them as AtlasSiteProgram.Folder, Mode, SpaceType, BuildingType, AtlasSiteIndex.Folders (AtlasSiteFolder, AtlasStoreFile), and ReleaseManifestSha256; a folder whose path is not its family, template, and building type, a folder that lists a program the index does not hold in that folder or lists one twice, a program whose folder does not list it, a generation other than a gzip store file whose sha256 is the hash of its path, with positive sizes, or one of the four program fields not a text is a fault of the folders (AtlasSiteIndex.FoldersFault, AtlasSiteIndexInvalid): the index then reads with no folders and those fields null, so the atlas window of Program JSON still works, and everything that needs the folders refuses with the fault. An index without these fields reads with no folders and no fault.
- Matching (
AtlasSiteIndex.MatchKeys(folder, keys)): a key matches the folder's one program of modesourcewhosespace_typeis the key, exactly. Department and general programs (the atlas's merged*_Mixedprograms) are never candidates. The result gives each key's program or none, and the number of candidates (0 for a gap, more for an ambiguity). - The DOE Programs components use it through
AtlasProgramMapPicker(OpenAsync: the families that hold the prototype's folder, with their coverage;PickAsync: every matched key's program downloaded in parallel withAtlasSite.DownloadAsync, each given the program check, gathered into oneAtlasProgramMap), and the solve imports the stored map offline withAtlasProgramMapImporter, which runsProgramJsonImporter.Importper key (doe-components.md). - Generation files (
AtlasSite.DownloadGenerationAsync(folder)): the folder'sgeneration.json, checked by the hash of its path, the decoded size the index gives, and a JSON object whoseprogramsnames every program of the folder;AtlasSiteGenerationNotPublishedwhere the folder has none,AtlasSiteFolderNotInIndexfor an unknown folder.AtlasGenerationPickerfinds the folder of a program JSON'sidfor Schedule Draw (schedule-draw.md).
Atlas mixes on Mix Programs (D-228)¶
The site index (site index schema 1.0.0, atlas ADR 0058) gives each folder's compositions: for each general and department mix, its members' release program IDs, represented_area_m2, and weight (the area share). AtlasSiteIndex reads them strictly (D-227): every member a source program of the folder, listed once, an area of at least 0, weights summing to 1 and equal to the area shares within ToleranceSettings.AbsoluteFraction, each mix of the folder with its composition and mode; a fault drops the compositions only (CompositionsFault), so the DOE maps and generation files keep working.
- The window (
MixAtlasDialog, logic inAtlasProgramMixPicker) lists the folders with source programs by template and building type, and a folder's starting selections (Starts): each general mix, then each department mix, with their members ticked and their area shares as weights, and a free selection of every source program, unticked, with its share of the folder's general mixes where it is a member of one (the plenum of the medium office is in none, so it has no weight until one is typed). The user unticks members and edits weights; the table shows each normalised share. - OK downloads every ticked member (filled by default, or source) with
AtlasSite.DownloadAsync(at most four at once), checks each withProgramJsonTextCheckon the component's Climate Zone, Year, Holidays, and Context, and mixes them with the stored weights as the solve does (the solve also applies the component's Name, Space Type, and WWR, which OK does not); only a mix that solve accepts is stored, as one textbemgen-atlas-program-mix1.0.0 (the site, its release and manifest checksum, the folder, the starting selection, the version, and per member its program, space type, store path, represented area, weight, text, and the text's canonical checksum as downloaded), with one undo event. Clear removes it. The starting selection and the represented areas are taken from the index, never from the window. A wire into Presets takes precedence even when it delivers nothing (a warning says so); a definition saved before D-228 reopens with Presets and Weights made optional. - The solve (
AtlasProgramMixImporter) imports each member withProgramJsonImporter.Import(an any-program preset named by its space type,Json.Storein its Source), checks that each text is the program and version it is stored as, and mixes them withProgramMix.Mix(D-124) on the stored weights or the given Weights. The mix's Source records each member's name and weight (Input.k) and nests each member's own Source, so the plan's provenance names every atlas program, checksum, and store file. - Why BEMGen mixes the members instead of taking the atlas's own
General_Mixedprogram, which Program JSON also reads: members can be unticked and reweighted, and every member stays traceable. The two can differ where the atlas's mixing differs from D-124's.
Provenance¶
The preset's Source is Provenance.Of("ProgramJson", …) with: Json.Id, Json.Name, Json.Scope, Json.Version (program JSON 6.1.0, filled|source), Json.Sha256 (canonical, D-226), Json.Store for a text taken from the atlas site; Source.ProgramId, Source.Release, Source.BuildingType, Source.Template, Source.Family; DefaultPolicy (the policy and the number of resolutions); Calendar; ClimateZone; Daylighting (the controls with their source and evidence, or why there are none); LightingCap for a block with the field (the rule, or none: <reason>, D-256); SpaceType, WindowToWallRatio, and SkylightRatio with how each was chosen; GlazingFixture when used; Context and Dwelling when present; one Override.k, Combined.k, and SharedService.k each.
Library and licences¶
Unchanged from D-126: JsonSchema.Net 7.2.3 (MIT) with its dependencies; see ADR-018.
The unoccupied lighting cap (D-256, ADR-040)¶
Program JSON 6.1.0 records PNNL's per-zone EMS rule of the 90.1-2019 prototypes on 19 programs (the offices, both restaurants, both retail types, the warehouse; the atlas's ADR 0060 section 1): while the zone's occupant count (or, for the warehouse storages, the office's) is 0 and its lighting is at least 0.02 W/ft², the zone's general lights are set to 0.02 W/ft². The rule is read as recorded (ProgramJsonLightingCap, ProgramJsonLightingCapRule) and becomes UnoccupiedLightingCap on the program:
cap_W_m2(0.21528, the cap's 0.09290304 m²/ft²) andlighting_threshold_W_m2(0.21505, the threshold's 0.093) as given;condition(zone_occupant_count_zero, orother_zone_occupant_count_zerowithcondition_zone),calling_point,additional_lighting_when_capped_W_m2(the strip mall's display lighting, 0), andzone_conditions(single source zones whose programs add a condition) are kept;comparison,action,zones,controlled_zones, andsource_valuesare provenance.rule: nullis a source zero (1,875 programs: 90.1-2007 and 2013, the other 90.1-2019 types, IECC, DOE Ref) and gives none; Source'sLightingCapgives its reason fromnulls. An unknown block and a mix have no field: a mix takes its members' caps by the merge rule (below).- Source records the rule as
LightingCap(the source values, the SI values, the condition, the calling point, the zones, and the added conditions, not applied). Left Out says that Convert2IDF writes it only when its Lighting Cap input is on (off by default) and lists each source zone's added condition as not applied: BEMGen's zones are not the source's. - A mix (Mix Programs) and a merged zone keep the cap only when every source carries the same rule, with a floor-area aggregation record; otherwise they have none, with the warning
LightingCapDiffers(D-256), since capping the merged zone would cap the lighting of a source whose program does not. - The release also corrects 11 office lighting powers to PNNL's (90.1-2019 medium office 6.889 → 6.664 W/m², the atlas's ADR 0060 section 2): read as any load.
What is left out¶
- Shared services, building services, water heating owned once per building, exterior lighting: not in a zone program; listed in Left Out. Since release 2.4.0 the building services include the 90.1 elevators and kitchen reach-ins, each listed by its ID; the building-level exterior elevators of the 90.1 hospital, large hotel, and large office are in the folder's building-services export only, which BEMGen does not read, so no program names them (D-258).
- Water heaters (the
water_heatingstages): systems, not modelled at ideal-loads grade. - Hot-water zone gains (
water_use_gains): BEMGen writes water use without zone gains. - Humidity setpoints: ideal loads control temperature only.
- The source calendar's holiday rules: give the year's holidays through Holidays (resolving the rules is open).
- Design-day profiles: checked, not used. Leap years: an error.
Tests and example¶
tests/Lod.ProgramJson.Tests/Fixtures/atlas holds twelve release 2.4.0 exports and the schema, pinned by manifest.json (since D-253 the 90.1-2019 warehouse fine storage and office, strip mall type 1, and large office for the cap's condition zone, additional lighting, and calling point; the 90.1-2019 medium office in both versions, the DOE Ref 1980–2004 medium office, the IECC 2021 small office, the 90.1-2019 stand-alone retail core with daylighting by climate, the 90.1-2019 full-service restaurant kitchen with per-program water heating, the IECC 2021 multifamily dwelling, and since D-239 the IECC 2021 single-family dwelling, whose window ratios depend on its context); synthetic/ holds the earlier synthetic fixtures rewritten in the 5.4.0 shape (D-172) and again in the 6.0.0 shape (D-239). The medium office preset is compared with hand values from its rules and a golden snapshot (Snapshots/medium-office-preset.txt); ReleaseTwoTests covers each export, the source version's nulls, the climate zone, the context, the window ratios, and daylighting. examples/program-json-office.gh still holds the retired 2.0.0 text and must be rebuilt in Rhino (open, D-172).
Contract gaps reported to the atlas¶
From D-126, still open: 1 (29 February), 3 (the vague setpoint rule), 4 (the annual schedule's allOf on rules), 5 (design-day coverage). Gap 2 is resolved by the filled version's holiday resolution. Reported with release 2.0.0 (D-172) and fixed by the atlas (D-173): the lighting_controls.daylighting description in the 5.4.0 schema said the fractions hold for the source model geometry; it now says they are applied as given (L-1).