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¶
Design Philosophy¶
Core Principles¶
Simplicity First: Single unified API (
.to()) for all conversionsType Safety: Dimensional analysis prevents nonsensical operations
Data-Driven: All configuration in JSON, zero code changes to extend
Composable: Natural mathematical operations with automatic unit handling
Explicit: Substance and basis conversions require explicit specification
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 ↔ ENERGYdensity- Density (kg/m³) for MASS ↔ VOLUMEcarbon_content- Carbon fraction for CO2 emission calculationshydrogen_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:
Substance conversion (e.g., coal → CO2)
Basis conversion (HHV ↔ LHV)
Unit conversion (kWh → MJ)
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:
Zero-compilation extensibility: Add units/fuels without rebuilding
Human-readable: Easy to review and audit
Version-controllable: Track data changes with git
Language-agnostic: Can be reused in other implementations
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:
Module import → registry initialization
Registry
__init__→_load_defaults()Defaults loaded from bundled JSON
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¶
Unit Tests: Each module tested in isolation
Integration Tests: Cross-module workflows
Example Tests: README code actually runs
Error Tests: All error paths validated
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:
Dict lookups in registries (acceptable)
NumPy array creation (necessary)
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 speedupImproved 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 autocompletionSubtraction 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¶
Pint (Python units library): Registry pattern, dimensional analysis
Pandas (data analysis): NumPy integration, vectorization
SQLAlchemy (ORM): Data-driven configuration, extensibility
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:
Simplicity: Single unified API
Safety: Dimensional analysis
Extensibility: JSON configuration
Performance: NumPy vectorization
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