About the naming map¶
idfkit is one toolkit published as two libraries. The Python package and the JavaScript package read the same EnergyPlus models, and the promise of the pair is that you learn the vocabulary once and spend it in either language. This page is the map between the two spellings. Hand it an operation you already know in one library and it gives you the counterpart in the other, or tells you plainly that there isn't one and why.
It explains the vocabulary rather than documenting it. The API reference says what a function takes and returns; the map says which function to look up, and why its name is the shape it is. Read it once and most lookups afterwards are guesses you barely need to check.
Every name, every difference, and every reason below the generated marker comes
from governance/naming.toml in the
conformance repository, read at
the immutable governance tag this release pins. That same file is what the
naming gate in both repositories reads on every pull request, so the page you
are reading and the check that guards the libraries cannot disagree. For the
capability side of the same governance, which library has what, see
the parity ledger.
One concept, one entry¶
The register records concepts, not names. Each concept appears exactly once, and each entry says how each language spells it. That shape is what makes the map usable in both directions and what makes a missing counterpart visible instead of merely absent.
Every entry is one of three kinds, and the kind tells you how much to trust your instincts:
- Aligned. The two spellings say the same word, differing only by the casing convention of each ecosystem. Guess and you will be right.
- Divergent. The two spellings differ by more than casing, or the same spelling means something different. Every divergent entry carries the reason, and the reason always says why each side is correct where it lives. None of them is a defect awaiting a fix.
- Excluded. The surface exists in one language and will not cross. Excluded is terminal, not a queue: a counterpart appearing in the other language fails the gate.
Exactly one public name per concept per language, too. A second public spelling of one operation is the thing that makes a reader wonder which one is the real one, so the gate fails on it.
Names are decided before they are written¶
An entry may name something neither library has yet, and that is not an oversight: it is the point. A name decided while a port is underway is decided by one language, under schedule pressure, by whoever got there first. A name decided in the register is decided by both languages, in the open, before any code depends on it.
Registering a name costs nothing on the day it is written down and saves a breaking change later. So the register runs ahead of both libraries, and the notes say which names are reserved rather than callable.
The same reasoning gives every name a budget of exactly one rename. Renames below are spent budgets, not plans.
What the map does not cover¶
Behaviour. Two libraries can agree perfectly on a name and disagree about what it returns. The conformance corpus is what holds behaviour together, and where the corpus and the register disagree neither wins silently: the disagreement blocks the merge and is resolved by amending whichever is wrong.
Whether a capability exists at all. The map answers "what is this called over there". The parity ledger answers "is it over there at all, and if not, is that for now or for good".
The single-language packages. Two JavaScript-only packages, @idfkit/engine
and @idfkit/viewer, have no entry here. Neither is reachable through the
shared install name and neither is a capability the other language is missing,
so neither belongs on a map of shared vocabulary. Both appear on the parity
ledger, where permanent single-language capabilities are recorded as such.
Generated from
governance/naming.toml
at governance-2026.22, the governance tag this release pins. It governs idfkit and
@idfkit/core and @idfkit/geometry and @idfkit/weather and @idfkit/language, and
it is read at a pinned governance-YYYY.N tag of idfkit-conformance, never the default
branch. Correct the register and regenerate; a correction made on this page would be
overwritten, and it would never reach either library's naming gate.
Guessing a name before you look it up¶
Most counterparts are derivable, and the quickest way to use this page is to guess first and confirm second.
Convert the casing. Python spells a member in snake_case and JavaScript spells it in
camelCase. Converting one to the other mechanically gives the right name for the large
majority of the register: create_compact_schedule_from_values becomes
createCompactScheduleFromValues, with nothing else changed.
Expect acronyms to differ in type names. IDFObject in Python is IdfObject in
TypeScript, and the same split runs through every type built on the abbreviation, so the
document class is IDFDocument against IdfDocument. PEP 8 capitalises a whole acronym
inside a CapWords name; the Google TypeScript style guide treats an abbreviation as an
ordinary word. Each is right where it lives, and neither is a candidate for renaming to
match the other.
Expect an abbreviation inside a camelCase name to become one word. The rule that
gives IdfObject also gives setWwr for Python's set_wwr, and calculateShgc for
calculate_shgc.
Read the verb first. One operation carries one verb in both languages, so the verb tells you what a call does to your disk before you have read the rest of the name.
Where the guess fails it fails in one of the ways listed under where the two libraries differ, and each of those is a difference with a reason rather than an oversight. A guess that appears neither in the map nor in that list does not exist: the gate fails on any public name the register cannot resolve, which is what makes the map complete rather than merely long.
The verb vocabulary¶
These verbs are fixed. A capability that needs one of these operations spells it with the verb below, in the casing of its own ecosystem, and a capability that does something else does not borrow one of them.
| Verb | What it means |
|---|---|
parse |
Read a model out of text already in memory. Never touches a disk. |
load |
Read a model from a path. The disk-reading counterpart of parse. |
write |
Serialize a model to text. Returns the text, never writes it. |
save |
Serialize a model and put it on disk. The disk-writing counterpart of write. |
add |
Create an object and attach it to the document. |
remove |
Detach an object from the document. |
rename |
Change an object's name and repoint every reference to it. |
validate |
Check a model or an object against its schema and report findings. |
The pair worth the most attention is write against save. Both libraries draw the
line in the same place: write hands you text and never touches your disk, save puts
it on disk. parse against load is the same line drawn on the way in.
The map¶
The map follows the register's own order, which groups related operations together. A row marked divergent or excluded links to the entry that says why, and a cell reading absent means the operation genuinely has no counterpart in that language.
Parsing, reading, writing, saving¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| parse IDF from a string | parse_idf |
parseIdf |
aligned |
| parse epJSON from a string | parse_epjson |
parseEpJson |
aligned |
| read IDF from disk | load_idf |
loadIdf |
aligned |
| read epJSON from disk | load_epjson |
loadEpJson |
aligned |
| serialize IDF to a string | write_idf |
writeIdf |
aligned |
| write IDF to disk | save_idf |
saveIdf |
aligned |
| serialize epJSON to a string | write_epjson |
writeEpJson |
aligned |
| write epJSON to disk | save_epjson |
saveEpJson |
aligned |
Editing a document¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| add an object | add |
add |
aligned |
| remove an object | remove |
remove |
aligned |
| rename an object | rename |
rename |
aligned |
Validation (registered ahead of the TypeScript implementation)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| validate a document | validate_document |
validateDocument |
aligned |
| validate an object | validate_object |
validateObject |
aligned |
| validation result | ValidationResult |
ValidationResult |
aligned |
| validation finding | ValidationError |
ValidationError |
aligned |
| validation severity | idfkit.validation.Severity |
Severity |
aligned |
Introspection (registered ahead of the TypeScript implementation)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| describe an object type | idfkit.introspection.describe_object_type |
describeObjectType |
aligned |
| object description | ObjectDescription |
ObjectDescription |
aligned |
| field description | FieldDescription |
FieldDescription |
aligned |
Documentation URL builders (registered ahead of the TypeScript port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| resolved documentation URL | DocsUrl |
DocsUrl |
aligned |
| documentation URL for an object type | docs_url_for_object |
docsUrlForObject |
aligned |
| I/O reference URL | io_reference_url |
ioReferenceUrl |
aligned |
| engineering reference URL | engineering_reference_url |
engineeringReferenceUrl |
aligned |
| documentation search URL | search_url |
searchUrl |
aligned |
The declared conformance level¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| declared conformance level | idfkit.CONFORMANCE_LEVEL |
CONFORMANCE_LEVEL |
aligned |
Accepted divergences¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| acronym casing | IDFObject |
IdfObject |
divergent |
| the document class | IDFDocument |
IdfDocument |
divergent |
| the collection class | IDFCollection |
IdfCollection |
divergent |
| the parse error type | IDFParseError |
IdfParseError |
divergent |
| every object of a type | doc["Zone"] |
doc.all('Zone') |
divergent |
| untyped collection access | IDFDocument.get_collection |
absent | excluded |
| one object, or nothing | col.get(name) |
col.get(name) |
aligned |
| one object, or an error | col[name] |
col.require(name) |
divergent |
| collection to a sequence | to_list() |
toArray() |
divergent |
| an object's type name | obj.obj_type |
obj.typeName |
divergent |
| version | doc.version |
doc.version |
divergent |
| all input and output | synchronous |
asynchronous |
divergent |
Excluded from alignment¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| eppy compatibility surface | 15 names | absent | excluded |
Geometry extraction, registered before the second-tier port begins¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| three-dimensional vector | Vector3D |
Vector3D |
aligned |
| three-dimensional polygon | Polygon3D |
Polygon3D |
aligned |
| read a surface's coordinates | get_surface_coords |
getSurfaceCoords |
aligned |
| a zone's origin | get_zone_origin |
getZoneOrigin |
aligned |
| a zone's rotation | get_zone_rotation |
getZoneRotation |
aligned |
| transform relative coordinates to world coordinates | translate_to_world |
translateToWorld |
aligned |
| a surface's area | calculate_surface_area |
calculateSurfaceArea |
aligned |
| a surface's tilt | calculate_surface_tilt |
calculateSurfaceTilt |
aligned |
| a surface's azimuth | calculate_surface_azimuth |
calculateSurfaceAzimuth |
aligned |
The object model¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the object class | IDFObject |
IdfObject |
divergent |
| an extensible group | idfkit.objects.ExtensibleGroup |
ExtensibleGroup |
aligned |
| an object's extensible rows | obj.<extensible key> |
obj.extensible |
divergent |
| a view over an object's extensible rows | idfkit.objects.ExtensibleList |
absent | divergent |
| create a new document | new_document |
new IdfDocument(schema) |
divergent |
The reference graph¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the reference graph | ReferenceGraph |
ReferenceGraph |
aligned |
| objects referencing a name | get_referencing |
referencingObjects |
divergent |
Schema access and the version registry¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| load a schema for a version | get_schema |
SchemaBundle.load |
divergent |
| load the schema prose | absent | SchemaBundle.loadProse |
divergent |
| the schema prose already loaded | absent | SchemaBundle.prose |
divergent |
| the supported version list | ENERGYPLUS_VERSIONS |
SchemaBundle.versions |
divergent |
| resolve a version string | find_closest_version |
resolveVersion |
divergent |
Static types generated from the schema¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| generated object types | idfkit._generated_types |
@idfkit/types-v26-1 |
divergent |
| a version type map | absent | AnyTypeMap |
divergent |
Diagnostics from a parse¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a parse diagnostic | idfkit.exceptions.ParseDiagnostic |
ParseDiagnostic |
aligned |
| a document and its parse findings | ParseResult |
ParseResult |
aligned |
| diagnostics from a parse | IDFParseError.diagnostics |
IdfParseError.diagnostics |
aligned |
| recoverable diagnostics from a parse | ParseResult.diagnostics |
ParseResult.diagnostics |
aligned |
Weather stations, files, and geocoding¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the station index | StationIndex |
StationIndex |
aligned |
| search stations | StationIndex.search |
StationIndex.search |
aligned |
| a weather station | WeatherStation |
WeatherStation |
aligned |
| download a weather file | WeatherDownloader.download |
fetchWeatherFiles |
divergent |
| refresh the station index | absent | refreshStationIndex |
divergent |
| geocode a place name | geocode |
geocode |
aligned |
| detect the current location | detect_location |
detectLocation |
aligned |
The rest of the weather surface¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a text search result | SearchResult |
SearchResult |
aligned |
| a proximity search result | SpatialResult |
SpatialResult |
aligned |
| which field a text search matched | absent | MatchField |
divergent |
| the station wire record | absent | StationRecord |
divergent |
| the station index wire form | absent | IndexData |
divergent |
Building the station index, and keeping it fresh¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| great-circle distance between two points | haversine_km |
haversineKm |
aligned |
| parse a KML station index | absent | parseKml |
divergent |
| read station metadata from a download URL | absent | parseUrlMetadata |
divergent |
| check the station index for updates | StationIndex.check_for_updates |
checkForUpdates |
aligned |
| build an index from index data | absent | indexFromData |
divergent |
| the upstream index file list | absent | INDEX_FILES |
divergent |
| the upstream index base URL | absent | SOURCES_BASE_URL |
divergent |
| fetch a prebuilt station index | absent | loadStationIndex |
divergent |
| the bundled station index | StationIndex.load |
loadBundledIndex |
divergent |
Retrieving the files, and the disk edge¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| download a station's EPW file | WeatherDownloader.get_epw |
fetchEpw |
divergent |
| download an EPW file by filename | WeatherDownloader.get_epw_by_filename |
fetchEpwByFilename |
divergent |
| download a station's DDY file | WeatherDownloader.get_ddy |
absent | divergent |
| download a DDY file by filename | WeatherDownloader.get_ddy_by_filename |
absent | divergent |
| download a station's archive | absent | fetchWeatherArchive |
divergent |
| the retrieved weather files | WeatherFiles |
WeatherFiles |
divergent |
| read a ZIP archive | absent | unzip |
divergent |
| the weather file cache | 2 names | absent | excluded |
Geocoding, beside geocode a place name and detect the current location¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a geocoding failure | GeocodingError |
GeocodingError |
aligned |
| the geocoding rate limiter | RateLimiter |
RateLimiter |
aligned |
| the injectable fetch | absent | FetchLike |
divergent |
| write weather files to disk | absent | saveWeatherFiles |
divergent |
| the written weather file paths | absent | SavedWeatherFiles |
divergent |
Reading a weather file (007-weather-file-readers)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| filter stations by metadata | StationIndex.filter |
StationIndex.filter |
aligned |
| read a weather file from text | parse_epw |
parseEpw |
aligned |
| read a weather file from a source | load_epw |
loadEpw |
aligned |
| the weather file a reader returns | WeatherFile |
WeatherFile |
aligned |
| monthly aggregates of a weather file | monthly_means |
monthlyMeans |
aligned |
The weather options-object types¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the weather options-object types | absent | 10 names | excluded |
The formatting-preserving round-trip (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| preserve formatting on a round-trip | preserve_formatting |
preserveFormatting |
aligned |
Zone measures, with the geometry extraction group above¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a zone's floor area | calculate_zone_floor_area |
calculateZoneFloorArea |
aligned |
| a zone's volume | calculate_zone_volume |
calculateZoneVolume |
aligned |
The scene description, registered before either language implements it¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a model's resolved geometry | get_scene |
getScene |
aligned |
| the resolved scene | Scene |
Scene |
aligned |
| a resolved surface | ResolvedSurface |
ResolvedSurface |
aligned |
| the rules resolution applied | AppliedRules |
AppliedRules |
aligned |
| the scene's extent | SceneBounds |
SceneBounds |
aligned |
| a surface that could not be resolved | UnresolvedObject |
UnresolvedObject |
aligned |
| a geometry type the reader did not attempt | UnattemptedType |
UnattemptedType |
aligned |
Geometry authoring (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| set the window to wall ratio | set_wwr |
setWwr |
aligned |
| rotate a building | rotate_building |
rotateBuilding |
aligned |
| translate a building | translate_building |
translateBuilding |
aligned |
| scale a building | scale_building |
scaleBuilding |
aligned |
| add a shading block | add_shading_block |
addShadingBlock |
aligned |
| set default constructions | set_default_constructions |
setDefaultConstructions |
aligned |
Surface matching (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| intersect and match surfaces | intersect_and_match |
intersectAndMatch |
aligned |
| a surface match report | MatchReport |
MatchReport |
aligned |
Zoning (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| create a zoned block | create_block |
createBlock |
aligned |
| a zone footprint | ZoneFootprint |
ZoneFootprint |
aligned |
| a zoning scheme | ZoningScheme |
ZoningScheme |
aligned |
| link blocks | link_blocks |
linkBlocks |
aligned |
Construction thermal properties (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| construction thermal properties | ConstructionThermalProperties |
ConstructionThermalProperties |
aligned |
| a construction's U-value | calculate_u_value |
calculateUValue |
aligned |
| a construction's R-value | calculate_r_value |
calculateRValue |
aligned |
| a construction's solar heat gain coefficient | calculate_shgc |
calculateShgc |
aligned |
| a construction's layers | get_construction_layers |
getConstructionLayers |
aligned |
Schedules (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| evaluate a schedule | idfkit.schedules.evaluate |
evaluate |
aligned |
| a schedule's values for a year | idfkit.schedules.values |
values |
aligned |
| a schedule as a series | to_series |
absent | divergent |
| create a constant schedule | create_constant_schedule |
createConstantSchedule |
aligned |
| create a compact schedule | create_compact_schedule_from_values |
createCompactScheduleFromValues |
aligned |
| extract special days | extract_special_days |
extractSpecialDays |
aligned |
| the holidays in a model | get_holidays |
getHolidays |
aligned |
Output variable selection (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| read the output variable dictionary | parse_rdd_file |
parseRddFile |
aligned |
| an output variable | OutputVariable |
OutputVariable |
aligned |
| an output meter | OutputMeter |
OutputMeter |
aligned |
| select output variables for a run | prep_outputs |
prepOutputs |
aligned |
Design days and sizing (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| apply ASHRAE sizing conditions | apply_ashrae_sizing |
applyAshraeSizing |
aligned |
| the design day manager | DesignDayManager |
DesignDayManager |
aligned |
Version migration (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| migrate a model to a newer version | migrate |
migrate |
aligned |
| a migration report | MigrationReport |
MigrationReport |
aligned |
| a migration step | MigrationStep |
MigrationStep |
aligned |
The command line (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the command line entry point | idfkit |
idfkit |
aligned |
Schema compatibility checking (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| check source compatibility with a version | check_compatibility |
checkCompatibility |
aligned |
| diff two schemas | diff_schemas |
diffSchemas |
aligned |
| a compatibility diagnostic | idfkit.compat.Diagnostic |
Diagnostic |
aligned |
Plotting simulation results (registered ahead of the port)¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| plot an energy balance | plot_energy_balance |
plotEnergyBalance |
aligned |
| plot a temperature profile | plot_temperature_profile |
plotTemperatureProfile |
aligned |
| plot comfort hours | plot_comfort_hours |
plotComfortHours |
aligned |
| a plotting backend | PlotBackend |
PlotBackend |
aligned |
Permanently single-language surfaces¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the local simulation surface | 19 names | absent | excluded |
| the vector image surface | 9 names | absent | excluded |
The error hierarchy¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the base error type | IdfKitError |
absent | divergent |
| an unknown object type | UnknownObjectTypeError |
absent | divergent |
| an invalid field | InvalidFieldError |
absent | divergent |
| a value outside its range | RangeError |
absent | divergent |
| a duplicate object | DuplicateObjectError |
absent | divergent |
| a version mismatch | VersionMismatchError |
absent | divergent |
| an unsupported version | UnsupportedVersionError |
absent | divergent |
| a schema that cannot be found | SchemaNotFoundError |
absent | divergent |
| a version that cannot be detected | VersionNotFoundError |
absent | divergent |
| a validation that failed | ValidationFailedError |
absent | divergent |
| a migration that failed | MigrationError |
absent | divergent |
| a DDY file with no design days | NoDesignDaysError |
absent | divergent |
| the local simulation error types | 3 names | absent | excluded |
Version constants and version helpers¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the newest supported version | LATEST_VERSION |
SchemaBundle.latest |
divergent |
| the oldest supported version | MINIMUM_VERSION |
absent | divergent |
| the ASHRAE perimeter depth | ASHRAE_PERIMETER_DEPTH |
ASHRAE_PERIMETER_DEPTH |
aligned |
| detect a document version | get_idf_version |
getIdfVersion |
aligned |
| detect an epJSON document version | idfkit.epjson_parser.get_epjson_version |
getEpJsonVersion |
aligned |
| render a version as text | version_string |
absent | divergent |
| whether a version is supported | is_supported_version |
absent | divergent |
| version string ordering | absent | 2 names | excluded |
Footprint builders and two-dimensional polygon helpers¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a rectangular footprint | footprint_rectangle |
footprintRectangle |
aligned |
| an L-shaped footprint | footprint_l_shape |
footprintLShape |
aligned |
| a T-shaped footprint | footprint_t_shape |
footprintTShape |
aligned |
| a U-shaped footprint | footprint_u_shape |
footprintUShape |
aligned |
| an H-shaped footprint | footprint_h_shape |
footprintHShape |
aligned |
| a courtyard footprint | footprint_courtyard |
footprintCourtyard |
aligned |
| a two-dimensional polygon's area | polygon_area_2d |
polygonArea2D |
aligned |
| whether a two-dimensional polygon contains a point | polygon_contains_2d |
polygonContains2D |
aligned |
| the difference of two two-dimensional polygons | polygon_difference_2d |
polygonDifference2D |
aligned |
| the intersection of two two-dimensional polygons | polygon_intersection_2d |
polygonIntersection2D |
aligned |
| a model's bounding box | bounding_box |
boundingBox |
aligned |
Zoning and horizontal surface matching, with the groups above¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a zoned block | ZonedBlock |
ZonedBlock |
aligned |
| a horizontal adjacency | HorizontalAdjacency |
HorizontalAdjacency |
aligned |
| surface matching options | MatchOptions |
MatchOptions |
aligned |
| detect horizontal adjacencies | detect_horizontal_adjacencies |
detectHorizontalAdjacencies |
aligned |
| link horizontal surfaces | link_horizontal_surfaces |
linkHorizontalSurfaces |
aligned |
| split a horizontal surface | split_horizontal_surface |
splitHorizontalSurface |
aligned |
| a zone's ceiling area | calculate_zone_ceiling_area |
calculateZoneCeilingArea |
aligned |
| a zone's height | calculate_zone_height |
calculateZoneHeight |
aligned |
Schema objects and the parser object¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| one version's schema | EpJSONSchema |
Schema |
divergent |
| the schema source | SchemaManager |
SchemaBundle |
divergent |
| the process-wide schema source | get_schema_manager |
schemas |
divergent |
| the IDF parser object | IDFParser |
absent | divergent |
| the schema bundle source surface | absent | 2 names | excluded |
| the slimmed schema record types | absent | 2 names | excluded |
| the difference between two schema versions | absent | SchemaDelta |
divergent |
| resolve the schema for a detected version | absent | schemaFor |
divergent |
The writer's controls¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| indent width | indent |
indent |
aligned |
| comment column | comment_column |
commentColumn |
aligned |
| object ordering | ordering |
ordering |
aligned |
| pin the version object first | version_first |
versionFirst |
aligned |
| the options-object types | absent | 5 names | excluded |
| the static typing surface | absent | 4 names | excluded |
| the per-type prototype surface | absent | 3 names | excluded |
| the object-model value types | absent | 4 names | excluded |
| the lexer surface | absent | 4 names | excluded |
Remaining single-language names on the JavaScript side¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| a reference edge | absent | ReferenceEdge |
divergent |
| an epJSON document value | absent | EpJson |
divergent |
| serialize one object | absent | writeObject |
divergent |
| serialize a document to an epJSON value | absent | toEpJson |
divergent |
| read IDF from disk, keeping diagnostics | load_idf_with_diagnostics |
loadIdfWithDiagnostics |
aligned |
The document's own members¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| the path a document was read from | IDFDocument.filepath |
absent | divergent |
| strict field access | IDFDocument.strict |
absent | divergent |
| a document's schema | IDFDocument.schema |
IdfDocument.schema |
aligned |
| label a field the author left without a comment | field_comments |
fieldComments |
aligned |
| the objects a preserving write will rewrite | IDFDocument.changed_objects |
IdfDocument.changedObjects |
aligned |
| where an object's characters sit in the retained source | IDFDocument.region_of |
IdfDocument.regionOf |
aligned |
| one object rendered as a preserving write would render it | IDFDocument.render_object |
IdfDocument.renderObject |
aligned |
| a half-open range of the source text | SourceSpan |
Region |
aligned |
| the retained source structure | IDFDocument.cst |
absent | divergent |
| the original source text | IDFDocument.raw_text |
IdfDocument.rawText |
aligned |
| every collection in a document | IDFDocument.collections |
absent | divergent |
| a document's reference graph | IDFDocument.references |
IdfDocument.references |
aligned |
| the object types present in a document | IDFDocument.keys |
types |
divergent |
| every non-empty collection | IDFDocument.values |
absent | divergent |
| every object type and its collection | IDFDocument.items |
absent | divergent |
| an object's outgoing references | IDFDocument.get_references |
referencedBy |
divergent |
| a model's schedules by name | IDFDocument.schedules_dict |
absent | divergent |
| one schedule by name | IDFDocument.get_schedule |
getSchedule |
aligned |
| the schedules a model uses | IDFDocument.get_used_schedules |
getUsedSchedules |
aligned |
| a zone's surfaces | IDFDocument.get_zone_surfaces |
getZoneSurfaces |
aligned |
| every object in a document | IDFDocument.all_objects |
objects |
divergent |
| an object's name changed | IDFDocument.notify_name_change |
onNameChanged |
divergent |
| an object's field changed | IDFDocument.notify_reference_change |
onFieldChanged |
divergent |
| copy a document | IDFDocument.copy |
IdfDocument.copy |
aligned |
| run the ExpandObjects preprocessor | IDFDocument.expand |
absent | excluded |
| second Python spellings of a registered concept | 4 names | absent | excluded |
Remaining top-level Python names¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| migrate a model without blocking | async_migrate |
absent | divergent |
| create schedule type limits | create_schedule_type_limits |
createScheduleTypeLimits |
aligned |
The language service for IDF text¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| scan IDF text | absent | scanIdf |
excluded |
| classify IDF text | absent | classify |
excluded |
| line and column at an offset | absent | lineColumnAt |
excluded |
| offset at a line and column | absent | offsetAt |
excluded |
| a source region | absent | Region |
excluded |
| a line and column | absent | LineColumn |
excluded |
| a syntax token | absent | Token |
excluded |
| a syntax token kind | absent | TokenKind |
excluded |
| the syntax layer | absent | SyntaxLayer |
excluded |
| a written statement | absent | Statement |
excluded |
| schema prose pool | absent | ProsePool |
excluded |
| cursor context | absent | contextAt |
excluded |
| the cursor context record | absent | CursorContext |
excluded |
| completions at an offset | absent | completionsAt |
excluded |
| explanation at an offset | absent | explainAt |
excluded |
| declaration at an offset | absent | declarationAt |
excluded |
| position findings | absent | findingsIn |
excluded |
| position findings already in hand | absent | position |
excluded |
| a positioned finding | absent | PositionedFinding |
excluded |
| a completion offer | absent | Offer |
excluded |
| an explanation | absent | Explanation |
excluded |
| a declaration site | absent | Declaration |
excluded |
| the completion options | absent | CompletionOptions |
excluded |
| a completion result | absent | CompletionResult |
excluded |
| an explanation result | absent | ExplanationResult |
excluded |
| a declaration result | absent | DeclarationResult |
excluded |
The column unit¶
| Concept | Python | TypeScript | Kind |
|---|---|---|---|
| column in a finding | column |
column |
divergent |
What the notes add¶
Some names carry a note the tables cannot hold. Notes are grouped here, so a note that applies to many names is stated once. "Registered before it is written" means exactly that: the name is decided and reserved, and the implementation follows it rather than the other way round.
parse epJSON from a string
The casing of the format name follows each ecosystem's rule for the rest of the
identifier, so Python's single-case snake_case gives epjson and TypeScript's camelCase
gives EpJson. Both recover the same word.
read IDF from disk, read epJSON from disk
TypeScript exports this from @idfkit/core/node, the async edge; see the I/O entry.
serialize IDF to a string
Python's write_idf keeps its spelling through the rename batch and loses only its
filepath parameter and the three @overload stanzas that parameter forced. The
disk-writing half moves to save_idf, so the string-serializing name is unchanged and
its rename count stays at 0.
write IDF to disk
Python's rename count is 1: this operation was spelled write_idf(doc, filepath) before
the rename batch. TypeScript already exports saveIdf from @idfkit/core/node and does
not change. Python's save_idf does not exist yet at the time this entry is registered;
it is written by the rename batch under this name.
serialize epJSON to a string
Same construction and same reasoning as the IDF pair above.
write epJSON to disk
Python's rename count is 1, for the same reason as save_idf: this operation was
spelled write_epjson(doc, filepath). save_epjson is written by the rename batch
under this name.
add an object
IDFDocument.add(obj_type, name, **fields) against IdfDocument.add(type, name,
values). The keyword-argument form is idiomatic Python and has no TypeScript
equivalent, so the field values arrive as an object literal there. Same verb, same
argument order, same result.
remove an object
Python spells it IDFCollection.remove(obj); TypeScript spells it
IdfDocument.remove(obj). The receiver differs because Python reaches a collection
through doc["Zone"] and TypeScript through doc.all('Zone'), which is the operator
divergence recorded below, not a second verb.
rename an object
The two signatures differ: Python's IDFDocument.rename(obj_type, old_name, new_name)
against TypeScript's IdfDocument.rename(obj, next).
Both languages support obj.name = new, which is unambiguous in either, needs no
object-type argument, and repoints every reference in both implementations. That
assignment is the PRIMARY PATH and is what the documentation teaches on both sides. The
method signatures are left alone rather than forced into agreement: aligning them would
rename a working method for a gain the assignment form already delivers, and would spend
a rename budget on syntax no reader has to use. Recorded per research R14.
validate a document
Registered before it is written. The TypeScript side does not exist at registration
time; the Tier 1 port writes it under this name, into
idfkit-js/packages/core/src/validate/. Naming an unwritten capability costs nothing
now and is breaking later, which is the entire reason for FR-007.
validate an object
Registered before it is written, with validate_document.
validation result
Registered before the TypeScript type is written. No acronym, so no casing divergence.
validation finding
Registered before the TypeScript type is written. The name says Error and the type is a
record, not a thrown exception: a finding carries a severity and a location and is
collected into a ValidationResult. Both languages keep the existing Python spelling
rather than improving it here, because renaming it would spend a rename budget on a type
the port has not yet shipped.
validation severity
Registered before the TypeScript type is written. Python reaches it at
idfkit.validation.Severity rather than through the top-level __all__; the values are
error, warning, info and the TypeScript port renders the same three strings.
describe an object type
Registered before it is written, into idfkit-js/packages/core/src/introspect/. Python
reaches it at idfkit.introspection.describe_object_type and through
IDFDocument.describe(obj_type); the module-level function is the registered name.
object description
Registered before the TypeScript type is written. Same field set on both sides.
field description
Registered before the TypeScript type is written. Same field set and constraints on both sides.
resolved documentation URL
Registered before the TypeScript type is written. Carries url, doc_set, version,
and label in Python; the TypeScript port spells the field docSet and keeps the rest,
which is the field-name casing rule rather than a divergence in the type.
documentation URL for an object type
Registered before it is written, into idfkit-js/packages/core/src/docs-url/.
I/O reference URL
Registered before it is written. io stays lowercase in TypeScript by the same rule
that gives IdfObject.
engineering reference URL, documentation search URL, a surface's area, rotate a building, translate a building, scale a building, add a shading block, set default constructions, link blocks, a construction's U-value, a construction's R-value, a construction's layers, create a constant schedule, create a compact schedule, extract special days, the holidays in a model, select output variables for a run, diff two schemas, plot an energy balance, plot a temperature profile, plot comfort hours, whether a two-dimensional polygon contains a point, the intersection of two two-dimensional polygons, split a horizontal surface
Registered before it is written.
declared conformance level
The conformance-YYYY.N tag of this repository that the release passes. A constant, not
a function, in both languages, and a string in both, so the two claims compare directly.
Each side reads it from one place rather than restating it, so the declaration and the
pin cannot diverge: Python from [tool.idfkit.conformance] level in pyproject.toml,
TypeScript from idfkit.conformance in packages/core/package.json. Registered here so
that the export passes the naming gate on the change that adds it.
Screaming snake case is the constant convention in both ecosystems, so this name is identical on both sides rather than merely aligned.
one object, or nothing
Returns None in Python and undefined in TypeScript, each language's own absent
value.
three-dimensional vector
Registered before the TypeScript type is written. 3D is a numeral and a letter rather
than an acronym, so the casing rule that splits IDFObject from IdfObject does not
apply and the two names are identical.
three-dimensional polygon
Registered before the TypeScript type is written. Carries normal, area, centroid,
tilt, azimuth, is_horizontal, and is_vertical in Python; the TypeScript port
spells the two predicates isHorizontal and isVertical by the field-casing rule and
keeps the rest.
read a surface's coordinates
Registered before it is written. Returns the surface's Polygon3D, or the absent value
when it has no vertices.
a zone's origin
Registered before it is written. Returns a Vector3D.
a zone's rotation
Registered before it is written. Returns degrees about the vertical axis.
transform relative coordinates to world coordinates
Registered before it is written. The one member of this group that mutates the document:
it rewrites relative surface coordinates in place and updates GlobalGeometryRules. It
is registered with the extraction group because every calculation below depends on
knowing which coordinate system it is reading.
The first language's implementation of this name is wrong and is corrected by the change that lands the scene description above. Measured against the engine's own surface vertex report over 17 models and 532 surfaces, it agrees on 5 of the 17, worst case 201.98 m. It applies the building rotation to each surface within its own zone frame, which leaves the zone layout unrotated so that the building is not rotated as a rigid body, and it collects a zone's surfaces through the reference graph, which fenestration never matches because a window references its parent wall rather than the zone.
Recorded here rather than only in the library, because a reader meets this name at its definition and because the register presents this function and the read-only surface above as one capability.
a surface's tilt
Registered before it is written. Degrees, using the EnergyPlus convention.
a surface's azimuth
Registered before it is written. Degrees clockwise from north, using the EnergyPlus convention.
an extensible group
One row of a repeated field group, for example a single vertex inside a surface's vertex
list. Python reaches it at idfkit.objects.ExtensibleGroup rather than through the
top-level __all__; TypeScript exports the type from @idfkit/core. No acronym, so no
casing divergence.
The accessor that hands these rows out is the next entry, and unlike the row type it does NOT align.
the reference graph
Exported by both libraries under the same name. No acronym, so no casing divergence.
a parse diagnostic
One finding from a parse: a message, a machine-readable code, and as much location as
the parser had at the point it noticed. Python reaches it at
idfkit.exceptions.ParseDiagnostic rather than through the top-level __all__.
There is no severity. An earlier version of this note claimed one, and neither implementation has ever carried it: every diagnostic either stopped the parse or was recoverable, and which of those it was is told by the path it arrives on rather than by a field. The claim is corrected here rather than implemented, because adding a field to match a note nobody had checked is the wrong direction.
code was added in feature 002 and is the field the corpus compares. It is derived from
the exception hierarchy by dropping the suffix, so the same eight values exist in both
languages; the message text is not compared and is free to differ.
TypeScript gained the object concerned in feature 002, and declares a file path and a
column beside it, so both sides name the same kinds of location (FR-033). Only Python
fills all four today: the TypeScript lexer counts lines and not columns, and parseIdf
is handed text rather than a path, so neither value exists where a finding is built.
Both are optional and absent rather than invented. obj_type against typeName stays
as it is: that is the casing rule this register already records, not a gap.
a document and its parse findings
What the returning path hands back: the document, and the recoverable findings that did not stop the parse. A frozen dataclass in Python and an interface in TypeScript, which is the house shape for a value object in each language.
TypeScript has returned this since it was written. Python's is new in feature 002 and is
reached through load_idf_with_diagnostics; the field names match, so a reader moving
between the two reads the same two names in the same order.
Registered in the same change as the path that returns it, and before either shipped.
diagnostics from a parse
The findings that stopped a parse, carried by the error that reports it. Both languages raise by default and both now carry the whole collection rather than one finding flattened into fields.
This entry was recorded as divergent, on the grounds that Python raises and TypeScript
returns. That was never what the two did: parse_idf defaults to strict_parsing=True
and raises, parseIdf defaults to strict: true and throws. The recorded divergence
described the non-strict mode while reading as though it described the default. What
actually differed was narrower, and feature 002 closed it: TypeScript's error carried
.line and .typeName from a single finding and now carries the collection, with both
accessors kept resolving to the first finding's values so no existing caller breaks
(FR-014).
THIS ENTRY IS THE ERROR'S COLLECTION. The result's is recoverable diagnostics from a
parse, below.
The registered pair was IDFParseError.diagnostics against ParseResult.diagnostics
until 2026-09-04. That crossed the two paths, and the crossing was the shape of the
divergence. Splitting it into one concept per carrier is what makes both entries
alignable, and it costs one TypeScript rename: this concept's TypeScript name moves from
ParseResult.diagnostics, which has not gone anywhere and is registered below, to
IdfParseError.diagnostics. Python's name does not move, so its budget is untouched.
The rename is spent deliberately and is the cheaper of the two available. Mapping this concept to the result instead would have kept the TypeScript name and renamed Python, which is the same cost against a name that already exists rather than one that is landing in the same feature.
Python's logging announcements are unchanged and are not a third name: they are the same findings reaching a caller who installed a handler.
recoverable diagnostics from a parse
The findings that did NOT stop the parse, carried alongside the document the parse still
produced. Reached through load_idf_with_diagnostics and loadIdfWithDiagnostics, or
by parsing with the strict flag off.
TypeScript has carried these since it was written; this is the name it has always had,
unmoved. Python's is new in feature 002 and is registered here before it ships.
Splitting them from diagnostics from a parse above is what lets both concepts be
aligned rather than one entry pretending an error member and a result member are the
same name.
the station index
Both libraries ship their own index and neither retrieves one to get started (FR-043, FR-075).
search stations
Same receiver, same name, same argument, and SearchResult on both sides.
a text search result
One hit from StationIndex.search: the station, a relevance score from 0 to 1, and the
field that matched. Identical names, and the field carrying the last of the three is
spelled match_field in Python and matchField in TypeScript by the field-casing rule.
Python reaches the type at idfkit.weather.SearchResult rather than through the
top-level __all__.
a proximity search result
One hit from StationIndex.nearest: the station and its great-circle distance,
distance_km in Python and distanceKm in TypeScript. Same construction and same
reasoning as a text search result above.
great-circle distance between two points
The Haversine formula over four decimal degrees, in kilometres. Both sides use the same
6371 km radius and the same clamp before the asin, so nearest orders a query
identically in either library. Python reaches it at
idfkit.weather.spatial.haversine_km.
check the station index for updates
The same operation under corresponding names: send HEAD requests for the upstream KML
files, compare Last-Modified against the values the index was built with, and answer
false when the index carries none or the probe cannot be completed.
The receiver differs and the names do not. Python hangs it on the index, because the
index it compares against is the one it is called on. TypeScript takes the index as an
argument, because everything reaching the network there is a free function taking an
overridable fetch. That is the download a weather file divergence, not a second name
for this operation.
Python also fires this check by itself from StationIndex.load, at most once a day.
That nudge has no TypeScript counterpart, for the reason refresh the station index
above gives, and the parity ledger records the consequence under weather-index.
a geocoding failure
Identical names, and one of the few failures the JavaScript side throws rather than
returns: an address that resolves to nothing leaves no partial answer to hand back, so
there is no value for the failure to be. Python reaches it at
idfkit.weather.GeocodingError.
the geocoding rate limiter
Nominatim asks callers to stay under one request per second, and both libraries hold to
it with an object of this name carrying a minimum interval, a wait, and a reset. The
implementations differ as the languages do: Python takes a lock, because threads exist
there, and TypeScript chains a promise, because they do not.
Public on both sides and advertised on neither. Python reaches it at
idfkit.weather.geocode.RateLimiter rather than through the sub-package's __all__,
the same shape as idfkit.exceptions.ParseDiagnostic. Registered so that the two
spellings cannot drift apart, not as a promise that either is prominent.
filter stations by metadata
Registered by 007-weather-file-readers, which found it absent. search stations was
already here and this was not, although the two are the same kind of method on the same
class, so the omission reads as a gap rather than a decision. Both signatures take the
same keys and always have.
A method rather than a top-level export, so it is outside the naming gate's reach and is
covered by its exported head, the station index. That is exactly why it is registered
explicitly: the gate will never fail on it, so review is the only control, and an entry
is what review reads.
The climate zone keys. 007 adds two keys to the options object both signatures take: one naming an ASHRAE climate zone, and one asking for the records whose zone could not be determined. They land in both languages in the same change, so this entry never passes through a divergent state.
The second key exists rather than being a magic value of the first because of what the
shipped index holds. Of 69,638 records, 1,262 are labelled 7A - ASHRAE Climate Zone
could not be determined and 900 are labelled 8A - .... Neither 7A nor 8A is an ASHRAE
zone, since zones 7 and 8 carry no suffix. Keying the filter on the label's first token
invents two zones that do not exist holding 3.1% of the index, so the key is matched
against a parsed code with the undetermined records excluded, and asking for them is a
separate question with a separate key rather than a reserved string in the first key's
domain.
read a weather file from text
Registered ahead of both implementations. Neither library has an EPW reader today; this is a new concept in both rather than a port, because Python's weather module is retrieval, geocoding, the index, spatial search and design days, and its design-day half parses DDY by calling the ordinary IDF loader rather than a reader of its own.
Lives on the portable surface in both languages. In TypeScript that means
@idfkit/weather and NOT @idfkit/weather/node: that entry point exists for the two
things a browser cannot do, being the bundled index and writing to disk, and reading a
string is neither.
read a weather file from a source
Registered ahead of both implementations. The filesystem edge over parse_epw /
parseEpw, adding no behaviour of its own. In TypeScript it lives in
@idfkit/weather/node, which the shared install name does not currently expose as a
subpath; that is an existing property of the facade's export map and not a claim made
here.
the weather file a reader returns
Registered ahead of both implementations. The eight header records plus the hourly table as columns.
Distinct from the retrieved weather files (WeatherFiles, plural), which is what
retrieval hands back: the EPW, DDY and STAT members of one archive. This is what reading
ONE of those members produces. The two names are one character apart and that is a
hazard, so it is named here rather than discovered in review: retrieval yields
WeatherFiles, and reading the EPW inside it yields a WeatherFile.
monthly aggregates of a weather file
Registered ahead of both implementations. Twelve values for one column, each carrying the count of hours it included, with absent hours excluded from both the sum and the divisor.
The count is part of the returned value rather than derivable: without it a caller cannot tell a mean over 744 hours from a mean over three, and both look like numbers.
preserve formatting on a round-trip
An option, not a function: load_idf(path, preserve_formatting=True) builds a concrete
syntax tree that write_idf reproduces, and load_epjson retains the raw JSON text the
same way. TypeScript retains the syntax layer and an index from statement to object
instead, and writeIdf walks the two together; parseEpJson retains the text the same
way Python does.
Registered at governance-2026.12, before either side of the TypeScript half existed,
on the reasoning that registering an option name costs nothing then and is breaking
later. It cost nothing: this feature made the registered name true rather than choosing
one.
a zone's floor area
Registered before it is written, with the geometry extraction group above. Read-only, like the rest of that group.
a zone's volume
Registered before it is written, with the geometry extraction group above.
a model's resolved geometry
Registered before it is written. Takes a document, returns the scene, modifies nothing.
Follows the get_* to get* pairing of the geometry extraction group above, which the
a zone's surfaces entry also cites.
One argument and no options. A filter over the result is a list operation the caller already has, and a viewing option would put a viewing decision in a package that does not draw.
the resolved scene
Registered before it is written. Carries the resolved surfaces in document order, the extent, the rules resolution applied, the objects it could not resolve, and the object types it did not attempt.
The last two are not diagnostics. A model whose geometry is stated in a form the reader does not handle must be distinguishable from a model with no geometry, and the difference between them is the difference between telling a reader their file is unsupported and telling them it is broken.
a resolved surface
Registered before it is written. One surface in the scene: its polygon in the resolved frame, its area, its outward normal, the address of the object it came from, the facts a reader classifies it by, and for fenestration the surface it sits on.
Carries the canonical object type alongside the name, because a name is not unique across types and a consumer that must search the document by name to discover what its user selected has been handed a picture rather than a view of the model.
Classification values drawn from an enumerated schema field are reported in the schema's
spelling rather than the author's. EnergyPlus matches them without regard to case and
models exercise that freely: across the example set of EnergyPlus 26.1.0 the four
surface types appear under twelve spellings and the five fenestration types under nine,
GLASSDOOR and GlassDoor among them. A value outside the enumeration passes through
unchanged, because a category invented for it would tell the reader something the model
does not say.
Carries no colour, palette or visibility. The first language's viewing surface is registered separately as excluded, and that exclusion is terminal.
the rules resolution applied
Registered before it is written. The coordinate system, the vertex entry direction, the starting vertex position and the building rotation, each recorded as declared by the model or defaulted.
The building rotation is recorded although it has already been applied to the vertices. That is what makes the choice reversible: the scene's numbers are the numbers the engine computes, so the oracle means what it says, and a consumer wanting to draw the building unrotated under a compass can undo it exactly rather than reimplementing the clause most easily got wrong.
the scene's extent
Registered before it is written. The smallest box enclosing every resolved vertex, absent rather than degenerate when nothing resolved, so that a consumer does not frame an empty box at the origin.
a surface that could not be resolved
Registered before it is written. An object extraction attempted and could not place, with its type, its name and an enumerated reason.
The reason is an enumeration rather than a message, so that a consumer can group on it and a reworded string does not change behaviour. The alternative this replaces is the first language's current habit of skipping such an object silently in three places, which is fine in a notebook and wrong everywhere else: a building drawn with a wall missing still looks like a building.
a geometry type the reader did not attempt
Registered before it is written. An object type present in the model that the reader does not read, with its count.
This is the mechanism by which a deferral stays honest. The first slice of this port reads the detailed surface forms and not the simplified family; five of the example models state their geometry only in the simplified forms and hold no detailed surface at all. Without this entry those five report as empty, which tells a reader their file is broken rather than that this reader does not read it yet.
set the window to wall ratio
Registered before it is written. wwr stays lowercase in TypeScript by the same rule
that gives IdfObject.
intersect and match surfaces
Registered before it is written. Python currently exports a second public name for the
same operation, intersect_match, which FR-005 prohibits; that duplicate is resolved by
the change that withdraws it, not here, and this entry records the surviving name.
a surface match report, a zone footprint, a zoning scheme, an output variable, an output meter, a migration report, a migration step
Registered before the TypeScript type is written.
create a zoned block
Registered before it is written, into the reserved @idfkit/geometry package.
construction thermal properties
Registered before the TypeScript type is written. Produced by get_thermal_properties
in Python.
a construction's solar heat gain coefficient
Registered before it is written. shgc is an abbreviation, so TypeScript writes it as
one word by the same rule that gives IdfObject, and Python keeps the lowercase run its
snake_case gives.
evaluate a schedule
Registered before it is written. One value at one instant.
a schedule's values for a year
Registered before it is written. The whole year at a chosen timestep.
read the output variable dictionary
Registered before it is written. Python reaches it at
idfkit.simulation.parsers.rdd.parse_rdd_file and pairs it with parse_mdd_file for
the meter dictionary.
apply ASHRAE sizing conditions
Registered before it is written. ashrae is an abbreviation, so TypeScript writes it as
one word by the same rule that gives IdfObject.
the design day manager
Registered before the TypeScript type is written. Reads a DDY file and injects design days into a model.
migrate a model to a newer version
Registered before it is written. Python pairs it with async_migrate; the TypeScript
port is async by construction.
the command line entry point
The console script name, not a module member. Python installs it from
idfkit.compat._cli:main with the check and migrate subcommands. Registered before
the JavaScript side is written so that a port cannot land under a different command
name; the parity ledger records the absence as Tier 3 under command-line.
check source compatibility with a version
Registered before it is written. Python reaches it at
idfkit.compat.check_compatibility.
a compatibility diagnostic
Registered before the TypeScript type is written. Distinct from a parse diagnostic:
this one reports a source construct that will not hold against another EnergyPlus
version, and it carries one of DIAGNOSTIC_CODES.
a plotting backend
Registered before the TypeScript type is written. Python selects one with
get_default_backend.
the ASHRAE perimeter depth
Registered before it is written, with the zoning group. The default perimeter depth used
when a footprint is split into core and perimeter zones, 4.57 m, which is 15 feet in the
ASHRAE source. Screaming snake case is the constant convention in both ecosystems, so
this name is identical on both sides rather than merely aligned, the same as
CONFORMANCE_LEVEL. The abbreviation stays fully capitalised in TypeScript because that
is what the constant convention does to every letter, which is why this does not
contradict the rule that gives applyAshraeSizing.
detect a document version
Read the EnergyPlus version out of an IDF document without a schema. Both sides exist and both are the minimum scan needed to break the chicken-and-egg between choosing a schema and reading the version that selects it.
TypeScript's rename count is 1. The package shipped this as detectVersion; it becomes
getIdfVersion, which settles the get against detect conflict in favour of get,
the verb the register already uses for every accessor pair it has decided:
get_surface_coords to getSurfaceCoords, get_zone_origin to getZoneOrigin,
get_construction_layers to getConstructionLayers. The new name also says which
format it reads, which the old one did not, and which matters now that
getEpJsonVersion sits beside it. Python's get_idf_version is unchanged and its count
stays 0.
DECIDED 2026-09-02. The rename lands in the T061 to T069 batch, with the rest of the
batch, and is breaking for anyone importing detectVersion from @idfkit/core@0.1.0.
The signatures are not identical and are not being aligned: Python's takes a path and
returns tuple[int, int, int], TypeScript's takes IDF text and returns string |
undefined. That is the version type divergence and the input and output divergence,
both already recorded, and the string is canonical across the boundary.
detect an epJSON document version
The epJSON counterpart of detect a document version, and it is named here so that the
pair cannot drift apart later.
TypeScript's rename count is 1. The package shipped this as detectEpJsonVersion; it
becomes getEpJsonVersion with detectVersion, in the same batch and for the same
reason. DECIDED 2026-09-02, landing in the T061 to T069 batch, and breaking for anyone
importing detectEpJsonVersion from @idfkit/core@0.1.0.
Python's get_epjson_version already carries the settled spelling and its count stays
0. It is reached at idfkit.epjson_parser.get_epjson_version rather than through the
top-level __all__, which is the same shape as
idfkit.introspection.describe_object_type and idfkit.schedules.evaluate. Promoting
it to the top level would be additive and is not part of this feature; whether or not
that happens, the name is fixed here.
The casing of the format name follows each ecosystem's rule for the rest of the
identifier, so Python gives epjson and TypeScript gives EpJson, exactly as
parse_epjson against parseEpJson.
a rectangular footprint
Registered before it is written. Returns the plan outline as a list of two-dimensional points.
an L-shaped footprint
Registered before it is written. The single letter is a word of the name in both languages, so Python's snake_case separates it and TypeScript's camelCase capitalises it.
a T-shaped footprint, a U-shaped footprint, an H-shaped footprint
Registered before it is written, with the other lettered footprints.
a courtyard footprint
Registered before it is written. A rectangular outline with a rectangular hole.
a two-dimensional polygon's area
Registered before it is written. Signed area, so the sign reports the winding direction.
the difference of two two-dimensional polygons
Registered before it is written. Returns the possibly several pieces the subtraction leaves.
a model's bounding box
Registered before it is written. The plan extent of every surface in the model, or the absent value when the model has no coordinates to bound.
a zoned block
Registered before the TypeScript type is written. What create_block produces, with the
zoning group above.
a horizontal adjacency
Registered before the TypeScript type is written. One ceiling-and-floor pair found between two stories.
surface matching options
Registered before the TypeScript type is written. The tolerances intersect_and_match
works to, beside MatchReport.
detect horizontal adjacencies
Registered before it is written. The verb is detect rather than get because the
operation searches: it compares outdoor horizontal surfaces across stories and reports
the overlaps it finds, which is not an accessor. That distinction is what detect a
document version gives up by becoming getIdfVersion, and it is why the two are not
the same case.
link horizontal surfaces
Registered before it is written. Sets each of a ceiling and floor pair as the other's outside boundary.
a zone's ceiling area
Registered before it is written, with calculate_zone_floor_area and
calculate_zone_volume.
a zone's height
Registered before it is written, with the other zone measures.
indent width
How far each field line is indented. A count of spaces in Python, the string itself in TypeScript, which is the shape each language's callers expect and is recorded here rather than reconciled.
The DEFAULTS differ and are not moving: two spaces in Python, four in TypeScript. Both
are published and neither is more correct, which is the whole subject of the two
writers, one model page.
comment column
The column field-name comments are padded to. The one writer default of the six that already agreed: 30 on both sides.
object ordering
How object types are ordered on the way out. Takes sorted or source rather than a
boolean, because three behaviours exist across the two languages and two formats and a
flag cannot say which of the three is wanted.
The defaults differ and are not moving: sorted in Python, whose !-Option SortedOrder
header announces it, and source in TypeScript, which is insertion order.
pin the version object first
Whether Version is written ahead of every other type, whatever the ordering. Defaults
to true on both sides, which is what both writers did before the control existed.
Composes with object ordering rather than overriding it: the ordering decides the
sequence, this decides whether Version is lifted out of it.
read IDF from disk, keeping diagnostics
Both languages need two loaders because the recoverable findings are worth keeping and
most callers do not want to unwrap a result to get the document. load_idf and
loadIdf hand back the document and drop the non-fatal findings; these two hand back a
document and its findings together.
Python had no counterpart until feature 002. The recoverable findings went to the logging module and were reachable only by installing a handler before the parse, which is not one call and not the same findings the other language returns. The logging announcements still fire, unchanged, for callers who rely on them (FR-014); this entry adds a second way to reach the same findings, and removes none.
Does the work strict_parsing=False does in Python and strict: false does in
TypeScript, without asking for it: a strict parse has no recoverable findings, since the
first one stops it, so the Python loader takes no strict_parsing argument at all. The
fatal path is unchanged in both and is recorded under diagnostics from a parse.
a document's schema
The schema the document was parsed against. Python's is EpJSONSchema | None, because a
document can be built without one; TypeScript's is a required Schema, because its
constructor takes one. Same name, same meaning, and the optionality difference follows
from create a new document.
label a field the author left without a comment
A writer control, "preserve" or "generate", meaningful only on the preserving path
because that is the only path that knows what the author wrote.
"preserve" leaves a bare field bare. A field written without a comment was written
that way on purpose, and absence is as much a thing the author wrote as the words are.
"generate" labels it, for a caller who wants the file annotated.
Neither setting touches the author's own comment LINES. A comment between two objects is carried by the text between them, and a comment on its own line inside an object is emitted with the field below it, so asking for labels adds them and never costs a line. That is the property this option exists to make safe: without it, wanting labels would mean taking the whole ordinary writer and losing everything it cannot regenerate.
An enumeration rather than a boolean, on the same reasoning as ordering: a third
behaviour is plausible, reusing the author's comment and appending the unit, and a flag
could not say which.
the objects a preserving write will rewrite
Every object a preserving write will write afresh rather than reproduce from the characters it was read from. Empty for a document read with preservation and not edited since; every object for a document read without it, because there is nothing to reproduce.
the original source text answers WHETHER a write will preserve. This answers HOW MUCH
of the file it will change, which is the question a save button has to put to a user out
loud, and it is the one a consumer cannot derive: a rename clears the record on every
object that referred to the renamed one, so counting from an edit log reports one where
the answer is nine. Deriving it means reimplementing the rule about what a change
touches, which is a second opinion about the same question and drifts the day the rule
moves.
A generator beside objects() rather than a count, so a consumer that wants to LIST
what it is about to reformat can, and a count is the length of what it yields.
Python could already answer this per object: IDFObject.source_text is None for an
object that has been changed, and the writer branches on exactly that. That is the
retained TEXT, though, and its use as a flag is incidental to what it is; a consumer
reading it has to know that having text means unchanged, which is a sentinel that reads
backwards. This asks the question directly, in both languages, and leaves source_text
as the text it is.
The anchoring behind the record stays internal in both. A consumer wanting the layer
calls scanIdf.
where an object's characters sit in the retained source
The half-open offset range in the original source text that an object was read from,
or nothing for an object added since the read.
This exists because the objects a preserving write will rewrite is otherwise unusable
by the consumer it was written for. An editor turning an edit into the smallest possible
change needs three things: WHICH objects will be rewritten, WHAT text each becomes, and
WHERE the old one was. The first two are public in both languages; the third was
reachable only through the anchoring, which is internal in both on purpose. Without it a
consumer writes the whole file and diffs it, which is the work the first name exists to
avoid, so publishing that name and withholding this one left the capability half-built.
Offsets into the retained text, not a line and column. A line and column is a rendering
of the same fact and a consumer that wants one can compute it from text it already
holds, while the reverse costs a scan. TypeScript's Region is already public and
already means exactly this, and carries the line-and-column conversion beside it.
The Python name for the range is SourceSpan rather than Region, which in a building
energy library reads as a piece of a surface. TypeScript keeps Region, where it has
meant a span of the syntax layer since feature 005 and where renaming it now would break
a published surface to fix a collision that language does not have.
Nothing here publishes the anchoring itself. Both languages answer the question and keep
the structure that answers it internal, which is the same line the retained source
structure draws.
one object rendered as a preserving write would render it
The text a preserving write would put in the range where an object's characters sit in
the retained source returns. The third leg, and without it the other two do not carry
weight.
Turning an edit into the smallest possible change to a file takes three things: WHICH
objects will be rewritten, WHERE the old one was, and WHAT text each becomes. The first
two were registered before this one and neither is usable alone. The ordinary per-object
writer is not the third: a preserving write hands it the author's own per-field
annotations, which are internal in both languages, so a consumer calling it with
arguments built by hand gets the author's units and notes back as generated labels.
Measured on one field of one object: !- North Axis {deg} came back !- North Axis. A
unit lost from an engineering model by an editor asked to save a file.
Registered because the alternative was a documented hazard. The accessor shipped with
the gap stated on it, and a doc comment is not a load-bearing place for a correctness
constraint; the same argument was accepted about what the objects a preserving write
will rewrite does not answer. Both language service and web editor readers reached the
same conclusion independently, that the range alone changes nothing they would build.
Takes the same write options as the whole-document write and resolves them the same way, so the two agree by construction rather than by a consumer reproducing the defaulting. Answers nothing for an object the retained source does not hold, which is the same set the range accessor declines.
No trailing line break. The range it fills ends at the terminator or at the comment on that line, and the break that follows is the first character of what separates one object from the next, which a preserving write leaves in place.
a half-open range of the source text
What where an object's characters sit in the retained source returns: start and
end as offsets into the retained text, end exclusive.
A frozen record in both languages rather than a tuple or a pair of arguments, so that a
consumer reading span.start cannot transpose it, and so that a later addition beside
the two offsets is not a breaking change.
TypeScript's already existed and is already exported; this entry records it rather than introduces it. The differing spelling is argued under the accessor's entry.
the original source text
The text the document was read from, kept only when the read asked for it. Present in both formats: for the text format it is the syntax layer's own text, and for the object notation it is the whole of what preservation has to work with, which is why that format's terms are all-or-nothing.
Divergent until governance-2026.13, and only because the second language had nothing
to put in the column. It is the minimum a consumer needs to answer whether a write will
preserve without reaching into the layer, which is why it is the one piece of the
retained material that is public on both sides.
a document's reference graph
The live ReferenceGraph for the document, under the same name on both sides. The
queries hung on it differ, which is recorded separately under objects referencing a
name and an object's outgoing references; the accessor itself does not.
one schedule by name
Registered before the TypeScript side is written, with the schedules group above.
Case-insensitive, because EnergyPlus resolves names that way, and returns the language's
own absent value on a miss: None in Python, undefined in TypeScript, as one object,
or nothing records.
the schedules a model uses
Registered before the TypeScript side is written, with the schedules group above. The names of schedules something in the model actually references, answered from the reference graph rather than by scanning.
a zone's surfaces
Registered before the TypeScript side is written, with the geometry group above, whose
get_* to get* pairing it follows.
copy a document
Registered before the TypeScript side is written. A deep, independent copy, which is what makes parametric work possible without reparsing the baseline. Python preserves the strict mode on the copy; the TypeScript port has no such mode to preserve.
create schedule type limits
Registered before it is written, with the schedule builders above.
Where the two libraries differ, and why¶
Each difference below stays. None is a defect and none is a candidate for a future rename: forcing either spelling onto the other language makes it wrong there, and wrong in a way that language's readers would feel on every line.
Acronym casing¶
| Python | TypeScript |
|---|---|
IDFObject |
IdfObject |
PEP 8 capitalises every letter of an acronym inside a CapWords name, which gives
IDFObject. The Google TypeScript style guide treats an abbreviation as an ordinary
word, which gives IdfObject. Each spelling is the correct one in its own ecosystem,
and forcing either onto the other makes it wrong there and inconsistent with every
neighbouring library its users already read.
The rule, not the single name: this governs IDF* against Idf* across both surfaces.
The document class¶
| Python | TypeScript |
|---|---|
IDFDocument |
IdfDocument |
The acronym casing rule above. TypeScript's rename count is 1: the package shipped
IDFDocument alongside IdfObject, IdfCollection, and IdfParseError, so the old
spelling contradicted both its own neighbours and the Google rule. The rename batch
fixes it, once.
Breaking for anyone importing IDFDocument from @idfkit/core@0.1.0. Recorded in the
changelog as such.
The collection class¶
| Python | TypeScript |
|---|---|
IDFCollection |
IdfCollection |
The acronym casing rule above. Both sides already spell it correctly for their ecosystem.
The parse error type¶
| Python | TypeScript |
|---|---|
IDFParseError |
IdfParseError |
The acronym casing rule above. Both sides already spell it correctly for their ecosystem.
Every object of a type¶
| Python | TypeScript |
|---|---|
doc["Zone"] |
doc.all('Zone') |
Python has operator overloading and __getitem__ is the idiomatic spelling of keyed
access; TypeScript has no such operator, and an index signature on a class cannot carry
the generic return type that makes the call statically checked against the generated
type map. all() is a method because it has to be, and it is typed because that is the
point of the type maps.
This is a syntactic divergence forced by the languages, not a naming disagreement, so neither side is a candidate for renaming to match the other.
One object, or an error¶
| Python | TypeScript |
|---|---|
col[name] |
col.require(name) |
Same reason as doc["Zone"]: Python spells throwing lookup with the subscript operator,
which raises KeyError on a miss and is what every Python reader expects of [].
TypeScript has no operator that can throw here, so the throwing variant needs a name,
and require says what it does at the call site.
The contract is identical on both sides, and identical to get except for the miss:
get returns the absent value, this raises.
Collection to a sequence¶
| Python | TypeScript |
|---|---|
to_list() |
toArray() |
The sequence type is named differently by the two languages. Python's is list and
to_list is the conventional spelling; JavaScript's is Array and toArray matches
Array.from, toSorted, and every neighbouring API. Renaming either to the other's
word would name a type that does not exist in that language.
An object's type name¶
| Python | TypeScript |
|---|---|
obj.obj_type |
obj.typeName |
Python's obj_type avoids shadowing the builtin type, which is the standard Python
workaround and reads naturally in snake_case. TypeScript has no such collision, and
typeName is clearer than objType in a language where type is a keyword only in
type position. Both name the same string, the EnergyPlus object type as the schema
spells it.
Version¶
| Python | TypeScript |
|---|---|
doc.version |
doc.version |
Python returns tuple[int, int, int], for example (26, 1, 0). TypeScript returns
string, for example "26.1.0".
This is worse than a rename and is recorded rather than reconciled. The name is
identical, the prose describing it is identical, and the value behaves differently, so
code written from one side's documentation compiles against the other and is wrong at
runtime. Each language keeps its idiomatic type: Python's tuple compares and sorts
correctly with no helper, which is why LATEST_VERSION, MINIMUM_VERSION, and
find_closest_version are all tuples; TypeScript has no comparable tuple literal and
its schema keys, resolveVersion, compareVersions, and versionKey are all strings
already.
The STRING is canonical for anything crossing the boundary, because IDF text, epJSON,
and the schema keys are strings already. Python renders it with the module-level
version_string(version). That is a function, not a property: there is no
doc.version_string, and asking for one raises.
WHAT THE CORPUS ACTUALLY PROVES, because an overstatement here is worse than a silence.
The case is cases/versions-canonical-string. It asserts that the version identifier a
file carries survives verbatim into the canonical epJSON in both libraries, which pins
the text form where a version crosses the boundary inside a document. It does NOT call
either accessor. None of the three shipping assertions reads doc.version or renders
it, so the two libraries could disagree about what the accessor returns, or about what
version_string renders, and every assertion would stay green. The case states that
limit itself, in its own why, so this entry is not reading against it.
Closing the gap needs an observation the case format cannot express: a check that calls
the accessor on both sides and compares the rendered strings. The corpus reserves
checks/ for precisely that and has no member in it yet. Until one exists, this
divergence is documented and only partly tested, and this entry says so rather than
claiming a proof the corpus does not carry (FR-004, SC-004).
Canonical form across the boundary: string.
No signature changes anywhere. This entry is documentation and a partial corpus case, not a rename.
Nothing here is a claim about writer OUTPUT. Written from the same document, the two
libraries produce IDF text that differs in indentation, object ordering, number
rendering, the generator header, field-comment casing, and the comment on each
extensible repeat. That is a behaviour difference rather than a naming one, so it is
recorded where behaviour differences belong: the parity ledger's write capability,
which lists every one of them and states that no corpus assertion covers any of them.
All input and output¶
| Python | TypeScript |
|---|---|
synchronous |
asynchronous |
Python's file API is synchronous, its ecosystem is synchronous by default, and
load_idf(path) returning a document is what every Python reader expects. TypeScript's
core is deliberately synchronous and pure so that it runs unchanged in a browser, a
worker, or an edge runtime; every operation that touches a disk or a network is
therefore pushed to @idfkit/core/node and is async, which is the only shape a
portable core can have without forcing node:fs into browser bundles.
Making Python async would be wrong for its ecosystem; making the TypeScript core sync would either break the browser target or force the whole portable surface to be async just in case. The split is the design, not an accident.
The verb pairs still align across the boundary: parse and write are sync and pure in
BOTH languages, and only load and save differ by being awaited in TypeScript.
The object class¶
| Python | TypeScript |
|---|---|
IDFObject |
IdfObject |
The acronym casing rule recorded above, which names this very pair as its exemplar. Both sides already spell it correctly for their ecosystem, and neither is a candidate for renaming to match the other.
The acronym casing entry states the rule; this entry is the class it governs, so that
the object class has a concept of its own in the same way the document class, the
collection class, and the parse error type each do.
An object's extensible rows¶
| Python | TypeScript |
|---|---|
obj.<extensible key> |
obj.extensible |
The two libraries disagree about the SHAPE of the access, not only about its spelling: Python has one named accessor per extensible type, TypeScript has one generic getter for all of them. Neither could take the other's form without becoming wrong in its own ecosystem, so this is recorded rather than reconciled.
Python names the rows after the field group that holds them. The schema gives every
extensible type one wrapper key, and Python exposes that key as an attribute:
surface.vertices, schedule.data, branchlist.branches. Each returns an
ExtensibleList view over the underlying list, and the generated stubs narrow the item
type per object, so surface.vertices is typed ExtensibleList[BuildingSurfaceVertex]
and an IDE completes vertex_x_coordinate on each row. Collapsing that to a single
generic accessor would throw the narrowing away and would read as a foreign word in a
language whose objects already answer to their own field names.
TypeScript exposes obj.extensible, returning ExtensibleGroup[]: the underlying array
itself, so pushing to it mutates the object. Generic is the only shape available there,
because the type parameterisation lives elsewhere. A document carries a type map, the
per-object field names come from @idfkit/types-v26-1 or its sibling, and the default
is UntypedMap, where a row is Record<string, string | number> and no per-key
accessor could be typed at all. Generating one accessor per EnergyPlus type per version
onto the object class is the 5.3 MB of type maps the core deliberately does not carry
(FR-039, FR-040).
Registered here because it was missing, not because anything changed. The row type above
aligns and the accessor never did, and a name absent from this file is not finished
(Constitution I, SC-001). Neither spelling moves: rename_count stays at 0 on both
sides.
The return values differ with the accessors. Python's ExtensibleList is a view
supporting indexing, iteration, append, insert, delete, pop, clear, extend, equality,
and bulk replace, and it bumps the owner's mutation version on every write. TypeScript
hands back the live array and needs no wrapper type to do it.
Python's obj[wrapper_key] reaches the same rows and is not a second public name for
the concept (FR-005). It is the ONLY path for the types whose wrapper key collides with
a property of the object, Schedule:Day:Interval and Schedule:Compact both spelling
theirs data; for every other type it resolves to the attribute above. Subscript
syntax, not a second word.
A view over an object's extensible rows¶
| Python | TypeScript |
|---|---|
idfkit.objects.ExtensibleList |
absent |
Python names this type because its accessor returns a view rather than the rows
themselves: ExtensibleList is a live window onto the owning object's field storage,
generic in the row type so surface.vertices narrows to
ExtensibleList[BuildingSurfaceVertex]. The view has to be a named class because it has
to exist: something must hold the owner, the wrapper key, and the inner field names, and
must write back through to the object when a row is assigned.
TypeScript has nothing to name. obj.extensible hands back the underlying
ExtensibleGroup[] array itself, so the language's own array type already is the view
and pushing to it mutates the object. Introducing a wrapper class there would add a
layer that Python needs only because its accessor cannot return storage directly.
Registered because the row type (an extensible group) and the accessor (an object's
extensible rows) both are, and the return type sitting between them was the only part
of the mechanism absent from this file. Nothing changed; it was missing (Constitution I,
SC-001).
ExtensibleList is reached at idfkit.objects.ExtensibleList, and is in that module's
__all__.
Create a new document¶
| Python | TypeScript |
|---|---|
new_document |
new IdfDocument(schema) |
Python offers a module-level factory because the constructor needs a loaded schema the
caller would otherwise fetch itself, and because loading a schema in Python is
synchronous, so the factory can do it. new_document() also seeds the baseline
singletons a usable model needs: Version, Building, SimulationControl, and
GlobalGeometryRules.
TypeScript loads a schema asynchronously from a BundleSource, and a constructor cannot
await, so the caller loads the schema first and hands it over. A synchronous factory is
not available to write there, which makes new both the idiomatic and the only possible
spelling. This is the input and output divergence recorded above, seen from the
construction side.
Objects referencing a name¶
| Python | TypeScript |
|---|---|
get_referencing |
referencingObjects |
The receiver carries the difference, not the operation. Python hangs the query on the
document, IDFDocument.get_referencing(name), because the graph is reached through the
document there. TypeScript exposes the graph itself as doc.references and hangs the
query on it, doc.references.referencingObjects(name), where repeating the word would
read as references.getReferencing. Each spelling says exactly what its receiver does
not already say, and aligning them would make one of the two stutter.
Load a schema for a version¶
| Python | TypeScript |
|---|---|
get_schema |
SchemaBundle.load |
Python ships every schema inside the package and reads it from disk synchronously, so a
module-level get_schema(version) is enough and is what a Python reader expects.
TypeScript cannot assume a filesystem: a browser fetches the bundle over HTTP and a
bundler-driven app resolves it through import(), so the caller constructs a
SchemaBundle over a BundleSource and loads from that. The operation is a method on
the object that owns the source because there is no process-wide source that would let
it be a free function. This is the input and output divergence recorded above.
Load the schema prose¶
| Python | TypeScript |
|---|---|
| absent | SchemaBundle.loadProse |
JavaScript deduplicates every memo and note of all seventeen schemas into one side file
the manifests carry indices into, and reads it on demand. Python has no counterpart
because it needs none: it ships the full schemas on disk and reads note and memo
straight off the field record it already holds, so there is nothing to load and no
moment at which to load it.
The same divergence as SchemaBundle.load against get_schema, and for the same
reason. The prose is roughly 175 KB the parse path never touches, and a reader who only
parses a model should not pay for a sentence of English. Keeping the fetch separate is
what lets the parse path stay unaware the pool exists.
Not a capability difference: describe_object_type reports the same prose in both
languages, and the corpus compares a digest over every memo and note of all 858 types.
Only the moment of loading differs.
The schema prose already loaded¶
| Python | TypeScript |
|---|---|
| absent | SchemaBundle.prose |
The synchronous half of SchemaBundle.loadProse, and the counterpart of
SchemaBundle.loaded. Python needs neither: its prose is on the record it already
holds.
It exists because every reader of prose in JavaScript is synchronous.
describeObjectType takes a pool rather than fetching one, and every answer in
@idfkit/language is a pure function so an editor server can answer a cursor without
holding a thread. A consumer loads once when a document arrives and reads here on the
request path. Without the pair it would either await inside a path it keeps synchronous
or build a second cache beside the one the bundle already has.
The supported version list¶
| Python | TypeScript |
|---|---|
ENERGYPLUS_VERSIONS |
SchemaBundle.versions |
Python's supported set is a constant because the schemas ship with the package, so the
answer is known at import time. TypeScript's depends on which bundle the caller
supplied, so it can only be a query on that bundle, and it is asynchronous for the same
reason SchemaBundle.load is. A constant there would name a set the library does not
control.
Python's members are version tuples and TypeScript's are strings, which is the version
divergence recorded above. The string is canonical for anything crossing the boundary.
Canonical form across the boundary: string.
Resolve a version string¶
| Python | TypeScript |
|---|---|
find_closest_version |
resolveVersion |
Both answer the same question: given a version the caller has in hand, which supported
release should the model be read as. Python's takes the version tuple and consults the
constant list, and closest names what it does when the exact release is not supported.
TypeScript's takes the detected string and the list the bundle reports, so both are
arguments, and resolve names the same operation over the string form that is canonical
across the boundary.
Neither signature exists in the other language, so neither name is a candidate for renaming to match: the Python name would describe a search over a list TypeScript does not have, and the TypeScript name would describe a resolution Python does not perform.
Generated object types¶
| Python | TypeScript |
|---|---|
idfkit._generated_types |
@idfkit/types-v26-1 |
Python generates one stub set at a time, ships it for the newest EnergyPlus release, and
applies it implicitly to every document through idfkit/document.pyi. TypeScript emits
one type map per version and the caller 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.
Each mechanism is the one its type checker allows. Python cannot select a stub set per
value, so an implicit set is the only thing available; TypeScript has generics, so an
opt-in map is both possible and necessary for version-generic code. The consequence for
a reader is recorded in the parity ledger under generated-object-types rather than
left here.
The TypeScript name was @idfkit/core/types until the maps were split out of core,
which took @idfkit/core from 7.2 MB to 285 KB and made the types an opt-in install.
There is one package per EnergyPlus version, @idfkit/types-v26-1 and
@idfkit/types-v9-4 today, and this entry names the package per EnergyPlus version:
@idfkit/types-v26-1 and @idfkit/types-v9-4 today.
This entry names one of those packages rather than a @idfkit/types-* pattern, because
the gate resolves a registered name against the public surface and a pattern matches
nothing. It was tried: the gate reports "registered as @idfkit/types-* with a rename
already spent, and no such name is public", which is the correct answer.
Naming one version does not put the entry on a treadmill. @idfkit/types-v26-1 stays
published and resolvable once it exists, so a later @idfkit/types-v26-2 is an addition
and not a rename, and this entry never needs touching again. That matters here because
rename_count is a budget of one and this entry has now spent it: a second change would
need an amendment reviewed by both languages, whatever its merits.
Those packages are deliberately outside governs. They hold roughly 1,676 generated
interface names, one per EnergyPlus object type, which are schema output rather than
designed vocabulary. The register governs names somebody chose; putting these inside it
would demand a register entry per EnergyPlus object and say nothing about whether the
two libraries agree. The naming gate counts them as outside the governs list and does
not fail, which is the intended behaviour and not an oversight.
A version type map¶
| Python | TypeScript |
|---|---|
| absent | AnyTypeMap |
Absent from Python, and correctly so. TypeScript names the map because the caller selects one and passes it as a type argument. Python's stubs apply to every document with nothing for a caller to select, so there is no value and no type to name, and a Python counterpart would name a choice the Python type checker cannot express.
Download a weather file¶
| Python | TypeScript |
|---|---|
WeatherDownloader.download |
fetchWeatherFiles |
Python's downloader is an object because it owns state a Python caller expects it to
own: a cache directory, its own session, and the settings that govern both. TypeScript's
is a free function because it owns none of that. It reaches the network through the
global fetch, which the caller overrides per call, and it caches nothing, so there is
no instance for a method to hang on and fetch names the platform primitive a
JavaScript reader already knows.
The packaging and freshness consequences are recorded in the parity ledger under
weather-index.
Refresh the station index¶
| Python | TypeScript |
|---|---|
| absent | refreshStationIndex |
Absent from Python because Python does it without being asked. StationIndex.load()
probes upstream at most once every 24 hours, warns when the bundled or cached index is
behind, records the check under the cache directory, and is turned off with
IDFKIT_NO_WEATHER_UPDATE_CHECK, so a Python caller needs no name for the operation.
JavaScript has no writable cache directory to throttle against in a browser, so the same nudge cannot exist there. It exposes the refresh explicitly instead and does nothing on its own. A Python counterpart would be a second public path to something already automatic, which FR-005 prohibits.
Which field a text search matched¶
| Python | TypeScript |
|---|---|
| absent | MatchField |
The same six strings on both sides: wmo, name, state, country, filename, and
the empty string for no match. TypeScript names the union because it appears in a
checked signature, on SearchResult.matchField, so a switch over it is exhaustive and
a misspelled case is a compile error.
Python annotates SearchResult.match_field as str and documents the same values on
the field itself. A Literal alias there is a fair thing to want and is additive rather
than part of this feature, so this is recorded as a divergence rather than as an
exclusion: it is a name Python may grow later, not one it must never have. Registering
the TypeScript spelling fixes it either way.
The station wire record¶
| Python | TypeScript |
|---|---|
| absent | StationRecord |
One station as stations.json.gz stores it, with the snake_case keys both libraries
read and write, so that an index refreshed by either side loads in the other. TypeScript
names the record because WeatherStation.fromJSON and toJSON carry it in their
signatures, and because those keys deliberately do not match the camelCase class they
convert to, which is exactly the mismatch a checked type is for.
Python moves the same content through WeatherStation.to_dict and from_dict as
dict[str, Any]. A TypedDict there would add real checking, so this too is a divergence
rather than an exclusion.
The station index wire form¶
| Python | TypeScript |
|---|---|
| absent | IndexData |
The whole serialized index: when it was built, the upstream Last-Modified values it
was built against, and the array of station records. TypeScript names it because
indexFromData takes it and loadStationIndex parses into it, so a caller that fetched
or imported the JSON itself hands over a value the compiler has checked.
Python reads the same file inside StationIndex.load and never hands the parsed payload
back, so there is no signature for the type to appear in. As with the station wire
record, that is a consequence of who opens the file rather than a name Python must
never have.
Parse a KML station index¶
| Python | TypeScript |
|---|---|
| absent | parseKml |
Both libraries build their index by regex over the upstream KML descriptions rather than through a DOM, and for the same reason: each description is a small HTML table inside a CDATA block, and a parser with no XML dependency runs in a worker or an edge runtime.
The step is public in TypeScript and private in Python because the two take different
inputs. TypeScript's takes the fetched text, which is the only form a browser ever has,
and the package's own offline index builder runs over that same text. Python's
_parse_kml takes a Path to a file StationIndex.refresh has already downloaded, so
a caller holding KML text could not call it in any case. Publishing it would mean
changing what it accepts first, which is a change to the code rather than to its name.
Read station metadata from a download URL¶
| Python | TypeScript |
|---|---|
| absent | parseUrlMetadata |
Country, state, city, and WMO number, recovered from the filename in a station's ZIP URL. Public in TypeScript because it is the half of index building a caller can reuse on its own, against a URL it already holds, with no KML anywhere in sight.
Python keeps _parse_url_metadata private beside _parse_kml, for the same reason that
one is private: the index-building path there is StationIndex.refresh, which owns the
whole download, and neither half of it is offered separately.
Build an index from index data¶
| Python | TypeScript |
|---|---|
| absent | indexFromData |
Turn an already-parsed IndexData into a searchable StationIndex, synchronously and
with no I/O. TypeScript needs the name because its callers legitimately hold the
payload: one has fetched it, one has imported it through a bundler, and
loadBundledIndex reads it off disk in Node. All three then need the same pure step.
Python has no such caller. StationIndex.load opens the file, decompresses it, and
returns the index in one call, and StationIndex.from_stations covers the other
construction anyone wants, from stations the caller built itself. A Python counterpart
would be a public name for the middle of a method nobody stands in.
The upstream index file list¶
| Python | TypeScript |
|---|---|
| absent | INDEX_FILES |
The ten regional KML files that together cover the globe, identical on both sides down
to the order. TypeScript exports the list because refreshStationIndex and
checkForUpdates both accept a baseUrl and a fetch, so a caller routing around the
missing CORS header has to know which ten files will be requested through its proxy.
Python's _INDEX_FILES is private because StationIndex.refresh offers no such
override: it owns the whole download, so the list names nothing a Python caller could
point anywhere else.
The upstream index base URL¶
| Python | TypeScript |
|---|---|
| absent | SOURCES_BASE_URL |
https://climate.onebuilding.org/sources, the same string on both sides. Public in
TypeScript for the reason the upstream index file list is public: it is the default a
caller replaces through baseUrl, and a proxy prefix is built by rewriting it.
Python's _SOURCES_BASE_URL is private for the reason its file list is private. Nothing
in the Python API accepts a base URL, so exporting the default would publish a constant
no signature takes.
Fetch a prebuilt station index¶
| Python | TypeScript |
|---|---|
| absent | loadStationIndex |
Fetch stations.json.gz from a URL, inflate it, and build the index. It exists because
a browser cannot read the copy inside node_modules: the page serves that file from its
own origin and points this at it.
Python needs no counterpart, and the absence must not be read as a gap. Its index is
installed with the package and StationIndex.load reads it from disk, so there is no
URL to load from. Neither library retrieves an index over the network to get started
(FR-043, FR-075): this names how a browser reaches the copy it was already shipped, not
a different starting point.
The bundled station index¶
| Python | TypeScript |
|---|---|
StationIndex.load |
loadBundledIndex |
The same operation, read the index shipped inside the package from disk with no network call, under two names that cannot be shared.
Python's is StationIndex.load because the index is always on disk there: load is the
register's verb for reading from a path, the class is the only place to reach it, and a
cached copy written by a refresh is preferred over the bundled one transparently.
TypeScript's says bundled because it has to distinguish itself from
loadStationIndex, which is the portable path and loads over the network, and because
it lives in @idfkit/weather/node where it touches node:fs. Spelling it load there
would name the disk path as the default in a package whose default runtime has no disk.
Python's load also fires the throttled freshness check recorded under refresh the
station index. The Node function does not, and has no cache directory to throttle
against.
Download a station's EPW file¶
| Python | TypeScript |
|---|---|
WeatherDownloader.get_epw |
fetchEpw |
The shortest path to one station's EPW, and the two libraries return different things
because their callers need different things. Python returns a Path, because the file
is in its cache and a path is what EnergyPlus is given. TypeScript returns the EPW text,
because a browser has no disk and the text is what @idfkit/engine takes.
The verbs follow the receivers, exactly as in download a weather file: get_ reads
something the downloader's cache already holds or will hold, and fetch names the
platform primitive the free function reaches through. Neither could take the other's
spelling without describing something it does not do.
Download an EPW file by filename¶
| Python | TypeScript |
|---|---|
WeatherDownloader.get_epw_by_filename |
fetchEpwByFilename |
Resolve a canonical EPW filename to a station through the index, then retrieve that
station's EPW. The same get_ against fetch split as download a station's EPW file
above, for the same reason.
The index arrives differently, which is the packaging asymmetry rather than a second naming disagreement: Python's is an optional keyword defaulting to the bundled index, because it can always find one on disk. TypeScript's is a required argument, because the caller had to obtain an index already and the function has nowhere to load one from.
Download a station's DDY file¶
| Python | TypeScript |
|---|---|
WeatherDownloader.get_ddy |
absent |
Python offers a per-member shortcut and TypeScript does not, and the cache is the
reason. Both libraries retrieve one ZIP archive per station and unpack its members.
Python's downloader keeps that archive's members on disk, so get_ddy after get_epw
costs a path lookup, and a function per member is worth having.
TypeScript keeps nothing. fetchWeatherFiles returns the EPW, DDY, and STAT text
together from the one archive it just fetched, so the DDY is already in hand and a
fetchDdy could only re-download the same bytes to hand back one of them. The concept
exists on both sides; only Python has a reason to give it a name.
Registered alongside download a station's EPW file, whose get_epw/fetchEpw pair
was already here while its DDY twin was not. See the weather-file-cache capability in
the parity ledger for the cache this turns on, which is Python's permanently (FR-031).
Download a DDY file by filename¶
| Python | TypeScript |
|---|---|
WeatherDownloader.get_ddy_by_filename |
absent |
Resolve a canonical EPW filename to a station through the index, then retrieve that
station's DDY. Python-only for the same reason as download a station's DDY file above:
the cache makes a per-member accessor cheap, and without one there is nothing for a
TypeScript counterpart to save.
The index arrives as an optional keyword defaulting to the bundled copy, matching
get_epw_by_filename rather than diverging from it.
Download a station's archive¶
| Python | TypeScript |
|---|---|
| absent | fetchWeatherArchive |
The low-level retrieval step: fetch the station's ZIP and return every member as raw
bytes. Public in TypeScript because it is the only way to reach the members with no
decoded convenience field, .clm, .wea, .rain, and .pvsyst among them, and
because a caller can hand the bytes anywhere.
Python reaches the same members as files. WeatherDownloader.download extracts the
archive into the cache and hands back WeatherFiles.zip_path beside the extracted
paths, so the archive is already addressable, and a method returning bytes would be a
second route to the same content in a language whose caller asked for paths.
The retrieved weather files¶
| Python | TypeScript |
|---|---|
WeatherFiles |
WeatherFiles |
Identical names, different values, and recorded rather than reconciled for the same
reason as version: files.epw is a Path in Python and the EPW text in TypeScript,
so code written from one side's documentation reads against the other and is wrong at
runtime.
Each is right where it lives, and the difference is the packaging asymmetry the parity
ledger records under weather-index. Python downloads into a cache directory, so paths
are what it has and what it should hand back, and zip_path and a guaranteed ddy
follow from the same fact. TypeScript writes nothing to disk, so text is the only thing
it can return, and it carries the undecoded members beside the three it decodes as a map
of bytes.
Renaming either side would not fix the collision and would spend a rename budget on
making it harder to find. write weather files to disk below names the step that turns
one into the other in Node.
Python's PartialWeatherFiles, returned by download(station, only=...), is the same
record with every path optional. It has no TypeScript counterpart and is not a gap:
selective extraction is a property of writing into a cache, and the JavaScript side
decodes from memory whatever the archive held.
Read a ZIP archive¶
| Python | TypeScript |
|---|---|
| absent | unzip |
Read a ZIP into a map of member name to bytes. TypeScript ships one and exports it
because JavaScript has no archive reader in its standard library and this package takes
no dependencies: it reads the central directory itself and inflates with
DecompressionStream('deflate-raw'), which browsers and Node 20 and later both have.
Python has zipfile in its standard library and WeatherDownloader uses it directly. A
public unzip there would be a wrapper over a module the caller can already import,
which is a public name for an import statement.
The injectable fetch¶
| Python | TypeScript |
|---|---|
| absent | FetchLike |
The subset of the fetch signature every network call in the package accepts, so that a
caller can substitute its own. It exists because climate.onebuilding.org sends no
Access-Control-Allow-Origin header: from a page, the only way to reach it is through a
proxy the caller supplies, and this type is what makes that argument checked rather than
hopeful.
Python has nothing to inject and needs no name for it. WeatherDownloader reaches the
network with urllib.request and owns its own requests, and a Python caller runs under
no same-origin policy, so a seam here would name an override nobody has a reason to
pass.
Write weather files to disk¶
| Python | TypeScript |
|---|---|
| absent | saveWeatherFiles |
Write a retrieved WeatherFiles into a directory, Latin-1, the encoding EPW uses, and
return the paths. It is a separate step, and a Node-only one, because the portable half
writes nothing to disk. That is the asymmetry the parity ledger records under
weather-index.
Python has no counterpart and lacks nothing. Retrieving and saving are one operation
there: WeatherDownloader.download puts the files in its cache directory on the way to
returning their paths, so a second name would be a public spelling for something already
done. The verb is still the register's save, so the two surfaces do not disagree about
the word for putting bytes on a disk, only about how many steps it takes to get there.
The written weather file paths¶
| Python | TypeScript |
|---|---|
| absent | SavedWeatherFiles |
Where saveWeatherFiles put each file, or the absent value for one the archive did not
carry. TypeScript needs a second record because its WeatherFiles holds text: the paths
do not exist until a Node caller has written them.
Python needs no second record because its WeatherFiles is paths already, which is the
the retrieved weather files divergence seen from the disk-writing side.
A schedule as a series¶
| Python | TypeScript |
|---|---|
to_series |
absent |
Absent from JavaScript, and not a gap. to_series returns a pandas Series, which is the
shape a Python analyst wants and is why the function exists. JavaScript has no pandas
and no equivalent labelled series type in its standard library, so a counterpart would
either invent a container this library has no business defining or wrap an array in a
name that promises more than it delivers. The year's values themselves are covered by a
schedule's values for a year, which does port.
The base error type¶
| Python | TypeScript |
|---|---|
IdfKitError |
absent |
Python gives every failure the library raises one catchable base, so except
IdfKitError holds a whole library's errors in one clause. That is the convention every
Python package a reader already imports follows, and it is why the sixteen classes in
exceptions.py all derive from it.
JavaScript's core throws exactly one type, IdfParseError, and only when
ParseOptions.strict is left on. Everything else it reports as a value. A base class
there would be a class with one subclass and nothing to catch, which names a hierarchy
the library does not have.
An unknown object type¶
| Python | TypeScript |
|---|---|
UnknownObjectTypeError |
absent |
Python raises, and this class also derives from KeyError so that doc["Nope"] behaves
the way a subscript is expected to. JavaScript reports the same condition as a
ParseDiagnostic, because a browser caller wants the partial document alongside the
finding rather than instead of it.
The canonical form is the shared diagnostic code the corpus compares on, so the two libraries agree about the condition even though only one of them names a class for it.
Canonical form across the boundary: UnknownObjectType.
An invalid field¶
| Python | TypeScript |
|---|---|
InvalidFieldError |
absent |
Python raises, and derives from AttributeError so that a mistyped field on an object
in a strict document fails the way attribute access is expected to. JavaScript catches
the same mistake in the type system: the generated type maps make zone.celing_height a
compile error, so the runtime condition it still reports is a diagnostic rather than a
throw.
The canonical form is the shared diagnostic code the corpus compares on.
Canonical form across the boundary: InvalidField.
A value outside its range¶
| Python | TypeScript |
|---|---|
RangeError |
absent |
Python raises when a field value falls outside the minimum or maximum the schema
declares. JavaScript reports it as a diagnostic, and cannot spell the class the same way
in any case: RangeError is a built-in global there, so a library type of that name
would shadow a language primitive inside every module that imported it.
The canonical form is the shared diagnostic code the corpus compares on, and it is
Range rather than RangeError for the same reason: stripping the suffix is what keeps
the code out of the way of the JavaScript global.
Canonical form across the boundary: Range.
A duplicate object¶
| Python | TypeScript |
|---|---|
DuplicateObjectError |
absent |
Python raises when a name is added twice within one object type. JavaScript reports it as a diagnostic and keeps parsing, which is what lets a viewer show a model that a strict reader would have refused.
The canonical form is the shared diagnostic code the corpus compares on.
Canonical form across the boundary: DuplicateObject.
A version mismatch¶
| Python | TypeScript |
|---|---|
VersionMismatchError |
absent |
Python raises when the version a caller asked for disagrees with the version the
document declares. JavaScript takes the version as an argument to schemaFor instead,
so the same disagreement is resolved at the call site rather than raised from inside the
parse.
The canonical form is the shared diagnostic code the corpus compares on.
Canonical form across the boundary: VersionMismatch.
An unsupported version¶
| Python | TypeScript |
|---|---|
UnsupportedVersionError |
absent |
Python raises when a version outside ENERGYPLUS_VERSIONS is requested, which it can do
because the supported set ships with the package and is known at import time.
JavaScript's supported set depends on the bundle the caller supplied, so
resolveVersion returns the absent value instead of throwing: it has no standing to
call a version unsupported, only to report that this bundle does not serve it.
The canonical form is the shared diagnostic code the corpus compares on.
Canonical form across the boundary: UnsupportedVersion.
A schema that cannot be found¶
| Python | TypeScript |
|---|---|
SchemaNotFoundError |
absent |
Python raises when the bundled schema file for a supported version is missing, which is
a packaging failure of the installed distribution. JavaScript reaches its schemas
through a BundleSource it does not own, so the same condition surfaces as a rejected
promise from the source rather than as a library type: the library cannot distinguish a
missing file from a network failure, and inventing a class that claimed to would be
wrong more often than right.
The canonical form is the shared diagnostic code the corpus compares on.
Canonical form across the boundary: SchemaNotFound.
A version that cannot be detected¶
| Python | TypeScript |
|---|---|
VersionNotFoundError |
absent |
Python raises when a file carries no readable Version object. TypeScript's
getIdfVersion and getEpJsonVersion return undefined for the same input, which
their own doc comments state as the contract: they exist to break the chicken-and-egg
between choosing a schema and reading the version, so a throw would put the caller back
where it started with nothing to fall back on.
No shared diagnostic code: the corpus vocabulary is derived from the parse hierarchy, and this condition is detected before a parse begins.
A validation that failed¶
| Python | TypeScript |
|---|---|
ValidationFailedError |
absent |
Python raises this when a caller asks for validation to be enforced rather than
reported. The TypeScript counterpart is already registered above and is a value, not a
throw: validateDocument returns a ValidationResult carrying ValidationError
findings, each with a severity and a location. Adding a thrown class beside it would
give the same failure two public shapes in one language.
A migration that failed¶
| Python | TypeScript |
|---|---|
MigrationError |
absent |
Python raises when a transition step fails. The registered TypeScript migration surface
reports the same failure through MigrationReport, which already carries the per-step
outcome, so the report is the counterpart and a thrown class would be a second way to
learn the same thing.
The TypeScript migration surface is registered above and is not written yet (FR-007).
A DDY file with no design days¶
| Python | TypeScript |
|---|---|
NoDesignDaysError |
absent |
Python raises when a DDY file carries no SizingPeriod:DesignDay, and the exception
carries the station name and a list of nearby stations that do have design conditions,
which is what makes it actionable rather than merely true. The registered TypeScript
design-day surface, DesignDayManager, is not written yet and reports its outcomes as
values like the rest of that side, so the empty file is a result there rather than a
class.
The newest supported version¶
| Python | TypeScript |
|---|---|
LATEST_VERSION |
SchemaBundle.latest |
Python's answer is a constant because the schemas ship with the package, so the newest
supported release is known at import time and LATEST_VERSION can be read without a
call. TypeScript's answer depends on which bundle the caller supplied, so it is a query
on that bundle and it is asynchronous for the same reason SchemaBundle.load is. A
constant there would name a release the library does not control.
This is the the supported version list divergence applied to one member of that list,
and the same canonical form applies: Python's value is a tuple, TypeScript's is a
string, and the string is what crosses the boundary.
Canonical form across the boundary: string.
The oldest supported version¶
| Python | TypeScript |
|---|---|
MINIMUM_VERSION |
absent |
Python names the oldest supported release as a constant, beside LATEST_VERSION, for
the same reason it can name the newest one. TypeScript names nothing, and correctly so:
SchemaBundle.versions returns the bundle's versions oldest first, so the answer is the
first element of a list the caller already has, and a second asynchronous method
returning it would be a public name for an array index.
Canonical form across the boundary: string.
Render a version as text¶
| Python | TypeScript |
|---|---|
version_string |
absent |
Absent from TypeScript, and not a gap. Python's version is a tuple, so turning it into
the canonical string needs a function, and the version entry above already names
version_string as the renderer the corpus asserts against. TypeScript's version is
that string already, so a counterpart would be the identity function under a name that
promised a conversion.
Canonical form across the boundary: string.
Whether a version is supported¶
| Python | TypeScript |
|---|---|
is_supported_version |
absent |
Absent from TypeScript, and not a gap. Python can answer the question from
ENERGYPLUS_VERSIONS, a constant, so a predicate over it is cheap and honest.
TypeScript's supported set belongs to the bundle the caller supplied, so the same
question is already answered by resolveVersion returning the absent value, and a
separate predicate would either ask the bundle twice or answer for a set the library
does not own.
One version's schema¶
| Python | TypeScript |
|---|---|
EpJSONSchema |
Schema |
The same object on both sides, one EnergyPlus version's schema, with the format in the
Python name and not in the TypeScript one. Python has a flat top-level namespace where
Schema alone would sit beside SchemaManager and say nothing about which of the two
formats it describes, so the qualifier earns its place. TypeScript's is exported from
@idfkit/schemas and is reached through that import, so EpJsonSchema would repeat the
package name at every call site and read as the stutter the objects referencing a name
entry rejects for the same reason.
The schema source¶
| Python | TypeScript |
|---|---|
SchemaManager |
SchemaBundle |
Each name says what its own object is, and the two objects are genuinely different.
Python's SchemaManager manages schemas the package already owns: it finds the file for
a version, parses it, and caches the result, so manager names the work. TypeScript's
SchemaBundle owns no schemas at all; it wraps a BundleSource the caller supplied and
serves versions out of it, so bundle names the thing it was handed.
Renaming either to the other would be a misdescription rather than an alignment: there is no bundle in Python and nothing to manage in TypeScript.
The process-wide schema source¶
| Python | TypeScript |
|---|---|
get_schema_manager |
schemas |
Both return the one instance a process should hold, so that loading 26.1.0 and then
9.4.0 costs far less than twice one version. The names diverge because the objects do,
which is the divergence recorded directly above: Python's accessor is named after the
SchemaManager it returns, and TypeScript's is named after the schemas the default
bundle serves.
TypeScript's also lives in @idfkit/core/node, because the default bundle reads from
disk. That is the input and output divergence, and it is why the plural noun rather than
a get verb: a caller writes schemas() once and hands the result to schemaFor,
where getSchemaBundle would name the container instead of the contents.
The IDF parser object¶
| Python | TypeScript |
|---|---|
IDFParser |
absent |
Python exposes the parser as an object because it holds state worth reusing: a file
path, an encoding, a memory-mapped view of the content, and a loaded schema, so a caller
parsing the same file at several versions constructs it once. parse_idf is the
one-line front door over it and is the name the documentation teaches.
TypeScript's parseIdf is a pure function that takes the text and the schema as
arguments and holds nothing between calls, which is what lets it run unchanged in a
browser or a worker. A class there would be a function with a constructor in front of
it.
parse IDF from a string above is the operation. This entry is the Python object that
implements it.
The difference between two schema versions¶
| Python | TypeScript |
|---|---|
| absent | SchemaDelta |
TypeScript names the delta because Schema hands it out, so it is part of the package's
public shape whether or not anyone asked for it. Python computes the same comparison
inside idfkit.compat and keeps the result type private, reaching it only through
diff_schemas, which is registered above.
Recorded rather than reconciled: the operation is registered and aligned, and only one side has a public name for what it returns.
Resolve the schema for a detected version¶
| Python | TypeScript |
|---|---|
| absent | schemaFor |
Absent from Python because Python has nothing to resolve. get_schema(version) takes
the version and returns the schema, synchronously, from the package's own files.
TypeScript needs the step and exports it: the version arrives as a string detected from
the file, the bundle is asynchronous and caller-supplied, and the caller may have
overridden the version or the bundle through LoadOptions. It is exported rather than
kept internal because a caller who parses text itself still has to take this step, and
taking it wrongly, loading 26.1 for a 9.0 file, mis-maps every positional field silently
instead of failing.
A reference edge¶
| Python | TypeScript |
|---|---|
| absent | ReferenceEdge |
One field of one object pointing at a name, with the repeat index when the field lives
inside an extensible group. TypeScript names the record because referencing,
retarget, and dangling all return it, and a reader meeting edge.from,
edge.field, edge.target at the call site can see what it is.
Python returns the same three values as an anonymous tuple[IDFObject, str, str] from
get_dangling_references and names nothing, so a caller unpacks by position. That is
worth recording rather than hiding: the two libraries carry the same information and
only one of them says what the parts are.
A Python counterpart would be additive and is not part of this feature. Registering the TypeScript name fixes the spelling either way.
An epJSON document value¶
| Python | TypeScript |
|---|---|
| absent | EpJson |
The epJSON document as a value, type to name to field values. TypeScript names it
because parseEpJson accepts it as an alternative to text and toEpJson returns it, so
it appears in two public signatures.
Python passes and returns plain dictionaries at the same points, and dict[str, Any] is
what a Python reader expects there. A type alias for it would be a name that adds no
checking, since the value is a nested Any at every level.
Serialize one object¶
| Python | TypeScript |
|---|---|
| absent | writeObject |
TypeScript exports the single-object writer because the whole-document writer is built out of it and a caller assembling IDF text by hand, a diff view or a snippet in a report, needs the same field alignment the document writer produces.
Python renders one object through IDFObject.__str__, which is the idiomatic place for
it: str(obj) and print(obj) already work, and a module-level write_object would be
a second public spelling of something the language already provides at the object.
Serialize a document to an epJSON value¶
| Python | TypeScript |
|---|---|
| absent | toEpJson |
TypeScript separates producing the epJSON value from producing epJSON text, because a JavaScript caller usually wants the value: it is already a JavaScript object, and stringifying it only to parse it again would be work for nothing.
Python's write_epjson returns the text, and the same conversion is reached at
idfkit.writers.EpJSONWriter.to_dict rather than as a module-level function. Promoting
it would be additive and is not part of this feature.
serialize epJSON to a string above is the text-producing operation and is aligned on
both sides.
The path a document was read from¶
| Python | TypeScript |
|---|---|
IDFDocument.filepath |
absent |
Python's load_idf opens the file itself, so the document it returns knows where it
came from and error messages can say so.
TypeScript's core never sees a path. @idfkit/core/node reads the text and hands it to
the pure parseIdf, which is what lets the same function run in a browser, so a
filepath on the document would be absent on every document a browser produced and
misleading on the rest. This is the input and output divergence, seen from the document.
Strict field access¶
| Python | TypeScript |
|---|---|
IDFDocument.strict |
absent |
Python's strict is a property of the document: with it on, reading or writing an
unknown field name raises InvalidFieldError instead of returning None. It is
read-only and set through the constructor, and it is also a type parameter, so the
checker knows which behaviour a document has.
TypeScript needs no such switch, because the generated type maps reject an unknown field at compile time, which is the same protection earlier and without a runtime mode.
Recorded because of a collision a reader will otherwise walk into: TypeScript's
ParseOptions.strict is a DIFFERENT setting with the same word, meaning throw on the
first parse diagnostic rather than collect them. The two are not the same switch and
neither is renamed here, because each is the correct word for its own setting; the
register's job is to say so out loud.
The retained source structure¶
| Python | TypeScript |
|---|---|
IDFDocument.cst |
absent |
Both languages retain the material a preserving write consumes, in different shapes.
Python holds a concrete syntax tree, a flat list of text runs each optionally anchored
to one object. TypeScript holds the syntax layer feature 005 built for this reader, plus
an index from statement to object, and keeps it internal: scanIdf already exports the
layer to a caller who wants one, so exposing a second path to it through the document
would publish a structure nothing reads. The capability is complete in both under
lossless-round-trip; only the internal representation differs.
The reason used to be that TypeScript had no lossless round-trip. That stopped being true when the preserving writer landed, and a divergence reason that describes an absence which has ended is worse than no reason at all: it reads as a gap nobody closed. This one states the difference that is actually there, which is a difference in representation behind an aligned capability.
The concept was spelled "the concrete syntax tree" until governance-2026.13. It was
renamed because the old spelling named Python's implementation of the idea rather than
the idea, so the TypeScript column could only ever be empty: there is no TypeScript
concrete syntax tree and there is not meant to be one.
Every collection in a document¶
| Python | TypeScript |
|---|---|
IDFDocument.collections |
absent |
Python hands out the whole map of object type to collection, which is a plain dict a Python reader iterates without being taught anything.
TypeScript exposes no equivalent. Its typed path is types() and all(), and handing
out the internal map would be an untyped second route to the same objects: exactly what
withdrawing collection() removed, recorded above under untyped collection access.
The object types present in a document¶
| Python | TypeScript |
|---|---|
IDFDocument.keys |
types |
Both return the object type names the document actually holds. Python's document
implements the mapping protocol, since doc["Zone"] is its idiomatic access, and keys
is the word that protocol requires: a Python reader who has seen doc[...] will reach
for keys(), values(), and items() without being told.
TypeScript's document is not a Map and cannot be one, for the reason recorded under
every object of a type, so keys there would promise a protocol the class does not
implement. types() says what the strings are instead.
Every non-empty collection¶
| Python | TypeScript |
|---|---|
IDFDocument.values |
absent |
The second member of Python's mapping protocol, beside keys and items. It exists
because the first one does: a class that offers keys() and stops is a mapping that
half works.
TypeScript implements no mapping protocol, so there is nothing for it to complete. The
same content is reached by mapping types() through all(), which stays typed, where a
values() returning every collection could not be.
Every object type and its collection¶
| Python | TypeScript |
|---|---|
IDFDocument.items |
absent |
The third member of Python's mapping protocol. Absent from TypeScript for the same
reason as values, and reached there by pairing types() with all().
An object's outgoing references¶
| Python | TypeScript |
|---|---|
IDFDocument.get_references |
referencedBy |
The receiver carries the difference, exactly as it does for objects referencing a
name, and this is that entry's mirror image. Python hangs the query on the document,
IDFDocument.get_references(obj), because the graph is reached through the document
there. TypeScript hangs it on the graph, doc.references.referencedBy(obj), where
getReferences would read as references.getReferences and say the word twice.
Both answer the same question, which names does this object point at, and the pair of them is why neither language can borrow the other's spelling without stuttering on one side.
A model's schedules by name¶
| Python | TypeScript |
|---|---|
IDFDocument.schedules_dict |
absent |
A cached map from schedule name to object, spanning every Schedule:* type at once,
which is what makes repeated schedule lookup cheap in a model with thousands of them.
Absent from TypeScript, where schedules are a Tier 2 capability the parity ledger
records as not yet ported. The name is not registered ahead, unlike the rest of the
schedules group, because the _dict suffix names a Python container: it is the
to_list against toArray case, and fixing a TypeScript spelling now would fix the
wrong one.
Every object in a document¶
| Python | TypeScript |
|---|---|
IDFDocument.all_objects |
objects |
Both walk every object in the document lazily. Python needs the all_ qualifier because
a bare objects on a document whose collections is a map of objects would read as
that map, and because objects_by_type sits beside it; it is a property rather than a
method because a lazy view spelled as a property is what a Python reader expects of an
iterable attribute.
TypeScript has neither neighbour, so objects() is unambiguous there, and it is a
method because a property returning a fresh generator on each read is a trap in
JavaScript: two reads would silently give two independent iterators.
An object's name changed¶
| Python | TypeScript |
|---|---|
IDFDocument.notify_name_change |
onNameChanged |
The same hook: an object tells its document that its name changed, so the collection can be re-keyed and every reference to the old name repointed. Both are public because the object calls them from outside the class, not because a user is meant to.
Python names the direction, notify_*, which is how a Python reader reads a callback
the callee invokes on its owner. TypeScript names the event, on*Changed, which is the
JavaScript convention and is what the ObjectOwner interface has to declare for a
detached object to stay free of the document.
An object's field changed¶
| Python | TypeScript |
|---|---|
IDFDocument.notify_reference_change |
onFieldChanged |
The other half of the hook pair above, and the same naming split, notify_* against
on*Changed.
The nouns differ too, and honestly: Python's fires only for fields the schema marks as
references, which is why it says reference, and TypeScript's fires for any field and
filters inside updateField, which is why it says field. Each name describes what its
own implementation does. Whether the two should agree on when they fire is a behaviour
question for the corpus, not a naming one, and renaming either without changing that
would make the name wrong.
Migrate a model without blocking¶
| Python | TypeScript |
|---|---|
async_migrate |
absent |
Python ships a second entry point because its ecosystem is synchronous by default:
migrate blocks while the transition binaries run, and an async caller needs a name of
its own rather than a thread.
TypeScript needs no second name because the registered migrate is async by
construction, like everything else that reaches a disk there. A TypeScript
asyncMigrate would be the same function under two names, which FR-005 prohibits. This
is the input and output divergence, seen from the migration side.
migrate a model to a newer version above is the operation. Its note already names this
pair; this entry gives the second name a concept of its own.
Column in a finding¶
| Python | TypeScript |
|---|---|
column |
column |
Python string indices are code points; JavaScript string indices are UTF-16 code units. The two agree for every character below the astral planes and differ for text containing anything above them, which in practice means an emoji in a comment.
Each is correct in its own ecosystem, and converting either would make positions wrong for that language's own consumers: the Language Server Protocol's default position encoding is UTF-16, as are Monaco's columns and CodeMirror's offsets, so the JavaScript value is what every JavaScript consumer needs unconverted.
Harmless in practice today: the corpus compares findings on (code, line, typeName) and never on a column. Registered because an unregistered divergence is indistinguishable from drift.
Canonical form across the boundary: 1-based, counted in the host language's own string index unit.
The canonical form across the boundary¶
A divergence in a name costs you a lookup. A divergence in a value costs you a bug, because the code reads the same in both languages and behaves differently. Where the two libraries hold one thing in two shapes, the register names the text form both sides render, and the conformance corpus asserts that they render it identically for the same model.
| Concept | Python | TypeScript | Canonical form |
|---|---|---|---|
| declared conformance level | idfkit.CONFORMANCE_LEVEL |
CONFORMANCE_LEVEL |
string |
| version | doc.version |
doc.version |
string |
| the supported version list | ENERGYPLUS_VERSIONS |
SchemaBundle.versions |
string |
| an unknown object type | UnknownObjectTypeError |
absent | UnknownObjectType |
| an invalid field | InvalidFieldError |
absent | InvalidField |
| a value outside its range | RangeError |
absent | Range |
| a duplicate object | DuplicateObjectError |
absent | DuplicateObject |
| a version mismatch | VersionMismatchError |
absent | VersionMismatch |
| an unsupported version | UnsupportedVersionError |
absent | UnsupportedVersion |
| a schema that cannot be found | SchemaNotFoundError |
absent | SchemaNotFound |
| the newest supported version | LATEST_VERSION |
SchemaBundle.latest |
string |
| the oldest supported version | MINIMUM_VERSION |
absent | string |
| detect a document version | get_idf_version |
getIdfVersion |
string |
| detect an epJSON document version | idfkit.epjson_parser.get_epjson_version |
getEpJsonVersion |
string |
| render a version as text | version_string |
absent | string |
| a source region | absent | Region |
half-open, offsets into the source text, an empty region where start equals end |
| a line and column | absent | LineColumn |
both counts 1-based; the column in the host language's own string index unit |
| column in a finding | column |
column |
1-based, counted in the host language's own string index unit |
The rule to carry away: keep each language's idiomatic shape in memory, and move the canonical form across the boundary. Anything written to a file, sent in a message, or compared in a test fixture is crossing the boundary.
Surfaces that stay in one language¶
Excluded does not mean "not yet". It is terminal: a counterpart appearing in the other language fails the gate, and adding one takes an amendment to the register reviewed by both languages. These surfaces are excluded because their names are quotations rather than choices. They reproduce another ecosystem's spelling, or they name a mechanism the other runtime does not have.
Untyped collection access¶
Python: IDFDocument.get_collection.
TypeScript: none, and never.
IdfDocument.collection() was public in @idfkit/core@0.1.0 and is withdrawn to
in-package use. Two public methods for one operation is what FR-005 prohibits, and
all() is the designed public surface: collection()'s own doc comment describes
itself as the escape hatch that exists so the single unavoidable cast lives in one
place. The version-generic case that would argue for keeping it is already covered,
because all() accepts unknown type names and returns them untyped.
Python's get_collection stays public: it is the named alternative to the doc["Zone"]
operator rather than a second untyped path, and Python has no generated type map
narrowing to lose. Recorded per research R1.
TypeScript's rename count is 1 and is spent on the withdrawal, which is as breaking as a
rename and must not happen twice. No third name is introduced: untyped() was
considered and rejected, because it would still be two public names and would invite
users onto the untyped path for a case all() already handles.
Eppy compatibility surface¶
Python, 15 names:
addidfobjectnewidfobjectpopidfobjectremoveidfobjectremoveallidfobjectsgetiddgroupdictgetobjectgetsurfacesidfobjectssaveassavecopytheidffieldnamesgetreferingobjsIDFObject.Name
TypeScript: none, and never.
Fifteen aliases that exist to let code written against eppy, a Python library, run
unchanged against idfkit. They are correct in Python precisely because they are NOT
idiomatic: they reproduce another Python library's spelling, lowercase-run-together and
all, so that a migration is a one-line import change. getreferingobjs even reproduces
eppy's misspelling of "referring", which is the clearest possible evidence that these
names are quotations rather than choices.
JavaScript has no eppy and no code to migrate, so a JavaScript counterpart would import a foreign Python library's spelling into a language that never had it, and would add fifteen public names that nothing calls. They are listed here individually, rather than described as a category, because a category is something a future contributor can misread as "port these too". A name written out is not portable by accident.
Excluded is terminal. A counterpart appearing on the TypeScript side fails the gate. Adding one requires amending this entry, reviewed by both languages.
Live at idfkit/src/idfkit/_compat.py (EppyDocumentMixin, mixed into IDFDocument)
and idfkit/src/idfkit/_compat_object.py (EppyObjectMixin, mixed into IDFObject).
The fifteen above are the register's named exclusions; the same exclusion covers the
whole of both mixins, including the further eppy members reachable from the same surface
(addidfobjects, removeidfobjects, copyidfobject, key, fieldvalues,
getfieldidd, getfieldidd_item, getrange, checkrange, save, run, update).
None of them crosses to TypeScript either.
The weather file cache¶
Python, 2 names:
PartialWeatherFilesWeatherDownloader.clear_cache
TypeScript: none, and never.
The on-disk cache for retrieved weather and design-day files, and the names that exist
only because Python keeps one. WeatherDownloader owns a directory, per platform by
default and moved with cache_dir or IDFKIT_CACHE_DIR; download consults it before
the network and skips the request on a hit within max_age; clear_cache empties it;
PartialWeatherFiles is the record selective extraction returns, and it is meaningful
only because a suffix skipped this time may already be in the directory from an earlier
call.
Excluded rather than divergent, and excluded is terminal here by requirement rather than by convention: FR-031 says the second language MUST NOT implement one. A counterpart appearing on the TypeScript side fails the gate (FR-006), which is the point of recording it this way.
@idfkit/weather is correct without one. It targets a browser, a worker, and an edge
runtime as well as Node; two of those have no directory it could own, and choosing one
in Node would make the package behave differently depending on where it runs.
saveWeatherFiles in @idfkit/weather/node writes a retrieved bundle where the caller
asks and is not a cache: it consults nothing on the way in and remembers nothing on the
way out.
The parity ledger records the capability as permanently single-language under
weather-file-cache, and the consequences for retrieval, paths against text, under
weather-download.
The weather options-object types¶
Python: none, and never.
TypeScript, 10 names:
SearchOptionsNearestOptionsFilterOptionsLoadIndexOptionsRefreshIndexOptionsFetchWeatherOptionsGeocodeOptionsDetectLocationOptionsSaveWeatherFilesOptionsWeatherStationFields
Ten interfaces naming argument objects, under one entry for the same reason the
options-object types covers the five in @idfkit/core: TypeScript has to name an
option bag, because it arrives as one argument and its type is what makes a misspelled
option a compile error instead of a silently ignored key, and Python passes the same
options as keyword arguments, which the language names and checks at the call site by
itself.
Nine are options in the plain sense, on search, nearest, filter,
loadStationIndex, refreshStationIndex, the three retrieval functions, geocode and
detectLocation, and saveWeatherFiles. The tenth, WeatherStationFields, is the same
mechanism at a constructor rather than at a call: new WeatherStation(fields) takes one
object, so its shape needs a name, where Python's WeatherStation is a frozen dataclass
whose constructor keywords are that list already.
Excluded is terminal. A Python counterpart appearing later fails the gate.
Option NAMES are not excluded here, only the containers that carry them. An option that
is a concept of its own gets its own entry, as preserve formatting on a round-trip
does, and the fetch these bags accept is typed by the injectable fetch above.
The local simulation surface¶
Python, 19 names:
simulatesimulate_batchasync_simulateasync_simulate_batchasync_simulate_batch_streamfind_energyplusexpand_objectsrun_preprocessingEnergyPlusConfigSimulationResultBatchResultSimulationCacheSimulationJobSimulationProgressESOResultCSVResultSQLResultTimeSeriesResultErrorReport
TypeScript: none, and never.
Running a locally installed EnergyPlus needs the installation and a subprocess to drive it, then reads the eplusout files that run leaves on disk. Neither the installation nor the subprocess exists in a browser, which is the runtime the JavaScript library targets, so these names are correct in Python precisely because they name a mechanism JavaScript cannot have.
JavaScript reaches EnergyPlus by the other mechanism, a WebAssembly build in
@idfkit/engine, which is a different capability rather than a workaround for this one.
Recording a JavaScript counterpart here would make one language read as deficient at
something it does not lack, which is what FR-068 and FR-078 forbid.
Narrowed on purpose. Choosing which variables to ask for and drawing the results are
both pure and both portable, so select output variables for a run and the plotting
group above are registered rather than excluded.
Live under idfkit/src/idfkit/simulation/. The names above are the register's named exclusions; the same exclusion covers the rest of that subpackage, including the FileSystem family, the progress reporters, and the remaining result readers. None of them crosses to TypeScript.
The vector image surface¶
Python, 9 names:
construction_to_svggenerate_construction_svgview_modelview_floor_planview_explodedview_normalsColorByModelViewConfigSVGConfig
TypeScript: none, and never.
All nine produce a vector image handed back 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, @idfkit/viewer, which
renders an interactive scene. The two are different capabilities, and merging them would
make each language read as missing something the other has.
Excluded is terminal here for the same reason it is terminal for the eppy surface: a JavaScript counterpart would add public names that nothing calls, in a runtime that already draws.
Live at idfkit/src/idfkit/visualization/, nine exports, verified against __all__.
construction_to_svg and generate_construction_svg are two public names for one
operation, which FR-005 prohibits; that duplicate is resolved by the change that
withdraws one, not here.
The local simulation error types¶
Python, 3 names:
EnergyPlusNotFoundErrorExpandObjectsErrorSimulationError
TypeScript: none, and never.
Three failures of the mechanism the local simulation surface already excludes: no
EnergyPlus installation was found, the ExpandObjects preprocessor failed, and the
simulation itself failed. Each names a subprocess or an installation, and neither exists
in a browser, so each is correct in Python precisely because it names something
JavaScript cannot have.
They are listed here rather than folded into the the local simulation surface entry
because they live in exceptions.py and are exported from the top level, so a reader
meets them on idfkit. completion rather than inside idfkit.simulation.
Excluded is terminal. A counterpart appearing on the TypeScript side fails the gate.
Version string ordering¶
Python: none, and never.
TypeScript, 2 names:
compareVersionsversionKey
Two JavaScript names that exist only because TypeScript's version is a string. Plain string ordering puts 8.9.0 after 22.1.0, so the library computes a numeric sort key and compares on that.
Python needs neither and must never grow them. Its version is tuple[int, int, int],
which the built-in comparison operators already order correctly, which is exactly why
ENERGYPLUS_VERSIONS, LATEST_VERSION, MINIMUM_VERSION, and find_closest_version
are all tuples. A Python compare_versions would be a public name for <.
Excluded is terminal. A Python counterpart appearing later fails the gate.
The schema bundle source surface¶
Python: none, and never.
TypeScript, 2 names:
BundleSourcehttpSource
The one runtime-specific seam in @idfkit/schemas: an interface saying where bundle
files come from, and the fetch-based implementation of it for browsers. They exist
because JavaScript runs in places that have no filesystem, and everything above the
interface is portable precisely because the seam is named.
Python must never grow a counterpart. Its schemas are installed with the package and
read from disk by SchemaManager, so a BundleSource there would be an abstraction
over one implementation, and an http_source would invite a Python caller to fetch over
the network what is already on their disk.
Excluded is terminal. A Python counterpart appearing later fails the gate.
The slimmed schema record types¶
Python: none, and never.
TypeScript, 2 names:
SlimTypeSlimField
The two record shapes @idfkit/schemas hands out: one object type as the bundle stores
it, and one field of it. They are public because a TypeScript caller reading
type.x.fields needs a checked shape for what it is reading, and because ObjectShape
is built from them.
Python reads the same content as the nested dictionaries the epJSON schema already is,
through EpJSONSchema's methods, and its reader-facing view of a type is
ObjectDescription and FieldDescription, which are registered above and are a
different thing: a description assembled for a human, not the storage record. Naming the
storage records in Python would add public types with no accessor of their own.
Excluded is terminal. A Python counterpart appearing later fails the gate.
The options-object types¶
Python: none, and never.
TypeScript, 5 names:
ParseOptionsLoadOptionsWriteIdfOptionsObjectWriteOptionsWriteEpJsonOptions
Five interfaces that name the option bags parseIdf, loadIdf, writeIdf,
writeObject, and writeEpJson accept. TypeScript has to name them: an options object
is one argument, and its type is what makes a misspelled option a compile error instead
of a silently ignored key.
Python passes the same options as keyword arguments, which are named and checked at the
call site by the language itself, so a Python counterpart would be a dataclass nobody
constructs, sitting between the caller and a signature that already reads better. The
add an object entry records the same difference from the other end, where Python's
keyword form has no TypeScript equivalent and the values arrive as an object literal
instead.
Excluded is terminal. A Python counterpart appearing later fails the gate.
Option NAMES still align: preserve_formatting against preserveFormatting is
registered above as its own concept. This entry excludes the container types, not the
options they carry.
The static typing surface¶
Python: none, and never.
TypeScript, 4 names:
TypeNameOfObjectOfValuesOfUntypedMap
Four type-level helpers that make doc.all('Zone') resolve to a collection of objects
with Zone's fields: the accepted type names for a map, the field interface for one of
them, the values accepted when creating one, and the map of a document with no version
types attached. They are erased at build time and none of them exists once the code is
running.
Python cannot express any of it. Its stubs apply implicitly to every document, with
nothing for a caller to select, which is the divergence generated object types and a
version type map already record. A Python counterpart would name a choice its type
checker cannot make.
Excluded is terminal. A Python counterpart appearing later fails the gate.
AnyTypeMap, the base constraint these are written against, has its own entry above.
The per-type prototype surface¶
Python: none, and never.
TypeScript, 3 names:
ObjectShapeshapeForshapeOf
JavaScript's answer to __getattr__. Python resolves zone.ceiling_height at runtime
through a lookup hook; the mechanical translation of that is a Proxy, which the
library deliberately does not use because proxies defeat inline caches and are invisible
to TypeScript, so nothing would complete. Instead each object type gets one prototype
carrying real accessors, built once and shared, and ObjectShape is that prototype plus
the field metadata the graph and the writer read off it.
Python already has the hook these three replace, so a counterpart there would be a second, slower way to do what the language does for free.
Excluded is terminal. A Python counterpart appearing later fails the gate.
The object-model value types¶
Python: none, and never.
TypeScript, 4 names:
FieldValueStoredValueFieldValuesObjectOwner
Three types naming what a field slot may hold and one interface naming what an object
needs from whatever owns it. TypeScript must name all four because they appear in
checked signatures, and ObjectOwner in particular is an interface rather than a
reference to the document class so that a detached object carries no dependency on the
document at all.
Python needs none of them. A field value is whatever the schema says it is, checked at
runtime by IDFObject, and an object reaches its document through an attribute rather
than through a declared interface. Naming them in Python would add four public types
that no signature would be improved by.
Excluded is terminal. A Python counterpart appearing later fails the gate.
The lexer surface¶
Python: none, and never.
TypeScript, 4 names:
lexRawObjectLexDiagnosticLexOptions
JavaScript splits IDF text into raw objects before any schema is involved, and exports that step: a hand-written character scan, the raw object it yields, and its diagnostic and option types. The split is what makes the diagnostics linear-time and line-accurate, and it is what lets a caller read a file whose version is not yet known.
Python tokenises inside IDFParser.parse with a regex and exposes no lexer. Exporting
one would be a second public parse path over the same text, which FR-005 prohibits, and
it would publish an internal representation that the retained source structure,
registered above as preserve formatting on a round-trip, already covers for the one
case a caller has a reason to see.
Excluded is terminal. A Python counterpart appearing later fails the gate.
Run the ExpandObjects preprocessor¶
Python: IDFDocument.expand.
TypeScript: none, and never.
Replaces HVACTemplate:* objects with their low-level equivalents and returns a new
document. It does that by running the ExpandObjects binary that ships with an EnergyPlus
installation, in a subprocess, which is the mechanism the local simulation surface
already excludes and which a browser does not have.
It is listed separately from that entry because it hangs on the document rather than
living in idfkit.simulation, so a reader meets it on doc. completion.
Excluded is terminal. A counterpart appearing on the TypeScript side fails the gate.
Second Python spellings of a registered concept¶
Python, 4 names:
ParseErrorintersect_matchIDFDocument.describeIDFDocument.objects_by_type
TypeScript: none, and never.
Four Python names, each a second public spelling of a concept whose registered name is elsewhere in this file. They are listed here so that the gate sees the whole surface, and listed individually so that no future contributor reads a category and ports them.
ParseError is bound to IdfKitError, not to IDFParseError, and is kept so that code
written against an older release still imports. Note the trap: the corpus's shared
diagnostic code ParseError is derived from IDFParseError, so the Python name and the
shared code are the same word for two different things.
intersect_match is a second name for intersect_and_match, which the intersect and
match surfaces entry already records as the surviving one.
IDFDocument.describe is the document-bound spelling of describe_object_type, which
the describe an object type entry already records as the registered name. The two take
different arguments, the method supplying the document's own schema, but they answer one
question.
IDFDocument.objects_by_type yields exactly what items returns, lazily rather than as
a list.
None of the four is a name JavaScript should acquire: each would import a Python duplicate into a language that never had the original. Excluded is terminal, and a counterpart appearing on the TypeScript side fails the gate.
This entry covers the surface. It does not resolve the FR-005 duplication, which is
resolved by the change that withdraws or renames each name, as the intersect and match
surfaces and the vector image surface entries already say for their own duplicates. A
withdrawal counts as a rename, so each of these has one budget to spend and no more.
Scan IDF text¶
Python: none, and never.
TypeScript: scanIdf.
Second-language-only by decision, recorded on the parity ledger as
idf-language-service. Listed here so that a Python counterpart is never added without
the ledger's never being amended first.
The one entry point to the syntax layer. Takes text and nothing else, and never throws.
Classify IDF text¶
Python: none, and never.
TypeScript: classify.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The complete-coverage view over a scanned
layer, yielding trivia as the complement of the stored tokens rather than storing it.
Listed here so that a Python counterpart is never added without the ledger's never
being amended first.
Line and column at an offset¶
Python: none, and never.
TypeScript: lineColumnAt.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". Turns an offset into the 1-based line and
column an editor draws with. Listed here so that a Python counterpart is never added
without the ledger's never being amended first.
Offset at a line and column¶
Python: none, and never.
TypeScript: offsetAt.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The inverse of lineColumnAt, for a consumer
whose editor speaks in line and column and whose service speaks in offsets. Listed here
so that a Python counterpart is never added without the ledger's never being amended
first.
A source region¶
Python: none, and never.
TypeScript: Region.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". A half-open span of the source text, and the
unit of every position this capability reports. Listed here so that a Python counterpart
is never added without the ledger's never being amended first.
A line and column¶
Python: none, and never.
TypeScript: LineColumn.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". Derived from a region rather than stored
beside one, because storing it would double the size of every region to hold two numbers
that are a function of one. Listed here so that a Python counterpart is never added
without the ledger's never being amended first.
The unit divergence itself is registered separately, under column in a finding.
A syntax token¶
Python: none, and never.
TypeScript: Token.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". One meaningful span of text with a
grammatical kind, materialised on demand from the layer's packed arrays rather than
stored as an object. Listed here so that a Python counterpart is never added without the
ledger's never being amended first.
A syntax token kind¶
Python: none, and never.
TypeScript: TokenKind.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The grammatical kinds a token can carry,
trivia included even though trivia is never stored. Listed here so that a Python
counterpart is never added without the ledger's never being amended first.
The syntax layer¶
Python: none, and never.
TypeScript: SyntaxLayer.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The whole scan: the text it was built from,
its statements, and its packed token store. Listed here so that a Python counterpart is
never added without the ledger's never being amended first.
Python has a formatting-preserving concrete syntax tree already, under
lossless-round-trip, and this is not it. That tree exists to be written back out; this
layer exists to be positioned against, holds no schema meaning, and is built only when a
caller names scanIdf.
A written statement¶
Python: none, and never.
TypeScript: Statement.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". One semicolon-terminated statement as it
appears in the text, carrying regions and no schema meaning: it is not an object in the
model, and a statement with the wrong number of fields is still a statement. Listed here
so that a Python counterpart is never added without the ledger's never being amended
first.
Schema prose pool¶
Python: none, and never.
TypeScript: ProsePool.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The deduplicated prose the schema bundle
carries, which describeObjectType already takes as an optional argument. It becomes a
public name here because explainAt cannot name its own optional prose parameter
without it.
Python has no counterpart because it needs none: its schema access reads prose directly
rather than through a pool, so there is nothing for a Python name to refer to. Listed
here so that a Python counterpart is never added without the ledger's never being
amended first.
Declared today at packages/core/src/introspect/describe.ts and not re-exported from
that package's root. Registering it is what lets the change that exports it pass the
naming gate.
Cursor context¶
Python: none, and never.
TypeScript: contextAt.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". Which statement an offset falls in, which
field, and which part, computed by a bounded backward scan rather than by building a
layer. Listed here so that a Python counterpart is never added without the ledger's
never being amended first.
The cursor context record¶
Python: none, and never.
TypeScript: CursorContext.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". What contextAt returns. A concept of its
own because the operation and its result are two public names, and FR-005 forbids one
concept carrying both. Listed here so that a Python counterpart is never added without
the ledger's never being amended first.
Completions at an offset¶
Python: none, and never.
TypeScript: completionsAt.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". What the schema permits at an offset, taken
from the schema itself and never from a second table. Listed here so that a Python
counterpart is never added without the ledger's never being amended first.
Explanation at an offset¶
Python: none, and never.
TypeScript: explainAt.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The schema's own facts about whatever is
under an offset, reported and never paraphrased. Listed here so that a Python
counterpart is never added without the ledger's never being amended first.
Declaration at an offset¶
Python: none, and never.
TypeScript: declarationAt.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". Where the name under an offset is declared,
or nothing when it is declared nowhere, which is the dangling-reference finding's answer
to give rather than this one's. Listed here so that a Python counterpart is never added
without the ledger's never being amended first.
Position findings¶
Python: none, and never.
TypeScript: findingsIn.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". Existing parse and validation findings with a
region attached. It adds no findings of its own and holds no second opinion about the
schema, which is what keeps this out of Python's way rather than duplicating it. Listed
here so that a Python counterpart is never added without the ledger's never being
amended first.
Position findings already in hand¶
Python: none, and never.
TypeScript: position.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The separately exposed half of findingsIn,
for a consumer that already holds findings from its own run and wants regions attached
without paying for a second parse. It is a distinct public name rather than an option on
findingsIn because the two take different inputs: one takes text and reads it, the
other takes findings and a layer the caller already built. Listed here so that a Python
counterpart is never added without the ledger's never being amended first.
A positioned finding¶
Python: none, and never.
TypeScript: PositionedFinding.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". An existing finding with a region and a
precision attached, generic over the finding type so that a parse diagnostic and a
validation error travel one path without either being modified. Listed here so that a
Python counterpart is never added without the ledger's never being amended first.
A completion offer¶
Python: none, and never.
TypeScript: Offer.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". One completion, carrying the region it would
replace so that a consumer renders and applies a list without measuring anything itself.
Listed here so that a Python counterpart is never added without the ledger's never
being amended first.
An explanation¶
Python: none, and never.
TypeScript: Explanation.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The region described, the schema's field
facts, its prose when the caller supplied a pool, and the manual location. Listed here
so that a Python counterpart is never added without the ledger's never being amended
first.
Carries FieldDescription and DocsUrl unchanged; both are already registered above.
A declaration site¶
Python: none, and never.
TypeScript: Declaration.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". Where a name is declared: the region of the
declaring statement's name field, and that statement's canonical type. Listed here so
that a Python counterpart is never added without the ledger's never being amended
first.
The completion options¶
Python: none, and never.
TypeScript: CompletionOptions.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The optional document and prose pool
completionsAt takes, an options-object rather than two more positional arguments.
Listed here so that a Python counterpart is never added without the ledger's never
being amended first.
A completion result¶
Python: none, and never.
TypeScript: CompletionResult.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". A discriminated union rather than an array,
so that "the schema constrains nothing here" and "I could not consult a schema" are
different answers instead of the same empty list. Listed here so that a Python
counterpart is never added without the ledger's never being amended first.
An explanation result¶
Python: none, and never.
TypeScript: ExplanationResult.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The same discriminated shape as
CompletionResult, for explainAt. Listed here so that a Python counterpart is never
added without the ledger's never being amended first.
A declaration result¶
Python: none, and never.
TypeScript: DeclarationResult.
Part of idf-language-service, second-language-only by decision and recorded on the
parity ledger with absence_kind = "never". The same discriminated shape again, for
declarationAt. Listed here so that a Python counterpart is never added without the
ledger's never being amended first.
Names that have spent their rename¶
Every name gets one rename during the unification. One. A name that has spent it is frozen: the gate blocks the change that would take it to a second, and unblocking it takes an amendment saying why the first rename was wrong. A user absorbs one rename with a changelog entry and a search-and-replace, and absorbs a second by concluding the library is unstable.
| Concept | Name | Language | Renames |
|---|---|---|---|
| write IDF to disk | save_idf |
Python | 1 |
| write epJSON to disk | save_epjson |
Python | 1 |
| the document class | IdfDocument |
TypeScript | 1 |
| untyped collection access | withdrawn | TypeScript | 1 |
| generated object types | @idfkit/types-v26-1 |
TypeScript | 1 |
| diagnostics from a parse | IdfParseError.diagnostics |
TypeScript | 1 |
| detect a document version | getIdfVersion |
TypeScript | 1 |
| detect an epJSON document version | getEpJsonVersion |
TypeScript | 1 |
A withdrawal counts as a rename. It is at least as breaking, and it must not happen twice to one name either.
Package names reserved before their ports¶
No package below is built yet, and the shared install map gains no entry for any of them: an entry that resolves to nothing breaks a clean install, which is worse than an unreserved name. The reservation exists because an npm package name cannot be corrected after publication, and a name chosen at the moment its port begins is a name chosen by one language under schedule pressure.
| Capability | npm package | Subpath | Mirrors | Tier | Built |
|---|---|---|---|---|---|
| schedules | @idfkit/schedules |
@idfkit/idfkit/schedules |
idfkit.schedules |
2 | not yet |
| geometry, geometry builders, surface matching, zoning | @idfkit/geometry |
@idfkit/idfkit/geometry |
idfkit.geometry, idfkit.geometry_builders, idfkit.surface_matching, idfkit.zoning |
2 | yes |
What the gate refuses¶
The gate that reads the register refuses a pull request when:
- A public name has no entry. It reports the name, its library, and the file it is exported from.
- A concept has two public names in one language. It reports the concept and both names.
- An excluded entry gains a counterpart in the other language. It reports the concept and the offending name.
- A divergent entry has no divergence_reason. It reports the concept.
- A change takes a name to rename_count 2. It reports the name and its first rename.
It passes when every public name in both libraries resolves to an entry.
Changing the register itself needs a maintainer of the other language, enforced by CODEOWNERS in both repositories.
Names that were never meant to be public are not registered to keep the gate quiet. They are deleted. A public surface that leaks an import or an assembly artefact fails outright, and adding an entry for one is the wrong fix.