About capability parity¶
Two libraries now answer to one name. That is the point of the unification, and
it is also its sharpest risk. Once both are called idfkit, a reader who finds
something in Python and not in JavaScript has no way to tell whether they have
met a deliberate boundary or a defect, and the reasonable assumption is defect.
The parity ledger removes the guesswork. Every public capability of either library appears on it exactly once, with its availability in both languages, and every absence is described rather than left to be discovered. A written-down absence is documentation. An undocumented one is a bug report waiting to be filed against behaviour that is working as designed.
This page is the reader-facing view of that ledger. Everything below the
generated marker comes from governance/parity.toml in the
conformance repository, read at
the immutable governance tag this release pins, so the page you are reading and
the file both libraries' CI gates read cannot disagree. For the vocabulary side
of the same governance, which name each concept carries in each language, see
the naming map.
Three states, not two¶
A capability is complete, partial, or absent in each language.
The middle state is the one that earns its keep. A capability that exists in
both languages and behaves differently is neither present nor absent, and
calling it present is precisely the lie this ledger exists to prevent. Anything
recorded as partial carries a description of what differs, because partial
with nothing said about it is indistinguishable from complete.
Two kinds of absence¶
An absence alone tells you almost nothing. What you need to know is whether to wait or to plan around it, so every absent side is recorded as one of two kinds.
- Not yet. The second language could have this and does not have it today. The entry names the issue tracking the port. Expect this to change.
- Never. The capability is permanently single-language, and the entry says
why. A permanent record is a stronger claim than a temporary one, so moving a
capability out of
nevertakes a constitutional amendment rather than an edit to the ledger.
On the page, a temporary gap is an amber notice naming a tracking issue and a
permanent one is a plain notice naming a reason. In the table at the top, the
same distinction reads as absent (not yet) against absent (never).
One entry per mechanism¶
Where the two libraries do something recognisably similar by fundamentally different means, that is two capabilities and not one.
Simulation is the case that forces the rule. Python drives a locally installed
EnergyPlus through a subprocess; JavaScript runs a WebAssembly build in the
browser. Recorded as a single simulation entry, whichever language the entry
was written from would make the other read as deficient, and every page
describing it would print a gap notice for a capability that is not missing.
Visual output splits for the same reason: a static vector image drawn once is
not a real-time rendered scene.
So local-simulation and
browser-simulation appear separately, as do
svg-visualisation and
scene-rendering. Neither pair is ever collapsed. When you
read one of them as Python-only or JavaScript-only, its partner entry is where
the other language's answer lives.
The ids are load-bearing¶
Each capability's ledger id is a permanent public identifier, and it does three
jobs at once. It is the anchor on this page, so a link to #local-simulation
keeps resolving. It is the argument the parity() macro takes on every page
that describes the capability, which is how availability reaches the point of
use instead of living only here. And it is what the gates in both libraries
match their exported surface against.
Ids get added and deprecated. They do not get renamed.
Generated from
governance/parity.toml
at governance-2026.9, the governance tag this release pins. Correct the ledger and
regenerate; a correction made on this page would be overwritten, and it would never
reach either library's CI gate.
Every capability at a glance¶
33 capabilities, counted by availability and then listed in full. Follow a capability to read what differs where the two libraries differ, and whether an absence is temporary or permanent.
| Availability | Python | JavaScript |
|---|---|---|
| complete | 28 | 12 |
| partial | 3 | 4 |
| absent, not yet | 0 | 13 |
| absent, never | 2 | 4 |
Tier 1: the shared core¶
Both libraries carry these, and both are expected to keep carrying them. A difference you meet inside this tier is either stated below or a bug, and there is no third possibility.
Reading IDF and epJSON¶
Python complete · JavaScript complete · Tier 1 · ledger id parse
Vocabulary this capability owns in the naming register
- parse IDF from a string
- parse epJSON from a string
- read IDF from disk
- read epJSON from disk
- the parse error type
- detect a document version
- an epJSON document value
- read IDF from disk, keeping diagnostics
- detect an epJSON document version
Writing IDF and epJSON¶
Python partial · JavaScript partial · Tier 1 · ledger id write
What differs, and why
Both libraries write both formats, and every document either one writes is read back by the other and by EnergyPlus. What they do NOT do is produce the same bytes. One model written from Python and from JavaScript gives two files whose object headers match and whose every field line differs, and neither is more correct than the other, so the difference is recorded here rather than resolved by changing a writer both ecosystems have already published.
Measured, not asserted. 5ZoneAirCooled.idf from EnergyPlus 26.1.0, 359 objects, through
load_idf/save_idf and through loadIdf/saveIdf, differs in seven ways:
- Python opens the file with
!-Generator idfkit v<version>and!-Option SortedOrder. TypeScript writes no header. - Python orders objects by type name, alphabetically, with Version pinned first, which is what
its
SortedOrderheader declares. TypeScript groups objects by type in the order the types first appeared in the source, with Version pinned first. - Python indents field lines two spaces. TypeScript indents four, and takes an
indentoption. - Both align the
!-comment at column 30. They therefore overflow at different values, and on overflow Python writes the comment flush against the comma while TypeScript keeps one space. - Python renders a float with
%g, so an integral value loses its decimal point:30.in the source comes back as30, and0.0as0. TypeScript consults the schema and writes30.0and0.0, because JavaScript has one number type and a real-valued field would otherwise be indistinguishable from an integer one on the way out. - Python title-cases every word of the field-name comment,
!- Number Of Verticesand!- View Factor To Ground. TypeScript keeps a list of minor words lowercase so the comment reads as EnergyPlus writes it,!- Number of Verticesand!- View Factor to Ground. Both drop the unit suffix the source carries,{deg}and the rest. - Python numbers the comment on each repeat of an extensible group,
!- Vertex X Coordinate 2. TypeScript repeats the unnumbered name on every repeat.
Blank lines differ with them: Python puts one between every pair of objects, TypeScript one between objects of the same type and two at each type boundary. On this file that is 4031 lines against 4125, for the same 359 objects.
partial on BOTH sides, because neither writer's controls contain the other's. Python's write_idf
takes output_type, so "nocomment" and "compressed", and preserve_formatting; it offers no way
to set the indent, the comment column, or the ordering. TypeScript's writeIdf takes comments,
commentColumn, indent, and versionFirst; it has no compressed mode and no lossless mode, the
second of which is lossless-round-trip rather than part of this entry.
What is proven, and by what. The corpus asserts that a document survives its OWN writer without
structural loss: assertion 3 re-parses each library's IDF output and compares object types, names,
field names, field values, and field order against the document it started from. It does not compare
the text, and runners/compare.md forbids any comparator from ever doing so, because one JSON value
has many JSON texts. So the seven differences above are outside every assertion the corpus runs, and
recording them here is the only place a reader meets them. A reader who needs byte-identical output
from both languages does not have it and is not going to: pass EnergyPlus the model, not a diff.
Vocabulary this capability owns in the naming register
- serialize IDF to a string
- serialize epJSON to a string
- write IDF to disk
- write epJSON to disk
- serialize a document to an epJSON value
- serialize one object
Documents, collections, and objects¶
Python complete · JavaScript complete · Tier 1 · ledger id document-model
Vocabulary this capability owns in the naming register
- the document class
- the collection class
- the object class
- an extensible group
- create a new document
- add an object
- remove an object
- rename an object
- every object of a type
- untyped collection access
- one object, or nothing
- one object, or an error
- collection to a sequence
- an object's type name
- version
Reference graph¶
Python complete · JavaScript complete · Tier 1 · ledger id references
Vocabulary this capability owns in the naming register
- the reference graph
- objects referencing a name
- a reference edge
Schema access and the version registry¶
Python complete · JavaScript complete · Tier 1 · ledger id schema-access
Vocabulary this capability owns in the naming register
- load a schema for a version
- the supported version list
- resolve a version string
- one version's schema
- the difference between two schema versions
- resolve the schema for a detected version
- the process-wide schema source
Model validation¶
Python complete · JavaScript complete · Tier 1 · ledger id validation
Vocabulary this capability owns in the naming register
- validate a document
- validate an object
- validation result
- validation finding
- validation severity
Describing an object type from the schema¶
Python complete · JavaScript partial · Tier 1 · ledger id introspection
What differs, and why
TypeScript never populates memo or note. Both are members of the two types, so the field set
matches, but they are always undefined. Python fills memo for 845 of 858 object types and note
for 6,212 of 12,712 fields in 26.1.0, both from epJSON keys that @idfkit/schemas drops on purpose
to keep the bundle off the parse critical path. The @idfkit/schemas/docs subpath its own header
comment promises does not exist. Describing a type is what a REPL, a notebook, and an LSP hover are
for, so the prose is most of the value; this is partial rather than complete.
Two further differences, both small and both pinned by tests. enumValues omits the empty string
that Python includes for 1,378 of its 2,293 enum-bearing fields, and omits the sentinel lists
(Autosize, Autocalculate) Python recovers from an anyOf branch for 769 fields. Field ORDER
differs for exactly two types in 26.1.0, ZoneProperty:UserViewFactors:BySurfaceName and
ZoneTerminalUnitList, and for six more in 8.9.0 through 9.2.0, because the bundle sorts property
keys for content-addressing and those types carry no positional field list to restore declaration
order from.
Vocabulary this capability owns in the naming register
- describe an object type
- object description
- field description
Building EnergyPlus documentation URLs¶
Python complete · JavaScript complete · Tier 1 · ledger id documentation-urls
Vocabulary this capability owns in the naming register
- resolved documentation URL
- documentation URL for an object type
- I/O reference URL
- engineering reference URL
- documentation search URL
Static types generated from the schema¶
Python partial · JavaScript complete · Tier 1 · ledger id generated-object-types
What differs, and why
Coverage differs, and the difference is visible to anyone not on the newest release. The Python stub set is generated for one EnergyPlus version at a time and is currently shipped for 26.1.0 only, so a model loaded at 9.4 gets the 26.1 field names and choice lists from the editor. The TypeScript type maps are emitted per version and selected by the caller, who parameterises the document with the map for the version being read, so an older model is typed as that older model or is left untyped rather than mistyped.
Application differs too. Python's stubs apply implicitly to every document. TypeScript's are opt-in by construction: an unparameterised document stays untyped, which is what version-generic code needs.
Vocabulary this capability owns in the naming register
- generated object types
- a version type map
Diagnostics from a parse¶
Python partial · JavaScript complete · Tier 1 · ledger id parse-diagnostics
What differs, and why
Both libraries produce diagnostics for a malformed input; only one hands them back. Python raises
IDFParseError carrying the diagnostics that stopped the parse, and reports the recoverable ones
(skipped malformed objects, discarded formatting trees, surplus fields on a non-extensible type)
through the logging module, where a caller who wants them must install a handler. TypeScript
returns them: parseIdf and loadIdfWithDiagnostics both yield a ParseResult whose diagnostics
array holds the non-fatal findings alongside the document.
This matters to the conformance corpus, whose diagnostics assertion compares what each side
reports for a malformed case (contracts/conformance-corpus.md).
Vocabulary this capability owns in the naming register
- a parse diagnostic
- diagnostics from a parse
The weather station index¶
Python complete · JavaScript partial · Tier 1 · ledger id weather-index
What differs, and why
Installation differs, and the difference is deliberate. pip install idfkit installs weather and
its station index unconditionally, because Python extras gate dependencies rather than files, so a
Python reader has weather whether or not they wanted it. npm install idfkit installs neither:
weather is an opt-in peer there, added with npm install @idfkit/weather. Both libraries ship
their own index once installed and neither retrieves one to get started (FR-043, FR-075,
research R11). A JavaScript reader who follows a weather page without installing that package
gets a resolution error, not a smaller feature, which is why the packaging is recorded here as a
stated difference rather than left as an undescribed detail.
Freshness handling differs. Python fires a throttled nudge from StationIndex.load(): at most once every 24 hours it probes the upstream KML files, warns when the bundled or cached index is behind, records the check under the cache directory, and can be turned off with IDFKIT_NO_WEATHER_UPDATE_CHECK. JavaScript has no such nudge and no timestamp to throttle against. It exposes checkForUpdates and refreshStationIndex for a caller who asks, and does nothing on its own, because the nudge is built on a writable cache directory and a browser-targeted package has none. The consequence for a reader is concrete: a stale index goes unmentioned in JavaScript until they check for themselves.
The installation difference above is the whole weather surface's, not the index's alone. It is
stated here because the index is where a reader meets it first, and weather-download and
weather-file-cache refer back to it rather than repeat it.
Vocabulary this capability owns in the naming register
- the station index
- search stations
- a weather station
- refresh the station index
- a text search result
- a proximity search result
- which field a text search matched
- the station wire record
- the upstream index base URL
- fetch a prebuilt station index
- parse a KML station index
- read station metadata from a download URL
- great-circle distance between two points
- the weather options-object types
- the station index wire form
- build an index from index data
- the upstream index file list
- the bundled station index
- check the station index for updates
Retrieving weather and design-day files¶
Python complete · JavaScript partial · Tier 1 · ledger id weather-download
What differs, and why
Both libraries retrieve a station's ZIP archive from climate.onebuilding.org and unpack the EPW,
DDY, and STAT members out of it. What they hand back differs, and it differs in the first line a
reader writes. Python's WeatherDownloader.download returns a WeatherFiles whose epw, ddy, stat, and
zip_path are Path objects, because the files are on disk by the time it returns and a path is what
EnergyPlus is given. TypeScript's fetchWeatherFiles returns a WeatherFiles whose epw, ddy, and stat
are the file TEXT, alongside a members map holding every archive member as bytes, because a browser
has no disk and the text is what @idfkit/engine takes. Same name, different values, which the
naming register records under the retrieved weather files: code written from one language's
documentation is wrong at runtime against the other rather than merely awkward.
Three things exist on one side only, and each follows from that one fact rather than from a gap. Python's download(station, only={".epw"}) extracts a chosen subset and returns PartialWeatherFiles; selective extraction is a property of writing into a cache, and the JavaScript side decodes from memory whatever the archive held. TypeScript's FetchWeatherOptions carries fetch, rewriteUrl, and signal, because climate.onebuilding.org sends no Access-Control-Allow-Origin header and a page can reach it only through a proxy the caller supplies; Python runs under no same-origin policy and owns its own urllib requests. TypeScript splits retrieving from writing, so @idfkit/weather/node adds saveWeatherFiles and SavedWeatherFiles, where in Python the two are one operation.
Resolving a canonical EPW filename differs in one argument. Python's index parameter is optional and defaults to the bundled index, because it can always find one on disk. TypeScript's is required, because the caller had to obtain an index already and the function has nowhere to load one from.
typescript = "partial" records the missing selective extraction and nothing else. It is not a
verdict on the on-disk cache: that is weather-file-cache, which is permanently absent by decision
rather than missing.
Vocabulary this capability owns in the naming register
- download a weather file
- download a station's EPW file
- download an EPW file by filename
- download a station's archive
- the retrieved weather files
- read a ZIP archive
- the injectable fetch
- write weather files to disk
- the written weather file paths
Declaring the conformance level a release passes¶
Python complete · JavaScript complete · Tier 1 · ledger id conformance-declaration
Vocabulary this capability owns in the naming register
- declared conformance level
Geocoding a place name¶
Python complete · JavaScript complete · Tier 1 · ledger id geocoding
Vocabulary this capability owns in the naming register
- geocode a place name
- detect the current location
- the geocoding rate limiter
- a geocoding failure
Tier 2: portable, not ported yet¶
Capabilities the second language could have and does not have today. Every entry carries a tracking issue, because recording any of them as permanent would claim more than the code supports.
Formatting-preserving round-trip¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id lossless-round-trip
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#12.
Vocabulary this capability owns in the naming register
- preserve formatting on a round-trip
Reading geometry from a model¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id geometry-extraction
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#13.
Vocabulary this capability owns in the naming register
- three-dimensional vector
- three-dimensional polygon
- read a surface's coordinates
- a zone's origin
- a zone's rotation
- transform relative coordinates to world coordinates
- a surface's area
- a surface's tilt
- a surface's azimuth
- a zone's floor area
- a zone's volume
Building and transforming geometry¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id geometry-authoring
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#14.
Vocabulary this capability owns in the naming register
- set the window to wall ratio
- rotate a building
- translate a building
- scale a building
- add a shading block
- set default constructions
Intersecting and matching surfaces¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id surface-matching
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#15.
Vocabulary this capability owns in the naming register
- intersect and match surfaces
- a surface match report
Generating zoned blocks from a footprint¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id zoning
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#16.
Vocabulary this capability owns in the naming register
- create a zoned block
- a zone footprint
- a zoning scheme
- link blocks
Construction thermal properties¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id thermal-properties
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#17.
Vocabulary this capability owns in the naming register
- construction thermal properties
- a construction's U-value
- a construction's R-value
- a construction's solar heat gain coefficient
- a construction's layers
Schedule evaluation¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id schedules
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#18.
Vocabulary this capability owns in the naming register
- evaluate a schedule
- a schedule's values for a year
- a schedule as a series
- create a constant schedule
- create a compact schedule
- extract special days
- the holidays in a model
Reading the output variable dictionary and selecting variables¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id output-variable-selection
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#19.
Vocabulary this capability owns in the naming register
- read the output variable dictionary
- an output variable
- an output meter
- select output variables for a run
Design days and ASHRAE sizing conditions¶
Python complete · JavaScript absent (not yet) · Tier 2 · ledger id design-day-sizing
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#20.
Vocabulary this capability owns in the naming register
- apply ASHRAE sizing conditions
- the design day manager
Tier 3: tooling and Node-bound capabilities¶
Absent from JavaScript today and not permanently so. Each one is reachable in Node, which is why none of them is recorded as permanent.
Forward-migrating a model between EnergyPlus versions¶
Python complete · JavaScript absent (not yet) · Tier 3 · ledger id version-migration
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#21.
Vocabulary this capability owns in the naming register
- migrate a model to a newer version
- a migration report
- a migration step
Command-line interface¶
Python complete · JavaScript absent (not yet) · Tier 3 · ledger id command-line
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#22.
Vocabulary this capability owns in the naming register
- the command line entry point
Checking source against another EnergyPlus version's schema¶
Python complete · JavaScript absent (not yet) · Tier 3 · ledger id schema-compatibility-check
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#23.
Vocabulary this capability owns in the naming register
- check source compatibility with a version
- diff two schemas
- a compatibility diagnostic
Plotting simulation results¶
Python complete · JavaScript absent (not yet) · Tier 3 · ledger id result-plotting
Not in JavaScript yet
A temporary gap, not a boundary. The port is tracked in idfkit-js#24.
Vocabulary this capability owns in the naming register
- plot an energy balance
- plot a temperature profile
- plot comfort hours
- a plotting backend
Permanently single-language¶
Pairs of capabilities that were never one capability. Neither library is missing anything here, which is why these entries sit apart from the gaps above: each pair is two different mechanisms serving two different runtimes.
Running a locally installed EnergyPlus and reading its results¶
Python complete · JavaScript absent (never) · Permanently single-language · ledger id local-simulation
Python only, permanently
Requires an EnergyPlus installation on the machine and a subprocess to drive it, then reads the
eplusout files that run leaves on disk. Neither the installation nor the subprocess is available in
a browser, which is the runtime the JavaScript library targets. JavaScript reaches EnergyPlus by
the other mechanism instead: see browser-simulation, which is not a workaround for this entry but
a different capability.
Vocabulary this capability owns in the naming register
- the local simulation surface
Running EnergyPlus in the browser¶
Python absent (never) · JavaScript complete · Permanently single-language · ledger id browser-simulation
JavaScript only, permanently
Delivered by @idfkit/engine, installed separately and deliberately not part of the shared install name: not a subpath, not a dependency, not an optional peer (research R19). The WebAssembly build exists to reach a runtime Python does not target, so Python has no counterpart and is not getting one. @idfkit/engine-assets is roughly 51 MB and versions on the EnergyPlus release it carries, while the loader versions on its own API, which is a second reason the facade does not carry it.
This is not a gap in Python. Python runs EnergyPlus by the other mechanism: see
local-simulation.
Rendering a model to a vector image¶
Python complete · JavaScript absent (never) · Permanently single-language · ledger id svg-visualisation
Python only, permanently
A Python-side capability with no JavaScript counterpart and no plan for one. The output is a static
vector image produced for a notebook, a report, or a file on disk, which is a Python workflow. A
JavaScript caller wanting to look at a model is already in a runtime that draws, and is served by
the other mechanism: see scene-rendering. Recording the two as one visualization entry would
make each language read as missing something the other has, which is exactly the failure FR-078
and FR-068 forbid.
Vocabulary this capability owns in the naming register
- the vector image surface
Rendering a three-dimensional scene¶
Python absent (never) · JavaScript complete · Permanently single-language · ledger id scene-rendering
JavaScript only, permanently
Delivered by @idfkit/viewer, installed by its own name like the simulation engine, and NOT reachable through the shared install name: no subpath, no dependency, no export-map entry, and no reserved name in the naming register. A reader reaches it by installing it explicitly, and the documentation says so rather than implying the facade carries it.
Python has no counterpart and is not getting one. A real-time interactive scene needs a rendering
context that a Python process does not have, and Python's static vector output is a different
mechanism serving a different workflow: see svg-visualisation. Neither is a gap in the other.
eppy compatibility surface¶
Python complete · JavaScript absent (never) · Permanently single-language · ledger id eppy-compatibility
Python only, permanently
A Python ecosystem concern with no JavaScript counterpart. These methods exist solely so that code written against eppy keeps working after a move to idfkit; every one of them names an idfkit alternative in its own docstring. There is no eppy in JavaScript, so there is nothing to be compatible with. Listed explicitly so it is never ported by mistake.
Vocabulary this capability owns in the naming register
- eppy compatibility surface
Caching retrieved weather files on disk¶
Python complete · JavaScript absent (never) · Permanently single-language · ledger id weather-file-cache
Python only, permanently
FR-031, stated as a decision rather than a backlog item: the on-disk cache for retrieved weather and
design-day files is Python's, and JavaScript MUST NOT grow one. Moving this entry out of never
needs a constitutional amendment, not an edit here.
What Python has. WeatherDownloader owns a cache directory, per platform by default and moved with cache_dir or IDFKIT_CACHE_DIR. download() looks in it first and reaches the network only on a miss or past max_age, so the second call for a station costs nothing; clear_cache empties it. Everything downstream follows: the record it returns is paths rather than text, zip_path addresses the archive it kept, and selective extraction through only= is meaningful because a suffix skipped this time may already be sitting there from an earlier call.
Why JavaScript will not have one. @idfkit/weather targets a browser, a worker, and an edge runtime as well as Node. Two of those have no directory it could own, and picking one in Node would make the package behave differently depending on where it runs, which is the one thing a portable package must not do. So it fetches every time and returns text the caller keeps. saveWeatherFiles in @idfkit/weather/node writes a retrieved bundle where the caller says, and that is a caller-directed write rather than a cache: it consults nothing on the way in and remembers nothing on the way out.
This is not a gap in JavaScript, and it is why several one-sided names exist on each side. The
station index is shipped, not cached, in both languages (FR-043, FR-075), so nothing here changes
how either library finds a station: that is weather-index. Retrieval itself is present in both
languages: that is weather-download.
Vocabulary this capability owns in the naming register
- the weather file cache