Skip to content

Unitary Core — Unit-Agnostic Modeling Standard

Phase 4, modeling-technique addendum (rev 2, draft for review). The BU logic is unitary: currency and market specifics never appear inside computational logic. Rev 2 sharpens two boundaries:

  1. Currency: USD is the single basis. All logic computes in USD. Published FX rates generate currency-specific outputs at the presentation layer — FX conversion is a boundary operation, not logic.
  2. Physical units: ISO standards, actual numbers. kW, kWh, Ah, km — and we use real specific values. No abstract "1 kWh battery": we build models for concrete configurations, e.g. an SNS operating 100 Ah batteries on 3-phase 6 kW chargers.

The three-layer split

Layer Owns Examples
Logic (unitary core) equations, identities, behavioral structure price wedge, utilization ratio, degradation-per-EFC shape, IRR, integer capacity
Profile (coloring) coefficients, concrete values, FX USD cost anchors, KES FX rate, tariff $/kWh, duty %, battery spec values
Presentation display formatting local-currency conversion via published FX, unit suffixes, rounding — rendered as the uniform BU Dashboard

The rule: logic owns equations; the profile owns coefficients. If an equation's shape changes per market, it is not one equation — it is two, and both belong in logic with a profile switch selecting between them.

Canonical internal representation

Dimension Canonical form Note
Time period index (months) calendar labels are presentation
Energy / power kWh / kW (ISO) values are specific per configuration
Charge / capacity Ah (ISO) battery spec, actual numbers
Count units (batteries, vehicles, swaps, stations) integers stay integers
Money USD one basis everywhere; FX only at presentation

Worked example — LCOB, unitary

LCOB_usd_per_kwh = (CAPEX_usd − residual_usd) / usable_energy_kwh   ← logic (USD in, USD out)
LCOB_local       = LCOB_usd_per_kwh × FX_rate                       ← presentation

Kenya and Tanzania profiles differ only in the USD inputs; the logic line is identical.

Granularity: unitary in representation, specific in structure

Unitariness governs representation (USD, ISO units) and shared equation structure. It does not mean one fully-generic model parameterized over all product variants. The BU boundary follows the operational unit, not type-system elegance.

Decision (Sep 2026): mixed-fleet BUs. A swap network operating both 100 Ah and 45 Ah batteries is modeled as one mixed SNS BU — not as two pure SNS models combined.

Why:

  • Real stations share infrastructure, demand pools, overheads, and slot-allocation decisions across battery types. Splitting into pure models fabricates P&L boundaries that do not exist; recombining loses the interactions that determine profitability (slot allocation, demand cannibalization between segments, mixed-fleet rotation scheduling).
  • Forcing all variants into one generic parameterized engine instead creates combinatory complexity: every equation grows a product-variant index, most combinations are unphysical, and the business logic becomes unreadable. Formally unitary, semantically opaque.
  • Business common sense wins: the model must read like the business. A mixed SNS reads like a mixed station.

Battery types inside a mixed BU are structured data — a fleet composition (list of battery classes with Ah rating, kWh, charger requirement, share) driving the mixed rotation and allocation logic — not separate objects and not flattened generic parameters.

Decision rule going forward:

Situation Modeling choice
Variants share operational behavior (same cabinet family, minor capacity difference) one BU class, parameters differ
Variants differ operationally (different cabinets, charger phases, customer segments) but coexist at one site one mixed BU, structured fleet composition
Genuinely separate operations (different sites, different P&L) separate BU objects

The Profile-Swap Test (acceptance gate)

A BU model is unitary iff: swapping the profile (Kenya → Tanzania: FX, tariffs, cost anchors) runs the same code unchanged with correct results. Product-configuration differences are handled by the granularity rule above — new structured composition, not a code fork.

Standing pytest fixture: same BU object built with two profiles; assert structural outputs (ratio relationships, breakeven period index) match and no hardcoded unit leaks.

Actionable implementation steps

  1. Dimension tags in the parameter schema (schema.py): dimension field per Parameter (usd | usd_per_kwh | energy | power | count | time | dimensionless).
  2. Canonicalization at the boundary: load_profile() validates and delivers USD + ISO values; local-currency inputs must declare their FX source row.
  3. BU objects accept canonical input only — setters reject non-canonical quantities (assert, fail fast).
  4. Presentation layer converts USD → local currency via published FX at report/bundle time only.
  5. Profile-Swap Test fixture in models/tests/ — the unitary gate, run for every BU class.
  6. Fleet-composition structure in the SNS schema: ordered battery-class list driving mixed allocation logic.

What this buys

  • One logic codebase, N markets — bundles differ by profile files, not code forks.
  • USD basis keeps model outputs internationally comparable and investor-ready; FX is a mechanical step, never a modeling question.
  • Specific configurations (100 Ah + 6 kW 3-phase) keep the model honest to the business; mixed BUs keep the P&L honest to the station.
  • Dimensionally homogeneous equations are self-checking: a unit bug cannot hide.

Next step (4.4): map Maxwell v12 onto the BU catalog + transaction map — line-by-line provenance, adopt/contrast/reject table.