Skip to content

corva-ai/corva-convert-units

Repository files navigation

corva-convert-units

Monorepo for unit conversion libraries used by Corva. A single set of shared JSON definitions powers both the JavaScript/TypeScript and Python packages.

Structure

definitions/     Shared JSON unit definitions
js/              npm package: @corva/convert-units
py/              PyPI package: corva-unit-converter
scripts/         Build and validation scripts

Packages

Package Language Registry Install
@corva/convert-units TypeScript/JS npm npm install @corva/convert-units
corva-unit-converter Python PyPI pip install corva-unit-converter

Usage

JavaScript / TypeScript

import {
  convert,
  getMeasures,
  describe,
  getUnit,
  getUnitForPair,
  listUnits,
  listUnitsWithAliases,
  possibilities,
  toBest,
  bucketMapping,
  getUnitKeyByAlias,
} from '@corva/convert-units';

convert(1, 'ft', 'm');                          // 0.3048
convert(100, 'C', 'F');                         // 212
convert(1, 'ft', 'm', 'length');                // 0.3048

getMeasures();                                  // ['length', 'pressure', ...]
describe('ft');                                 // { abbr: 'ft', measure: 'length', ... }
getUnit('ft');                                  // { abbr: 'ft', measure: 'length', system: 'imperial', unit: ... }
getUnitForPair('ft', 'm');                      // [{ abbr: 'ft', ... }, { abbr: 'm', ... }]
listUnits('length');                            // [{ abbr: 'mm', ... }, ...]
listUnitsWithAliases('length');                 // [{ abbr: 'm', aliases: ['meter', ...], ... }, ...]
possibilities('length');                        // ['mm', 'cm', 'm', 'km', ...]
toBest(100000, 'mm');                           // { val: 100, unit: 'm', ... }
bucketMapping;                                  // { 'Length': ['m', 'ft', ...], ... }
getUnitKeyByAlias('meter');                     // 'm'

Python

from corva_unit_converter import (
    convert,
    get_measures,
    describe,
    get_unit,
    get_unit_for_pair,
    list_units,
    list_units_with_aliases,
    possibilities,
    to_best,
    bucket_mapping,
    get_unit_key_by_alias,
)

convert(1, 'ft', 'm')                           # 0.3048
convert(100, 'C', 'F')                          # 212.0
convert(1, 'ft', 'm', measure='length')         # 0.3048

get_measures()                                  # ['length', 'pressure', ...]
describe('ft')                                  # {'abbr': 'ft', 'measure': 'length', ...}
get_unit('ft')                                  # {'abbr': 'ft', 'measure': 'length', 'system': 'imperial', ...}
get_unit_for_pair('ft', 'm')                    # ({'abbr': 'ft', ...}, {'abbr': 'm', ...})
list_units('length')                            # [{'abbr': 'mm', ...}, ...]
list_units_with_aliases('length')               # [{'abbr': 'm', 'aliases': ['meter', ...], ...}, ...]
possibilities('length')                         # ['mm', 'cm', 'm', 'km', ...]
to_best(100000, 'mm')                           # {'val': 100, 'unit': 'm', ...}
bucket_mapping()                                # {'Length': ['m', 'ft', ...], ...}
get_unit_key_by_alias('meter')                  # 'm'

Shared unit keys and measure priority

Some unit abbreviations appear in more than one measure with different anchor chains. For example, bbl exists in both volume (liquid barrel, 158.987 L) and gasVolume (gas-accounting barrel, MCF-based). When no measure is specified, the library resolves to the general-purpose measure using a fixed priority order:

Unit(s) Resolves to (JS) Resolves to (Python)
gal, bbl, m3, l, fl-oz, … volume volume
mV voltage voltage
% gasConcentration gasConcentration
g mass (gram) mass (gram)
kPa/m, psi/ft pressureGradient pressureGradient
ppm partsPer gasConcentration

Note

Cross-measure pairs still convert when a single measure defines both units — e.g. %→Fraction (both in proportion) and bbl→Mscf (both in gasVolume) work without an explicit measure.

Caution

ppm resolves differently in the two packages, and the scales are not the same: JS uses partsPer (1 ppm = 1 ppm, 1:1), while Python uses gasConcentration (1 ppm = 0.0001 %EMA). The same ppm conversion can return different values in JS and Python. Each package preserves its original library's behavior for backward compatibility. If your code runs in both languages, always pass an explicit measure when converting ppm.

To use a specialised measure, pass it explicitly:

// JS
convert(100, 'm3', 'bbl', 'gasVolume');   // gas-accounting barrels
# Python
convert(100, 'm3', 'bbl', measure='gas_volume')   # gas-accounting barrels

See definitions/SYNC_REPORT.md for the full list of shared unit keys and the rationale behind the priority order.

Development

Prerequisites

  • Node.js 24+ (see .nvmrc)
  • Python 3.9+
  • GNU Make

Setup

# JS
cd js && npm install

# Python
cd py && python3 -m venv .venv && .venv/bin/pip install -e ".[test]"

Commands

make sync-defs    # Copy definitions into packages (local only; CI does this automatically)
make test-js      # Run JS tests
make test-py      # Run Python tests
make test         # Run all tests (includes sync-defs automatically)
make validate     # Validate JSON definition files (also runs in CI on every PR)
make build-js     # Build JS package
make build-py     # Build Python package
make clean        # Remove build artifacts

Adding a new unit

  1. Edit the JSON file in definitions/ (or create a new one)
  2. Run make test to verify both packages (syncs definitions automatically)
  3. The formation_density measure is an alias for density (handled in loader code, not in JSON)

Release

Both packages use release-please with the linked-versions plugin. Merging to master creates a combined release PR. When merged, tags js-v* and python-v* trigger separate publish workflows to npm and PyPI.