API Reference
metricengine public API.
This package provides a pluggable metric engine with optional integrations via entry points. It includes a calculation registry, utility formatters with optional Babel support, and a comprehensive unit system for type-safe financial calculations with explicit unit conversions.
Key Features: - Type-safe FinancialValue objects with unit awareness - Comprehensive unit system with Money, Quantity, and Percent units - Conversion registry with multi-hop routing capabilities - Policy-driven conversion behavior (strict vs permissive) - Provenance tracking for unit conversions and calculations - Unit-aware rendering and formatting
- class metricengine.FinancialValue(_value, policy=None, unit=<class 'metricengine.units.Dimensionless'>, _is_percentage=False, _prov=None)[source]
Bases:
Generic[U]- as_percentage()[source]
Convert this FinancialValue to percentage representation with provenance tracking.
- Return type:
- classmethod constant(value, policy=None, unit=<class 'metricengine.units.Dimensionless'>)[source]
Create a constant FinancialValue with appropriate provenance.
- export_provenance_graph()[source]
Export the complete provenance graph for this FinancialValue.
- Return type:
- Returns:
Dictionary containing the provenance graph in JSON-serializable format
Example
>>> result = FinancialValue(10) + FinancialValue(5) >>> graph = result.export_provenance_graph() >>> print(graph['root']) # Root provenance ID
- get_calculation_summary()[source]
Get a brief summary of how this value was calculated.
- Return type:
- Returns:
Brief string summary of the calculation
Example
>>> result = FinancialValue(10) + FinancialValue(5) >>> print(result.get_calculation_summary()) # "Op: + | Inputs: 2"
- get_input_count()[source]
Get the number of inputs that contributed to this FinancialValue.
- Return type:
- Returns:
Number of input values, 0 for literals or values without provenance
Example
>>> result = FinancialValue(10) + FinancialValue(5) >>> print(result.get_input_count()) # 2
- get_inputs()[source]
Get the input provenance IDs that contributed to this FinancialValue.
- Return type:
- Returns:
Tuple of provenance IDs for inputs, empty tuple if no provenance
Example
>>> a = FinancialValue(10) >>> b = FinancialValue(5) >>> result = a + b >>> inputs = result.get_inputs() >>> print(len(inputs)) # 2
- get_operation()[source]
Get the operation that created this FinancialValue.
Example
>>> a = FinancialValue(10) >>> b = FinancialValue(5) >>> result = a + b >>> print(result.get_operation()) # "+"
- get_provenance()[source]
Get the provenance record for this FinancialValue.
- Return type:
- Returns:
Provenance record if available, None otherwise
- get_provenance_id()[source]
Get the unique provenance ID for this FinancialValue.
Example
>>> value = FinancialValue(100) >>> prov_id = value.get_provenance_id() >>> print(prov_id[:8]) # First 8 chars of hash
- get_provenance_metadata()[source]
Get the metadata associated with this FinancialValue’s provenance.
Example
>>> with calc_span("analysis"): ... result = FinancialValue(10) + FinancialValue(5) >>> meta = result.get_provenance_metadata() >>> print(meta.get("span")) # "analysis"
- has_operation(operation)[source]
Check if this FinancialValue was created by a specific operation.
- Parameters:
operation (
str) – Operation string to check for (e.g., “+”, “literal”, “calc:margin”)- Return type:
- Returns:
True if the operation matches, False otherwise
Example
>>> result = FinancialValue(10) + FinancialValue(5) >>> print(result.has_operation("+")) # True >>> print(result.has_operation("*")) # False
- has_provenance()[source]
Check if this FinancialValue has provenance information.
- Return type:
- Returns:
True if provenance is available, False otherwise
- is_computed()[source]
Check if this FinancialValue was computed from other values.
- Return type:
- Returns:
True if this value was computed, False if it’s a literal
Example
>>> literal = FinancialValue(100) >>> computed = FinancialValue(50) + FinancialValue(50) >>> print(literal.is_computed()) # False >>> print(computed.is_computed()) # True
- is_literal()[source]
Check if this FinancialValue is a literal (not computed from other values).
- Return type:
- Returns:
True if this is a literal value, False if computed
Example
>>> literal = FinancialValue(100) >>> computed = FinancialValue(50) + FinancialValue(50) >>> print(literal.is_literal()) # True >>> print(computed.is_literal()) # False
- classmethod none(policy=None)[source]
Create a None FinancialValue with appropriate provenance.
- Return type:
- classmethod none_with_unit(unit, policy=None)[source]
Create a None FinancialValue with specific unit and appropriate provenance.
- Return type:
- ratio()[source]
Convert this FinancialValue to ratio representation with provenance tracking.
- Return type:
- render(fmt='text', **context)[source]
Render this FinancialValue using a registered renderer.
- Parameters:
fmt (
str) – Name of the renderer to use (default: “text”)**context – Additional context passed to the renderer
- Return type:
- Returns:
Rendered string representation
- Raises:
KeyError – If the specified renderer is not registered
Example
>>> amount = money(1234.56) >>> amount.render("html") # '<span class="fv positive">$1,234.56</span>' >>> amount.render("html", css_classes="highlight")
- to(unit, *, at=None, meta=None)[source]
Convert this FinancialValue to a different unit.
This method performs explicit unit conversion using the registered conversion functions. It creates a new FinancialValue with the target unit and converted value, preserving the original policy and percentage flag.
- Parameters:
- Return type:
- Returns:
New FinancialValue with the target unit and converted value
- Raises:
KeyError – If no conversion is registered between the units
ValueError – If this FinancialValue has a None value
TypeError – If this FinancialValue doesn’t use the new unit system
Example
>>> from metricengine import FinancialValue as FV >>> from metricengine.units import MoneyUnit >>> >>> usd = MoneyUnit("USD") >>> gbp = MoneyUnit("GBP") >>> amount = FV(100, unit=usd) >>> >>> # Convert with default context >>> converted = amount.to(gbp) >>> >>> # Convert with specific rate and timestamp >>> converted = amount.to(gbp, at="2025-09-06T10:30:00Z", ... meta={"rate": "0.79", "source": "ECB"})
- trace_calculation(max_depth=10)[source]
Generate a human-readable trace of how this value was calculated.
This method provides a detailed explanation of the calculation chain that led to this FinancialValue, useful for debugging and auditing.
- Parameters:
max_depth (
int) – Maximum depth to traverse in the calculation tree- Return type:
- Returns:
Formatted string showing the calculation trace
Example
>>> revenue = FinancialValue(1000) >>> cost = FinancialValue(600) >>> profit = revenue - cost >>> print(profit.trace_calculation())
- unit
alias of
Dimensionless
- metricengine.FV
alias of
FinancialValue
- class metricengine.NewUnit(category, code)[source]
Bases:
objectGeneric unit with category and code dimensions.
A unit represents a measurement dimension with both a category (the type of measurement) and a specific code (the particular unit within that category). Units are immutable and hashable, making them suitable for use as dictionary keys in conversion registries.
- category
The category of measurement (e.g., “Money”, “Quantity”, “Percent”)
- code
The specific unit code within the category (e.g., “USD”, “kg”, “ratio”)
Examples
>>> usd = NewUnit("Money", "USD") >>> str(usd) 'Money[USD]' >>> kg = NewUnit("Quantity", "kg") >>> str(kg) 'Quantity[kg]' >>> ratio = NewUnit("Percent", "ratio") >>> str(ratio) 'Percent[ratio]'
- metricengine.MoneyUnit(code)[source]
Create a Money unit with the specified currency code.
- Parameters:
code (
str) – Currency code (e.g., “USD”, “GBP”, “EUR”)- Return type:
- Returns:
NewUnit with “Money” category and the specified code
Example
>>> usd = MoneyUnit("USD") >>> str(usd) 'Money[USD]'
- metricengine.Qty(code)[source]
Create a Quantity unit with the specified unit code.
- Parameters:
code (
str) – Quantity unit code (e.g., “kg”, “L”, “m”)- Return type:
- Returns:
NewUnit with “Quantity” category and the specified code
Example
>>> kg = Qty("kg") >>> str(kg) 'Quantity[kg]'
- metricengine.Pct(code='ratio')[source]
Create a Percent unit with the specified code.
- Parameters:
code (
str) – Percent unit code, defaults to “ratio”- Return type:
- Returns:
NewUnit with “Percent” category and the specified code
Example
>>> ratio = Pct() >>> str(ratio) 'Percent[ratio]' >>> bp = Pct("bp") >>> str(bp) 'Percent[bp]'
- class metricengine.ConversionContext(at=None, meta=<factory>)[source]
Bases:
objectContext information for unit conversions.
Provides metadata and timing information that conversion functions can use to perform dynamic rate lookups or apply business rules. This allows conversion functions to access external data sources like exchange rate APIs or historical rate databases.
- at
Optional timestamp or date string for rate lookups
- meta
Dictionary of additional metadata (rates, tenant info, etc.)
Examples
>>> # Simple context with timestamp >>> ctx = ConversionContext(at="2025-09-06T10:30:00Z") >>> >>> # Context with metadata >>> ctx = ConversionContext( ... at="2025-09-06", ... meta={"rate": "0.79", "source": "ECB"} ... )
- class metricengine.ConversionPolicy(strict=True, allow_paths=True)[source]
Bases:
objectPolicy configuration for unit conversions.
Controls the behavior of the conversion system, including whether to raise errors on missing conversions and whether to allow multi-hop conversion paths through intermediate units.
- strict
If True, raise KeyError on missing conversions; if False, return original value
- allow_paths
If True, enable multi-hop conversions; if False, only direct conversions
Examples
>>> # Strict policy (default) - raises on missing conversions >>> strict_policy = ConversionPolicy(strict=True, allow_paths=True) >>> >>> # Permissive policy - returns original value on missing conversions >>> permissive_policy = ConversionPolicy(strict=False, allow_paths=True) >>> >>> # Direct-only policy - no multi-hop conversions >>> direct_only = ConversionPolicy(strict=True, allow_paths=False)
- class metricengine.Conversion(src, dst, fn)[source]
Bases:
objectRepresents a registered conversion between two units.
Contains the source unit, destination unit, and the function that performs the actual conversion calculation. Conversion functions receive the value to convert and a context object that can provide additional information like exchange rates.
- src
Source unit for the conversion
- dst
Destination unit for the conversion
- fn
Function that performs the conversion, taking (Decimal, ConversionContext) -> Decimal
Examples
>>> usd = MoneyUnit("USD") >>> gbp = MoneyUnit("GBP") >>> >>> def usd_to_gbp(value: Decimal, ctx: ConversionContext) -> Decimal: ... return value * Decimal("0.79") >>> >>> conversion = Conversion(usd, gbp, usd_to_gbp)
-
fn:
Callable[[Decimal,ConversionContext],Decimal]
- metricengine.register_conversion(src, dst)[source]
Decorator for registering conversion functions between units.
The decorated function must accept a Decimal value and ConversionContext, and return a Decimal result.
- Parameters:
- Returns:
Decorator function that registers the conversion
- Raises:
ValueError – If the function signature is invalid
Example
>>> usd = MoneyUnit("USD") >>> gbp = MoneyUnit("GBP") >>> >>> @register_conversion(usd, gbp) ... def usd_to_gbp(value: Decimal, ctx: ConversionContext) -> Decimal: ... # Simple fixed rate for example ... return value * Decimal("0.79")
- metricengine.get_conversion(src, dst)[source]
Get a registered conversion between two units.
- Parameters:
- Return type:
- Returns:
Conversion object containing the conversion function
- Raises:
KeyError – If no conversion is registered for the unit pair
- metricengine.list_conversions()[source]
Get a copy of all registered conversions.
- Return type:
- Returns:
Dictionary mapping unit pairs to their conversions
- metricengine.convert_decimal(value, src, dst, *, at=None, meta=None)[source]
Convert a decimal value from one unit to another.
This function performs unit-to-unit conversion using the registered conversion functions. It handles same-unit conversions by returning the original value unchanged, and supports multi-hop conversions through intermediate units when no direct conversion exists.
The behavior is controlled by the current ConversionPolicy: - strict=True: Raises KeyError on missing conversions - strict=False: Returns original value on missing conversions - allow_paths=True: Enables multi-hop conversions - allow_paths=False: Only allows direct conversions
- Parameters:
- Return type:
- Returns:
Converted decimal value, or original value if conversion fails and strict=False
- Raises:
KeyError – If no conversion path exists and strict=True
ValueError – If conversion function raises an exception and strict=True
Example
>>> usd = MoneyUnit("USD") >>> gbp = MoneyUnit("GBP") >>> >>> # Strict mode (default) - raises on missing conversion >>> result = convert_decimal(Decimal("100"), usd, gbp) >>> >>> # Permissive mode - returns original on missing conversion >>> policy = ConversionPolicy(strict=False) >>> with use_conversions(policy): ... result = convert_decimal(Decimal("100"), usd, gbp) ... # Returns Decimal("100") if no conversion exists
- metricengine.use_conversions(policy)[source]
Context manager for scoped conversion policy.
Temporarily sets the conversion policy for the duration of the context. The previous policy is restored when the context exits.
- Parameters:
policy (
ConversionPolicy) – ConversionPolicy to use within the context
Example
>>> permissive_policy = ConversionPolicy(strict=False, allow_paths=True) >>> with use_conversions(permissive_policy): ... # Conversions within this block use permissive policy ... result = convert_decimal(value, usd, gbp)
- metricengine.get_current_conversion_policy()[source]
Get the current conversion policy from context.
- Return type:
- Returns:
Current ConversionPolicy in effect
- class metricengine.Policy(decimal_places=2, rounding='ROUND_HALF_UP', none_text='—', percent_display='percent', cap_percentage_at=<factory>, percent_style='percent', quantizer_factory=<function default_quantizer_factory>, negative_sales_is_none=True, compare_none_as_minus_infinity=False, arithmetic_strict=False, thousands_sep=True, currency_symbol=None, currency_position='prefix', negative_parentheses=False, locale=None, display=None)[source]
Bases:
objectImmutable configuration for financial calculations and formatting.
-
display:
Optional[DisplayPolicy] = None
- format_decimal(d, unit)[source]
Format a decimal with thousands separators, currency, and negative style.
This method is deprecated and will delegate to the built-in formatter for backward compatibility.
- Return type:
- format_percent(ratio_value)[source]
Render ratio (0..1) as percent text. Always clamp to cap_percentage_at if provided.
- Return type:
- quantize(d)[source]
Quantize according to policy. Supports arbitrary step sizes (e.g., 0.5) by rounding to nearest step.
- Return type:
- quantizer_factory()
Exact, fast quantizer for given dp: e.g., dp=2 -> Decimal(‘0.01’).
- Return type:
-
display:
- class metricengine.PolicyResolution(value)[source]
Bases:
Enum- CONTEXT = 1
- LEFT_OPERAND = 2
- STRICT_MATCH = 3
- exception metricengine.MetricEngineError[source]
Bases:
ExceptionBase exception for all Metric Engine errors.
- exception metricengine.MissingInputError(message, missing_inputs=None)[source]
Bases:
MetricEngineErrorRaised when required inputs are missing for a calculation.
- exception metricengine.CircularDependencyError(cycle)[source]
Bases:
MetricEngineErrorRaised when a circular dependency is detected in calculations.
- exception metricengine.CalculationError(message, calculation_name=None)[source]
Bases:
MetricEngineErrorGeneric calculation error.
- metricengine.calc(name, *, depends_on=())[source]
Decorator to register a calculation function with its dependencies.
- metricengine.list_calculations()[source]
List all registered calculations and their dependencies (copies).
- class metricengine.Engine(default_policy=None)[source]
Bases:
objectExecution engine for financial calculations.
Builds dependency graphs, detects circular dependencies, caches results, and executes calculations in the correct order.
- calculate(name, ctx=None, *, policy=None, allow_partial=False, **kwargs)[source]
Calculate a target metric given a context of input values.
The engine follows a “let calculations validate” philosophy: - None values propagate naturally through calculations - No need for defensive checks before calling calculate - Each calculation determines what inputs are valid - FinancialValue results can be passed directly to other calculations
- Parameters:
name (
str) – Name of the calculation to computectx (
dict[str,Union[int,float,str,Decimal,SupportsFloat,None,FinancialValue]] |None) – Dictionary of input values (optional if using kwargs)policy (
Policy|None) – Optional policy to override defaultallow_partial (
bool) – If True, return None on failure instead of raising**kwargs (
Union[int,float,str,Decimal,SupportsFloat,None,FinancialValue]) – Input values as keyword arguments (can include None)
- Return type:
- Returns:
FinancialValue containing the result (may wrap None)
- Raises:
MissingInputError – If required non-None inputs are missing
CircularDependencyError – If circular dependencies are detected
CalculationError – If calculation fails
- calculate_many(targets, ctx=None, *, policy=None, allow_partial=False, **kwargs)[source]
Resolve all targets in one pass with shared dependency resolution.
- Parameters:
targets (Set of metric names you want)
ctx (Inputs you already have (optional if using kwargs))
policy (Optional Policy override)
allow_partial (If True, return what can be computed and) – leave missing ones out instead of raising.
**kwargs (Input values as keyword arguments)
- Return type:
Dictionary mapping metric name to FinancialValue
- Raises:
MissingInputError – If any targets cannot be computed (unless allow_partial=True):
CircularDependencyError – If circular dependencies are detected:
CalculationError – If any calculation fails:
Examples: – # Using context dict >>> results = engine.calculate_many( … {“gross_profit”, “gross_margin_percentage”}, … {“sales”: 1000, “cost”: 650} … ) # Using keyword arguments >>> results = engine.calculate_many( … {“gross_profit”, “gross_margin_percentage”}, … sales=1000, cost=650 … )
- constant(value)[source]
Create a constant FinancialValue.
- get_all_calculations()[source]
Get information about all registered calculations.
- Returns:
function: The calculation function
depends_on: Set of dependencies
docstring: The function’s docstring
- Return type:
Dict mapping calculation names to their metadata including
- get_dependencies(target)[source]
Get all dependencies (direct and transitive) for a calculation.
- Parameters:
target (
str) – Name of the calculation- Return type:
- Returns:
Set of all dependency names
- Raises:
CircularDependencyError – If circular dependencies detected
- metricengine.format_currency(amount, currency='USD', *, locale=None)[source]
Format currency, using Babel if available and a locale is provided.
Falls back to a simple “1,234.56 USD” style when Babel or locale is not available.
- Return type:
- metricengine.format_percent(value, *, locale=None, precision=2)[source]
Format a percentage, using Babel if available and a locale is provided.
Falls back to a simple “12.34%” style when Babel or locale is not available.
- Return type:
- class metricengine.Renderer(*args, **kwargs)[source]
Bases:
ProtocolProtocol for custom FinancialValue renderers.
Renderers must implement a render method that takes a FinancialValue and optional context, returning a string representation.
- metricengine.register_renderer(name, renderer)[source]
Register a renderer with the given name.
- Parameters:
- Raises:
TypeError – If renderer doesn’t implement the Renderer protocol
- Return type:
Example
>>> class CustomRenderer: ... def render(self, fv, *, context=None): ... return f"Custom: {fv.as_str()}" >>> register_renderer("custom", CustomRenderer())
- metricengine.get_renderer(name)[source]
Get a registered renderer by name.
- Parameters:
name (
str) – Name of the renderer to retrieve- Return type:
- Returns:
The registered renderer instance
- Raises:
KeyError – If no renderer is registered with the given name
Example
>>> renderer = get_renderer("html") >>> output = renderer.render(my_value)
- metricengine.list_renderers()[source]
List all registered renderer names.
Example
>>> list_renderers() ['text', 'html', 'markdown']
- metricengine.inputs_needed_for(targets)[source]
Determine base inputs needed for the given targets by walking dependencies.
A “base input” is any dependency name that is not a registered calculation. Registered calculations that have no dependencies are not counted as inputs.
- class metricengine.CalculationService(policy=None)[source]
Bases:
objectUtility class for calculation services.
- class metricengine.NullBinaryMode(value)[source]
Bases:
EnumHow None is handled in binary ops (a ⊕ b).
- PROPAGATE = 1
- RAISE = 2
- class metricengine.EqualityMode(value)[source]
Bases:
Enum- VALUE_ONLY = 1
- VALUE_AND_UNIT = 2
- VALUE_UNIT_AND_POLICY = 3
- metricengine.load_plugins(context=None)[source]
Load all available plugins with optional context.
- Return type:
- metricengine.get_calc(name)[source]
Get a registered calculation function by name.
- Parameters:
name (
str) – The name of the calculation to retrieve- Return type:
- Returns:
The calculation function
- Raises:
CalculationError – If the calculation is not found or name is invalid
Example
>>> cogs = get_calc("cogs") >>> result = cogs(opening_inventory=FV(100), purchases=FV(150), closing_inventory=FV(100))
- metricengine.calc_span(name, **attrs)[source]
Context manager for grouping calculations under a named span.
This context manager allows grouping related financial calculations under a named span, which will be included in the provenance metadata of all operations performed within the span context.
- Parameters:
name (
str) – Name of the calculation span**attrs – Additional attributes to associate with the span
- Yields:
None
- Return type:
Example
>>> with calc_span("quarterly_analysis", quarter="Q1", year=2024): ... revenue = FinancialValue(1000) ... cost = FinancialValue(600) ... profit = revenue - cost # Will include span info in provenance
>>> prov = profit.get_provenance() >>> print(prov.meta.get("span")) # "quarterly_analysis" >>> print(prov.meta.get("span_attrs")) # {"quarter": "Q1", "year": 2024}
- metricengine.explain(fv, max_depth=10)[source]
Generate human-readable explanation of calculation.
This function creates a formatted text representation of how a FinancialValue was calculated, showing the operation tree in a readable format. This is useful for debugging and understanding complex calculations.
- Parameters:
fv (
FinancialValue) – FinancialValue to explainmax_depth (
int) – Maximum depth to traverse (prevents infinite recursion)
- Return type:
- Returns:
Human-readable string explaining the calculation
Example
>>> revenue = FinancialValue(1000) >>> cost = FinancialValue(600) >>> profit = revenue - cost >>> print(explain(profit)) # Result (400.00): # Operation: - # Left: 1000.00 (literal) # Right: 600.00 (literal)
- metricengine.get_provenance_graph(fv)[source]
Extract complete provenance graph as dictionary.
This function traverses the complete provenance graph starting from the given FinancialValue and returns a dictionary mapping provenance IDs to their Provenance records. This is useful for analysis and debugging of calculation lineage.
Note: This implementation can only traverse the provenance records that are directly accessible from the root FinancialValue. In the current architecture, we don’t maintain a global provenance store, so we can only include the root provenance record. A full implementation would require either: 1. A global provenance registry, or 2. Maintaining references to parent FinancialValue instances
- Parameters:
fv (
FinancialValue) – FinancialValue to extract provenance graph from- Return type:
- Returns:
Dictionary mapping provenance IDs to Provenance records
Example
>>> revenue = FinancialValue(1000) >>> cost = FinancialValue(600) >>> profit = revenue - cost >>> graph = get_provenance_graph(profit) >>> print(len(graph)) # 1 (only profit, as we can't traverse to inputs) >>> print(list(graph.keys())) # ['profit_id']
- metricengine.to_trace_json(fv)[source]
Export complete provenance graph as JSON-serializable dictionary.
This function creates a complete JSON representation of the provenance graph that can be serialized, stored, or transmitted. The format includes a root node identifier and a nodes dictionary containing all provenance records.
- Parameters:
fv (
FinancialValue) – FinancialValue to export provenance graph from- Return type:
- Returns:
Dictionary with ‘root’ and ‘nodes’ keys containing the complete graph
Example
>>> revenue = FinancialValue(1000) >>> cost = FinancialValue(600) >>> profit = revenue - cost >>> trace = to_trace_json(profit) >>> print(trace['root']) # profit provenance ID >>> print(len(trace['nodes'])) # 3 nodes
Formatting
Registry
Registry for financial calculations with dependency tracking.
- metricengine.registry.calc(name, *, depends_on=())[source]
Decorator to register a calculation function with its dependencies.
- metricengine.registry.list_calculations()[source]
List all registered calculations and their dependencies (copies).
- metricengine.registry.clear_registry()[source]
Clear all registered calculations. Primarily for testing.
- Return type:
- metricengine.registry.is_registered(name)[source]
Check if a calculation is registered.
- Return type:
- metricengine.registry.unregister(name)[source]
Remove a calculation from the registry (and its edges).
- Return type: