EnergyUnits Architecture

Version: 0.2.0 Last Updated: 2026-02-15

This document describes the architecture, design decisions, and extensibility mechanisms of the EnergyUnits library.


Table of Contents

  1. Design Philosophy

  2. System Overview

  3. Core Concepts

  4. Architecture Patterns

  5. Conversion Pipeline

  6. Data-Driven Design

  7. Extensibility

  8. Testing Strategy

  9. Performance Considerations

  10. Design Decisions

  11. Future Enhancements


Design Philosophy

Core Principles

  1. Simplicity First: Single unified API (.to()) for all conversions

  2. Type Safety: Dimensional analysis prevents nonsensical operations

  3. Data-Driven: All configuration in JSON, zero code changes to extend

  4. Composable: Natural mathematical operations with automatic unit handling

  5. Explicit: Substance and basis conversions require explicit specification

  6. Traceable: All data sourced from authoritative references

Design Goals

  • Energy-focused: Purpose-built for energy system modeling, not general-purpose

  • No magic: Explicit conversion paths, clear error messages

  • Extensible: Add new units/substances without modifying code

  • Dependency-minimal: Core requires only NumPy; pandas optional

  • Production-ready: Comprehensive tests, error handling, documentation


System Overview

Component Architecture

┌─────────────────────────────────────────────────────────────┐
│                         User API                            │
│                    Quantity (quantity.py)                    │
│  • .to() - unified conversion method                        │
│  • Arithmetic operators (+, -, *, /)                        │
│  • Comparison operators (<, >, ==, ...)                     │
│  • Discovery: list_units(), list_substances(), etc.         │
│  • _repr_html_() for Jupyter notebooks                     │
└──────────────┬──────────────────────────────────────────────┘
               │
     ┌─────────┼─────────────────────────┐
     │         │                         │
┌────▼─────────┐  ┌─────────────────┐  ┌─▼───────────────────┐
│ UnitRegistry │  │  Unit Constants │  │ SubstanceRegistry   │
│ (registry.py)│  │  (units.py)     │  │  (substance.py)     │
│              │  │                 │  │                     │
│ • Dimensions │  │ • MWh, GJ, MW  │  │ • Heating values    │
│ • Conversions│  │ • USD, EUR     │  │ • Densities         │
│ • Dim. rules │  │ • IDE-friendly │  │ • Carbon content    │
│ • @lru_cache │  │   constants    │  │ • Combustion calcs  │
└──────┬───────┘  └─────────────────┘  └─────────────────────┘
       │
┌──────▼────────────┐  ┌────────────────────────┐
│ InflationRegistry │  │ ExchangeRateRegistry   │
│  (inflation.py)   │  │  (exchange_rate.py)    │
│                   │  │                        │
│ • Rates by year   │  │ • Year-dependent rates │
│ • Currency detect │  │ • USD/EUR/GBP/JPY/CNY │
│ • Cumulative calc │  │ • Cross-currency conv  │
└──────┬────────────┘  └───────────┬────────────┘
       │                           │
┌──────▼───────────────────────────▼──────────────┐
│                 Data Layer (JSON)                │
│  • units.json           - Unit definitions      │
│  • substances.json      - Fuel properties       │
│  • inflation.json       - Inflation rates       │
│  • exchange_rates.json  - Exchange rates        │
└─────────────────────────────────────────────────┘

Code Statistics

  • Total Python code: ~1,500 lines

  • Core modules: 6 (quantity, registry, substance, inflation, exchange_rate, units)

  • Test files: 17 with 270+ tests

  • Data files: 4 JSON configuration files


Core Concepts

1. Dimensions

Dimensions represent physical quantities and define what operations are valid.

Supported Dimensions:

  • ENERGY - Joules, Watt-hours (J, kJ, MJ, GJ, Wh, kWh, MWh, GWh, MMBTU)

  • POWER - Watts (W, kW, MW, GW, TW)

  • MASS - Grams, kilograms, tons (g, kg, t, Mt, Gt)

  • VOLUME - Liters, cubic meters (L, m3, barrel)

  • TIME - Seconds, hours, years (s, min, h, a)

  • CURRENCY - Dollars, Euros (USD, EUR, GBP, JPY, CNY)

  • Compound Dimensions - ENERGY_PER_MASS, CURRENCY_PER_POWER, etc.

Key Design: Dimensions enforce type safety. You cannot accidentally add energy to mass or multiply incompatible quantities.

2. Units

Units are specific measurements within a dimension.

# Same dimension - simple conversion
100 kWh == 360 MJ  # Both ENERGY dimension

# Different dimensions - requires context
1000 kg of coal  ? MWh  # Needs heating value (substance)

3. Substances

Substances provide physical/chemical properties for conversions that cross dimensions.

Properties:

  • hhv, lhv - Heating values (MJ/kg) for MASS ↔ ENERGY

  • density - Density (kg/m³) for MASS ↔ VOLUME

  • carbon_content - Carbon fraction for CO2 emission calculations

  • hydrogen_content, ash_content - For H2O and ash calculations

Example Substances:

  • Fossil fuels: coal, natural_gas, diesel, gasoline

  • Renewables: wood_pellets, hydrogen

  • Zero-carbon: wind, solar, hydro, nuclear

  • Products: CO2, H2O, ash

4. Basis

Basis specifies the heating value convention for energy calculations.

  • HHV (Higher Heating Value): Includes latent heat of water vapor condensation

  • LHV (Lower Heating Value): Excludes latent heat (water remains vapor)

Typical Differences:

  • Coal/Oil: LHV ≈ 95% of HHV

  • Natural gas: LHV ≈ 90% of HHV

  • Hydrogen: LHV ≈ 82% of HHV (18% difference)

Default: LHV (more conservative, matches most engineering practice)


Architecture Patterns

1. Registry Pattern

All three registries (UnitRegistry, SubstanceRegistry, InflationRegistry) follow the same pattern:

class Registry:
    def __init__(self):
        self._data = {}
        self._load_defaults()  # Load from JSON

    def _load_defaults(self):
        # Load bundled data

    def load_custom(self, file_path):
        # Extend with user data
        # Uses .update() - can override defaults

Benefits:

  • Singleton pattern for global state

  • Lazy loading on first import

  • Extensible without subclassing

  • Testable with custom data

2. Unified Conversion API

The .to() method is the single entry point for all conversions:

quantity.to(
    target_unit=None,       # Unit conversion
    basis=None,             # HHV/LHV conversion
    substance=None,         # Cross-substance conversion
    reference_year=None     # Inflation adjustment
)

Pipeline Order:

  1. Substance conversion (e.g., coal → CO2)

  2. Basis conversion (HHV ↔ LHV)

  3. Unit conversion (kWh → MJ)

  4. Reference year adjustment (2020 USD → 2025 USD)

Rationale: Sequential pipeline ensures predictable, composable conversions. Each stage operates on the result of the previous stage.

3. Dimensional Analysis

Automatic dimensional analysis in arithmetic operations:

power * time  energy    # MW * h → MWh (via rule)
energy / time  power    # MWh / h → MW (via rule)
(USD/kW) * MW  USD      # Smart cancellation

energy + mass  ERROR    # Prevented at runtime

Implementation:

  • Multiplication rules stored in JSON

  • Division rules stored in JSON

  • Smart cancellation for compound units

  • Corresponding units map (MW ↔ MWh)

4. Metadata Preservation

Quantity objects carry metadata through operations:

class Quantity:
    value: np.ndarray       # Numerical value(s)
    unit: str               # Current unit
    substance: Optional[str]   # Associated material
    basis: Optional[str]       # HHV or LHV
    reference_year: Optional[int]  # Economic context
    dimension: str          # Computed from unit

Propagation Rules:

  • Addition: Units converted, metadata preserved if matching

  • Multiplication: Metadata kept if compatible, else None

  • Division: Similar to multiplication

  • Conversion: Metadata transforms according to operation


Conversion Pipeline

Conversion Types

1. Same-Dimension Conversions

Simple factor-based conversions within a dimension:

100 kWh  360 MJ

factor = conversion_factors["kWh"] / conversion_factors["MJ"]
result = 100 * factor

Lookup Path: unit conversion_factor base conversion_factor target_unit

2. Cross-Dimension Conversions

Require substance properties and dimensional compatibility:

MASS ↔ ENERGY (via heating value):

1000 kg coal  ? MWh

1. Convert to standard: 1000 kg  1 t
2. Get heating value: coal.lhv = 25.0 MJ/kg
3. Calculate energy: 1 t × 25.0 MJ/kg × 0.2778 MWh/MJ = X MWh
4. Convert to target: X MWh  Y GJ

MASS ↔ VOLUME (via density):

1000 kg diesel  ? L

1. Get density: diesel.density = 840 kg/m³
2. Calculate volume: 1000 kg / 840 kg/m³ = 1.19 m³
3. Convert to target: 1.19 m³  1190 L

ENERGY ↔ VOLUME (via mass intermediary):

100 MWh  ? m³ natural gas

1. Convert to mass: MWh  kg (using LHV)
2. Convert to volume: kg  m³ (using density)

3. Basis Conversions

HHV ↔ LHV using substance-specific ratios:

100 MWh (HHV)  ? MWh (LHV) for coal

ratio = lhv / hhv = 25.0 / 26.0 = 0.9615
result = 100 × 0.9615 = 96.15 MWh (LHV)

4. Substance Conversions

Combustion stoichiometry for emissions:

CO2 Emissions:

1000 kg coal  ? kg CO2

1. Get carbon content: coal.carbon_content = 0.75
2. Calculate carbon: 1000 kg × 0.75 = 750 kg C
3. Stoichiometry: 750 kg C × (44/12) = 2750 kg CO2

Alternative via carbon intensity:

100 MWh (coal, LHV)  ? t CO2

1. Get carbon intensity: coal.carbon_intensity = 95.0 kg CO2/GJ
2. Convert energy: 100 MWh  360 GJ
3. Calculate emissions: 360 GJ × 95.0 kg CO2/GJ = 34,200 kg = 34.2 t

5. Inflation Adjustments

Cumulative inflation between years:

1000 USD (2020)  ? USD (2025)

1. Get rates: [2021: 4.7%, 2022: 8.0%, 2023: 4.12%, 2024: 3.15%, 2025: 2.5%]
2. Compound: 1000 × (1.047) × (1.08) × (1.0412) × (1.0315) × (1.025)
3. Result:  1256 USD (2025)

Data-Driven Design

Why JSON?

Advantages:

  1. Zero-compilation extensibility: Add units/fuels without rebuilding

  2. Human-readable: Easy to review and audit

  3. Version-controllable: Track data changes with git

  4. Language-agnostic: Can be reused in other implementations

  5. Validation-friendly: JSON schema validation possible

Trade-offs:

  • Slightly slower startup (negligible: <10ms)

  • No compile-time validation (runtime checks compensate)

  • More verbose than code constants

Decision: The extensibility benefits far outweigh the minor performance cost.

Data Structure

units.json

{
  "dimensions": {
    "kWh": "ENERGY",
    "MW": "POWER"
  },
  "conversion_factors": {
    "kWh": 0.001,  // to base unit (MWh)
    "MW": 1.0
  },
  "base_units": {
    "ENERGY": "MWh",
    "POWER": "MW"
  },
  "corresponding_units": {
    "MW": "MWh",  // MW ↔ MWh for POWER × TIME
    "MWh": "MW"
  },
  "dimensional_multiplication_rules": [
    {
      "dimensions": ["POWER", "TIME"],
      "result_dimension": "ENERGY",
      "source_dimension": "POWER"  // Use POWER unit for result
    }
  ],
  "dimensional_division_rules": [
    {
      "numerator_dimension": "ENERGY",
      "denominator_dimension": "TIME",
      "result_dimension": "POWER"
    }
  ]
}

substances.json

{
  "_note": "Data sourced from IPCC 2006...",
  "coal": {
    "name": "Coal (generic)",
    "hhv": 26.0,     // MJ/kg
    "lhv": 25.0,     // MJ/kg
    "density": 833,  // kg/m³
    "carbon_intensity": 95.0,  // kg CO2/GJ (LHV basis)
    "carbon_content": 0.75,    // Mass fraction
    "hydrogen_content": 0.05,
    "ash_content": 0.10
  }
}

inflation.json

{
  "_note": "Historical rates from FRED/Eurostat...",
  "USD": {
    "2020": 1.23,  // Annual CPI %
    "2021": 4.70,
    "2022": 8.00
  }
}

Data Loading Strategy

Load Order:

  1. Module import → registry initialization

  2. Registry __init___load_defaults()

  3. Defaults loaded from bundled JSON

  4. User can extend via load_units(), load_substances(), load_inflation()

Caching: Data loaded once on first import, then cached in singleton instances.


Extensibility

Adding Custom Units

Create custom_units.json:

{
  "dimensions": {
    "BTU": "ENERGY",
    "hp": "POWER"
  },
  "conversion_factors": {
    "BTU": 0.000293071,  // BTU to MWh
    "hp": 0.000746       // hp to MW
  }
}

Load in code:

from energyunits.registry import registry

registry.load_units("custom_units.json")

energy = Quantity(1000, "BTU")
energy_mwh = energy.to("MWh")

Adding Custom Substances

Create custom_fuels.json:

{
  "wood_waste": {
    "name": "Industrial Wood Waste",
    "hhv": 16.5,
    "lhv": 14.8,
    "density": 450,
    "carbon_intensity": 0,  // Biogenic
    "carbon_content": 0.49,
    "hydrogen_content": 0.06,
    "ash_content": 0.05
  }
}

Load in code:

from energyunits.substance import substance_registry

substance_registry.load_substances("custom_fuels.json")

fuel = Quantity(1000, "kg", "wood_waste")
energy = fuel.to("MWh", basis="LHV")

Adding Custom Dimensional Rules

Extend multiplication/division behavior:

{
  "dimensional_multiplication_rules": [
    {
      "dimensions": ["FORCE", "LENGTH"],
      "result_dimension": "ENERGY",
      "source_dimension": "FORCE"
    }
  ],
  "dimensional_division_rules": [
    {
      "numerator_dimension": "VOLUME",
      "denominator_dimension": "TIME",
      "result_dimension": "FLOW_RATE"
    }
  ]
}

Plugin System (Future)

Potential Architecture:

# energyunits/plugins/base.py
class ConversionPlugin:
    def can_convert(self, from_dim, to_dim, substance):
        return False

    def convert(self, value, from_unit, to_unit, substance, **kwargs):
        raise NotImplementedError

# User plugin
class GeoThermalPlugin(ConversionPlugin):
    def can_convert(self, from_dim, to_dim, substance):
        return substance == "geothermal" and from_dim == "TEMPERATURE"

    def convert(self, value, from_unit, to_unit, substance, **kwargs):
        # Custom geothermal conversion logic
        return result

# Registration
registry.register_plugin(GeoThermalPlugin())

Benefits:

  • Domain-specific conversions without core changes

  • Third-party extensions

  • Industry-specific logic (e.g., oil & gas, renewables)


Testing Strategy

Test Structure

17 Test Files, 270+ Tests:

tests/
├── test_quantity.py                    # Core Quantity class
├── test_registry.py                    # Unit registry
├── test_substance_conversions.py       # Substance logic
├── test_inflation_adjustment.py        # Inflation adjustment
├── test_inflation_integration.py       # Inflation integration
├── test_compound_units.py              # Complex units
├── test_smart_unit_cancellation.py     # Unit cancellation
├── test_complex_conversion_chains.py   # Multi-step conversions
├── test_integration.py                 # Real-world scenarios
├── test_readme_examples.py             # Doc validation
├── test_error_handling.py              # Error cases
├── test_error_handling_comprehensive.py # Comprehensive error cases
├── test_edge_cases.py                  # Edge cases
├── test_main.py                        # Main module tests
├── test_pandas_integration_expanded.py # Pandas integration
├── test_year_dependent_currency.py     # Exchange rate conversions
└── test_v020_features.py               # Discovery, caching, warnings, repr

Testing Philosophy

  1. Unit Tests: Each module tested in isolation

  2. Integration Tests: Cross-module workflows

  3. Example Tests: README code actually runs

  4. Error Tests: All error paths validated

  5. Edge Cases: Boundary conditions, special values

Coverage Strategy

Target: 80%+ coverage across:

  • Core conversion logic (>90%)

  • Arithmetic operations (>90%)

  • Error handling (>85%)

  • Edge cases (>75%)

Not covered:

  • Pandas integration (optional dependency)

  • CLI tools (none yet)

  • Performance benchmarks (manual)

Test Categories

1. Functional Tests

def test_basic_energy_conversion():
    energy = Quantity(100, "kWh")
    result = energy.to("MJ")
    assert result.value == pytest.approx(360)

2. Cross-Dimensional Tests

def test_coal_to_energy():
    coal = Quantity(1000, "kg", "coal")
    energy = coal.to("MWh", basis="LHV")
    assert energy.value > 0

3. Arithmetic Tests

def test_power_times_time():
    power = Quantity(100, "MW")
    time = Quantity(24, "h")
    energy = power * time
    assert energy.unit == "MWh"
    assert energy.value == 2400

4. Error Tests

def test_invalid_conversion():
    energy = Quantity(100, "MWh")
    with pytest.raises(ValueError):
        energy.to("kg")  # No substance specified

5. Documentation Tests

def test_readme_basic_usage():
    # Exact code from README
    energy = Quantity(100, "MWh")
    assert energy.to("GJ").value == 360.0

CI/CD Pipeline

GitHub Actions Workflow:

- Python 3.9, 3.10, 3.11, 3.12, 3.13
- Run: pytest, black --check, flake8
- Coverage: pytest --cov → Codecov
- On: push, pull_request to main

Performance Considerations

Current Performance

Typical Operations (on 2024 laptop):

  • Simple conversion: ~5 μs

  • Cross-dimensional: ~50 μs

  • Array operations (1000 elements): ~100 μs

  • JSON loading (startup): ~10 ms

Bottlenecks:

  1. Dict lookups in registries (acceptable)

  2. NumPy array creation (necessary)

  3. String parsing for compound units (minor)

Implemented Optimizations (v0.2.0)

1. Conversion Factor Caching (implemented)

# @lru_cache on get_dimension() and get_conversion_factor()
# Cache automatically invalidated when custom units are loaded
@lru_cache(maxsize=256)
def get_conversion_factor(self, from_unit, to_unit):
    ...

Up to 10x speedup for repeated lookups in hot loops.

Future Optimization Opportunities

1. Compound Unit Parsing

# Current: Split string every time
if "/" in unit:
    parts = unit.split("/")

# Potential: Pre-parse at creation
@cached_property
def parsed_unit(self):
    return parse_unit_string(self.unit)

2. Array Operations

# Current: NumPy (already fast)
result = self.value * factor

# Potential: Numba JIT for hot paths
@numba.jit(nopython=True)
def multiply_array(arr, factor):
    return arr * factor

Design for Performance

Good Decisions:

  • NumPy for vectorization

  • Singleton registries (no re-instantiation)

  • Simple data structures (dicts, lists)

  • Minimal dependencies

Trade-offs Accepted:

  • Runtime type checking (vs. mypy enforcement)

  • String-based unit representation (vs. enum/class)

  • Dict lookups (vs. compiled constants)

Rationale: Readability and extensibility prioritized over micro-optimization. Performance is already sufficient for typical energy modeling (millions of conversions/sec).


Design Decisions

1. Why Unified .to() Instead of Separate Methods?

Rejected Alternatives:

# Option A: Separate methods
quantity.to_unit("MJ")
quantity.to_substance("CO2")
quantity.to_basis("HHV")
quantity.inflate_to(2025)

# Option B: Chaining
quantity.to_unit("MJ").to_basis("HHV").inflate_to(2025)

Chosen: Unified .to() with optional kwargs

Rationale:

  • Single method to learn

  • Clear conversion order (no ambiguity)

  • Handles complex conversions naturally

  • Still allows simple calls: .to("MJ")

2. Why Dimension-Based Type System?

Alternative: Dynamic/permissive conversions

Chosen: Strict dimensional analysis

Rationale:

  • Prevents common errors (adding MWh + kg)

  • Makes invalid operations impossible

  • Clear error messages

  • Mirrors physical reality

3. Why NumPy Arrays by Default?

Alternative: Python floats/lists, lazy NumPy

Chosen: Always wrap in NumPy arrays

Rationale:

  • Uniform API (scalars and arrays)

  • Vectorization for free

  • Minimal overhead for scalars

  • Simplifies internal logic

4. Why Substance as Optional Parameter?

Alternative: Separate FuelQuantity class

Chosen: Single Quantity class with optional substance

Rationale:

  • Simpler mental model (one class)

  • Natural conversions between pure/substance quantities

  • More flexible (can add substance later)

  • Less code duplication

5. Why JSON Instead of YAML/TOML/Code?

Alternatives:

  • YAML: More readable, but requires extra dependency

  • TOML: Good for config, but less structured for nested data

  • Python code: Fast, but not data-portable

Chosen: JSON

Rationale:

  • Standard library support (no deps)

  • Universal format (language-agnostic)

  • Strict schema (less ambiguity than YAML)

  • Good enough readability with formatting

6. Why Singleton Registries?

Alternative: Pass registries around, dependency injection

Chosen: Module-level singletons

Rationale:

  • Simpler API (no registry parameter)

  • Global truth (one unit definition)

  • Lazy loading on import

  • Testable via load_*() methods

7. Why LHV as Default Basis?

Alternative: HHV default, or required parameter

Chosen: LHV default, optional specification

Rationale:

  • Engineering convention (boilers, turbines)

  • Conservative (lower) estimates

  • Consistent with most industry tools

  • Can override when needed


Future Enhancements

Completed in v0.2.0

  • Conversion factor caching (@lru_cache) — up to 10x speedup

  • Improved error messages with close-match suggestions and conversion hints

  • Unit discovery API (list_units(), list_substances(), list_currencies(), list_dimensions())

  • Unit constants module (energyunits.units) for IDE autocompletion

  • Subtraction operator (__sub__)

  • Jupyter rich HTML repr (_repr_html_())

  • Substance mismatch warnings on arithmetic operations

Short-Term (v0.3.0)

1. Additional Dimensions

  • Temperature (K, C, F) with conversion logic

  • Pressure (bar, PSI, Pa)

  • Flow rates (m3/s, L/min)

  • Area (m2, km2, ha)

2. Extended Substance Database

  • More biomass types (switchgrass, miscanthus)

  • Synthetic fuels (e-fuels, hydrogen derivatives)

  • Industrial gases (nitrogen, oxygen, argon)

  • Regional coal variants (US, Chinese, Indonesian)

3. Validation Tools

# Validate custom data files
from energyunits import validate_units_file

validate_units_file("custom.json")
# -> Reports: missing fields, invalid values, circular dependencies

Medium-Term (v0.4.0)

1. Plugin Architecture

  • Base class for conversion plugins

  • Plugin discovery and registration

  • Custom dimensional rules

  • Industry-specific extensions

2. Performance Optimizations

  • Numba JIT for hot paths

  • Lazy NumPy wrapping for scalars

  • Compiled regex for unit parsing

3. Enhanced Economics

  • Multiple inflation indices (CPI, PPI, GDP deflator)

  • Real vs. nominal currency handling

  • Discount rate calculations

  • NPV/IRR helpers

4. Serialization

# Save/load Quantity objects
quantity.to_dict()
Quantity.from_dict(data)

# Pickle support
import pickle
pickle.dump(quantity, file)

Long-Term (v1.0.0+)

1. Uncertainty Quantification

energy = Quantity(100, "MWh", uncertainty=0.05)  # ±5%
result = energy.to("GJ")
# result.value = 360 ± 18 GJ

2. Unit Algebra DSL

# Define custom compound units
EUR_per_MWh = EUR / MWh
tonnes_per_year = t / a

cost_rate = Quantity(50, EUR_per_MWh)

3. Time Series Support

# Native time series handling
ts = TimeSeriesQuantity(
    values=[100, 120, 90],
    timestamps=["2023-01", "2023-02", "2023-03"],
    unit="MWh"
)

4. Visualization

quantity.plot()  # Automatic unit-aware plots
quantity.compare_to(other_quantity)  # Side-by-side viz

5. Database Integration

# SQL-aware quantity columns
# Pandas extension array
# Parquet schema support

API Stability (Post-1.0)

Semantic Versioning Commitment:

  • MAJOR: Breaking API changes (rare)

  • MINOR: New features, backward compatible

  • PATCH: Bug fixes, data updates

Deprecation Policy:

  • Warning in version N

  • Deprecated in N+1

  • Removed in N+2

  • Minimum 1-year notice


Contributing to Architecture

Adding New Features

1. Propose Enhancement

  • Open GitHub issue

  • Describe use case

  • Discuss API design

  • Consider data-driven approach

2. Implement

  • Add data to JSON (if applicable)

  • Implement logic in appropriate module

  • Add comprehensive tests

  • Document in docstrings

3. Document

  • Update this ARCHITECTURE.md

  • Add to CHANGELOG.md

  • Update README examples

  • Create migration guide if breaking

Architecture Review Checklist

Before merging significant changes:

  • [ ] Maintains data-driven philosophy

  • [ ] Consistent with existing API patterns

  • [ ] No breaking changes (or justified + documented)

  • [ ] Comprehensive test coverage

  • [ ] Performance acceptable (benchmarked)

  • [ ] Documented in ARCHITECTURE.md

  • [ ] Examples in README

  • [ ] Backward compatibility considered


References

Design Inspirations

  1. Pint (Python units library): Registry pattern, dimensional analysis

  2. Pandas (data analysis): NumPy integration, vectorization

  3. SQLAlchemy (ORM): Data-driven configuration, extensibility

  4. Click (CLI framework): Unified API, good defaults

Relevant Literature

  • NIST Special Publication 811: “Guide for the Use of SI Units”

  • IPCC 2006 Guidelines: Energy sector methodology

  • IEEE Standard 260.1: Letter Symbols for Units


Conclusion

EnergyUnits is designed as a domain-specific, data-driven, production-ready library for energy system modeling. The architecture prioritizes:

  1. Simplicity: Single unified API

  2. Safety: Dimensional analysis

  3. Extensibility: JSON configuration

  4. Performance: NumPy vectorization

  5. Maintainability: Clean separation of concerns

The design has been validated through:

  • 270+ comprehensive tests

  • Real-world energy modeling scenarios

  • Multiple user feedback cycles

  • Performance profiling

This architecture is stable and production-ready (v0.2.0).

Future enhancements will maintain backward compatibility while expanding capabilities through the proven data-driven approach.


Document Version History:

  • 2026-02-15: Updated for v0.2.0 (caching, discovery API, exchange rates, units module)

  • 2025-11-24: Initial comprehensive architecture documentation (v0.1.0)

Maintainers:

  • Martin Klein (@0-k)

License: MIT