Resolve applicable per-energy prices for a URPX rate plan at a given instant.
Given a coerced rate plan (urpx.coerce/coerce-rate-plan) and a
ZonedDateTime, resolve-prices returns the season, the matched TOU period
(if any), and one entry per active per-energy ledger (commodity, distribution,
public benefits, etc.). Each ledger entry carries a :tiers vector — for
block-tiered ledgers (CPAU E-1) every tier's bound and unit-price is
surfaced so the caller can pick the active one based on cumulative usage;
for non-tiered ledgers the vector has a single entry.
Skipped:
:urpx/hasCalculationMethod is set
(recurring fixed charges like a monthly customer charge — these are
subscription-style and don't apply to instantaneous lookup).Day types honored (URPX ontology): urpx:allDays, urpx:weekdayDays, urpx:weekendDays, urpx:customDays. Holiday inclusion is expressed by the TimeBracket's optional urpx:includeHolidays / urpx:includeNonHolidays booleans (both default to true when omitted). Holiday detection sources, in priority order:
:holiday? predicate in opts (highest precedence — overrides).urpx.holidays/derive-predicate.Plans without a HolidayCalendar and called without :holiday? retain the
pre-existing 'every date is a non-holiday' fallback — so brackets that
opt out of non-holidays (urpx:includeNonHolidays = false) will never match.
Resolve applicable per-energy prices for a URPX rate plan at a given instant.
Given a coerced rate plan (`urpx.coerce/coerce-rate-plan`) and a
ZonedDateTime, `resolve-prices` returns the season, the matched TOU period
(if any), and one entry per active per-energy ledger (commodity, distribution,
public benefits, etc.). Each ledger entry carries a `:tiers` vector — for
block-tiered ledgers (CPAU E-1) every tier's bound and unit-price is
surfaced so the caller can pick the active one based on cumulative usage;
for non-tiered ledgers the vector has a single entry.
Skipped:
- PriceDefinitions whose own `:urpx/hasCalculationMethod` is set
(recurring fixed charges like a monthly customer charge — these are
subscription-style and don't apply to instantaneous lookup).
Day types honored (URPX ontology): urpx:allDays, urpx:weekdayDays,
urpx:weekendDays, urpx:customDays. Holiday inclusion is expressed by the
TimeBracket's optional urpx:includeHolidays / urpx:includeNonHolidays
booleans (both default to true when omitted). Holiday detection sources,
in priority order:
1. Explicit `:holiday?` predicate in opts (highest precedence — overrides).
2. The plan's embedded urpx:HolidayCalendar, derived via
`urpx.holidays/derive-predicate`.
3. Fallback: every date treated as a non-holiday.
Plans without a HolidayCalendar and called without `:holiday?` retain the
pre-existing 'every date is a non-holiday' fallback — so brackets that
opt out of non-holidays (urpx:includeNonHolidays = false) will never match.(has-energy-ledger? resolved)True if resolved carries any ledger measured in urpx:energy, which is the
precondition for marginal-unit-rate returning a number.
Ask this before calling it when sweeping a corpus. Plenty of real schedules price nothing per kWh (street lighting, fixed service charges), and for those a marginal per-kWh rate is not a small number, it is a question that does not apply.
True if `resolved` carries any ledger measured in urpx:energy, which is the precondition for `marginal-unit-rate` returning a number. Ask this before calling it when sweeping a corpus. Plenty of real schedules price nothing per kWh (street lighting, fixed service charges), and for those a marginal per-kWh rate is not a small number, it is a question that does not apply.
(marginal-unit-rate resolved)(marginal-unit-rate resolved opts)The marginal $/kWh at the start of a billing period: summed across all energy ledgers, and within each ledger the sum of its ADDITIVE rate components plus the LOWEST rung of its block tier ladder, if it has one.
A ledger is used for two different things in practice. A block tier ladder is mutually exclusive, so only the marginal rung counts; an additive rate stack (a delivery ledger carrying transmission, distribution, public purpose and the rest) is cumulative, so all of it counts. urpx:serviceTierNumber is what separates them: a numbered tier belongs to a ladder, a bare one is a component. URPX has no vocabulary saying which a ledger is, so this is the signal available rather than the signal designed, and a ledger mixing both contributes its components plus the lowest rung of that ladder.
A ledger is treated as energy iff its referenced MetricInput's urpx:measurementKind is urpx:energy. Per-meter, per-customer, per-day, or per-month service charges (urpx:profileMetricInput, or non-energy measurementKinds) are excluded — they have a different shape and don't belong in a $/kWh sum.
Useful for price signaling: when you don't know a customer's usage but want a single $/kWh number, this is the marginal rate at zero baseline usage.
REFUSES, rather than returning a number, in six cases, each carrying a
named :reason in its ex-data so a sweeping consumer can classify:
:no-energy-ledger this plan prices nothing per kWh, so the question does not apply to it. 0M would be indistinguishable from a plan whose energy ledgers genuinely sum to zero :unvaluable-marginal-tier a marginal tier could not be valued. A nil unit price reads to a consumer as free, and for a discount that is the worst available answer :unselected-alternatives an energy ledger carries mutually-exclusive price alternatives with no caller selection, AND either at least one of the withheld prices would have entered this sum, or nothing is recorded about what was withheld. Withholding a price the sum would not have taken costs the caller nothing and does not refuse; not KNOWING what was withheld is not evidence that it did not matter, so that refuses :condition-gated-tier a price in the sum is gated by a urpx:ConditionExpression this library does not evaluate, so including it would report an assumption as a number. Pass :assume-conditions-hold? true for the every-condition-true upper bound :all-energy-tiers-excluded every energy tier was event-driven, recurring, or both. Free is not the answer either :outside-version-window the version used does not describe this instant
The alternatives case is why composing a PG&E plan with its vintage-PCIA
modifier needs :profile-alternatives. Taking the first of eighteen mutually
exclusive prices added an arbitrary 0.02973 to the rate; the eighteen span
0.06391/kWh, so there is no default worth inventing.
EVENT-DRIVEN PRICES ARE EXCLUDED BY DEFAULT. A Price carrying
urpx:hasDynamicPricing applies only while its event is called: a critical
peak charge is real but exceptional, capped around 50 hours a summer against
roughly 2,200 summer hours. Summing it unconditionally answered what would this cost during an event', which read as the ordinary rate. The default is now the non-event baseline, which is what a price signal means bythe next
kWh'. Unlike the alternatives above there IS a defensible default here,
because no-event-today is the ordinary state rather than an arbitrary pick.
Pass {:include-dynamic-pricing? true} for the during-event upper bound.
Either way :urpx.resolved/dynamic-pricing on the tier says which prices
are event-driven, so a caller can compute both.
The marginal $/kWh at the start of a billing period: summed across all
**energy** ledgers, and within each ledger the sum of its ADDITIVE rate
components plus the LOWEST rung of its block tier ladder, if it has one.
A ledger is used for two different things in practice. A block tier ladder
is mutually exclusive, so only the marginal rung counts; an additive rate
stack (a delivery ledger carrying transmission, distribution, public purpose
and the rest) is cumulative, so all of it counts. urpx:serviceTierNumber is
what separates them: a numbered tier belongs to a ladder, a bare one is a
component. URPX has no vocabulary saying which a ledger is, so this is the
signal available rather than the signal designed, and a ledger mixing both
contributes its components plus the lowest rung of that ladder.
A ledger is treated as energy iff its referenced MetricInput's
urpx:measurementKind is urpx:energy. Per-meter, per-customer, per-day, or
per-month service charges (urpx:profileMetricInput, or non-energy
measurementKinds) are excluded — they have a different shape and don't
belong in a $/kWh sum.
Useful for price signaling: when you don't know a customer's usage but want
a single $/kWh number, this is the marginal rate at zero baseline usage.
REFUSES, rather than returning a number, in six cases, each carrying a
named `:reason` in its ex-data so a sweeping consumer can classify:
:no-energy-ledger this plan prices nothing per kWh, so the
question does not apply to it. 0M would be
indistinguishable from a plan whose energy
ledgers genuinely sum to zero
:unvaluable-marginal-tier a marginal tier could not be valued. A nil
unit price reads to a consumer as free, and
for a discount that is the worst available
answer
:unselected-alternatives an energy ledger carries mutually-exclusive
price alternatives with no caller selection,
AND either at least one of the withheld prices
would have entered this sum, or nothing is
recorded about what was withheld. Withholding
a price the sum would not have taken costs the
caller nothing and does not refuse; not
KNOWING what was withheld is not evidence that
it did not matter, so that refuses
:condition-gated-tier a price in the sum is gated by a
urpx:ConditionExpression this library does not
evaluate, so including it would report an
assumption as a number. Pass
:assume-conditions-hold? true for the
every-condition-true upper bound
:all-energy-tiers-excluded every energy tier was event-driven, recurring,
or both. Free is not the answer either
:outside-version-window the version used does not describe this
instant
The alternatives case is why composing a PG&E plan with its vintage-PCIA
modifier needs `:profile-alternatives`. Taking the first of eighteen mutually
exclusive prices added an arbitrary 0.02973 to the rate; the eighteen span
0.06391/kWh, so there is no default worth inventing.
EVENT-DRIVEN PRICES ARE EXCLUDED BY DEFAULT. A Price carrying
urpx:hasDynamicPricing applies only while its event is called: a critical
peak charge is real but exceptional, capped around 50 hours a summer against
roughly 2,200 summer hours. Summing it unconditionally answered `what would
this cost during an event', which read as the ordinary rate. The default is
now the non-event baseline, which is what a price signal means by `the next
kWh'. Unlike the alternatives above there IS a defensible default here,
because no-event-today is the ordinary state rather than an arbitrary pick.
Pass `{:include-dynamic-pricing? true}` for the during-event upper bound.
Either way `:urpx.resolved/dynamic-pricing` on the tier says which prices
are event-driven, so a caller can compute both.(measurement-kinds resolved)The set of urpx:measurementKind @ids present across resolved's ledgers.
What a caller usually wants to know, having found that a plan prices nothing per kWh, is what it DOES price in, so this answers with the kinds rather than a bare yes or no and lets a sweeping consumer route instead of merely skip.
May be empty. CPAU E-14 is the case: a street lighting schedule whose charges are all per-lamp-per-month and per-fixture-per-month, filed as recurring charges with their own CalculationMethod, which price resolution excludes. Nothing resolves, so nothing is reported, and an empty set says exactly that.
The set of urpx:measurementKind @ids present across `resolved`'s ledgers. What a caller usually wants to know, having found that a plan prices nothing per kWh, is what it DOES price in, so this answers with the kinds rather than a bare yes or no and lets a sweeping consumer route instead of merely skip. May be empty. CPAU E-14 is the case: a street lighting schedule whose charges are all per-lamp-per-month and per-fixture-per-month, filed as recurring charges with their own CalculationMethod, which price resolution excludes. Nothing resolves, so nothing is reported, and an empty set says exactly that.
(resolve-modifier-prices modifier zdt)Resolve every per-energy unit price that modifier (a coerced
urpx:RatePlanModifier) contributes at zdt.
Returns the same shape as resolve-prices minus the season/TOU keys
(modifier ledgers are flat add-ons): {:urpx.resolved/ledgers [...]}.
When the modifier carries its own urpx:hasTOUSchedule, PDs with :urpx/touPeriodNumber are gated against the modifier's resolved period. Without an own TOUSchedule and no base context, every applicable Price is surfaced — matrix-style modifiers (e.g. CPAU E-HRA — 4x4 reserve level x generation tier driven by external state) rely on this so the caller can select the active value, since the URPX condition vocabulary (operator + label conditionValue) is not yet specified in a way that's programmatically interpretable. Filter the returned :tiers vector on :urpx.resolved/price-id when needed.
Resolve every per-energy unit price that `modifier` (a coerced
urpx:RatePlanModifier) contributes at `zdt`.
Returns the same shape as `resolve-prices` minus the season/TOU keys
(modifier ledgers are flat add-ons): {:urpx.resolved/ledgers [...]}.
When the modifier carries its own urpx:hasTOUSchedule, PDs with
:urpx/touPeriodNumber are gated against the modifier's resolved period.
Without an own TOUSchedule and no base context, every applicable Price
is surfaced — matrix-style modifiers (e.g. CPAU E-HRA — 4x4 reserve
level x generation tier driven by external state) rely on this so the
caller can select the active value, since the URPX condition
vocabulary (operator + label conditionValue) is not yet specified in a
way that's programmatically interpretable. Filter the returned :tiers
vector on :urpx.resolved/price-id when needed.(resolve-prices rate-plan zdt)(resolve-prices rate-plan zdt opts)Resolve all per-energy unit prices that apply to rate-plan at zdt
(a java.time.ZonedDateTime in the rate plan's local timezone).
Optional opts map:
:holiday? predicate (LocalDate -> bool) used to evaluate a bracket's
urpx:includeHolidays / urpx:includeNonHolidays gates. A plain
set of LocalDates works (sets are functions). When omitted,
a predicate is derived from the plan's embedded
urpx:HolidayCalendar via urpx.holidays/derive-predicate;
if neither is available, every date is treated as a
non-holiday — so brackets that opt out of non-holidays
(urpx:includeNonHolidays = false, e.g. holiday-only off-peak
overrides) will never match.
:profile-alternatives a {<ProfileAlternatives @id> <ProfileAlternative @id>} map driving customer-profile-driven price selection, keyed by the ProfileAlternatives CONTAINER. It scopes both Prices.hasPriceSetAlternatives (income-tier CARE/FERA/Standard, vintage-PCIA departure year, voltage configuration) and PriceDefinition.referencesProfileAlternative.
A dimension the caller says nothing about is NOT skipped: its
prices are withheld, the ledger carries
:urpx.resolved/unselected-alternatives, and marginal-unit-rate
refuses if withholding them would make its sum short. A value
the keyed container does not list is refused outright, since
compared for equality a stale id is indistinguishable from a
deliberate choice of some other cell.
Programmatic ConditionExpression evaluation against a customer
profile is a future enhancement; a consumer names the cell.
Examples of :holiday?:
;; Most rate plans embed urpx:HolidayCalendar — no opts needed: (resolve-prices plan zdt)
;; Static set — overrides any embedded calendar: (def us-federal-2026 #{(LocalDate/of 2026 1 1) ; New Year's Day (LocalDate/of 2026 1 19) ; Martin Luther King Jr. Day (LocalDate/of 2026 2 16) ; Presidents Day (LocalDate/of 2026 5 25) ; Memorial Day (LocalDate/of 2026 6 19) ; Juneteenth (LocalDate/of 2026 7 3) ; Independence Day (observed) (LocalDate/of 2026 9 7) ; Labor Day (LocalDate/of 2026 11 11) ; Veterans Day (LocalDate/of 2026 11 26) ; Thanksgiving (LocalDate/of 2026 12 25)}) ; Christmas (resolve-prices plan zdt {:holiday? us-federal-2026})
;; Composed predicate — if you maintain a calendar elsewhere: (resolve-prices plan zdt {:holiday? (fn [d] (calendar/observed? :us-federal d))})
Returns: {:urpx.resolved/season-name String or nil :urpx.resolved/tou-period-name String or nil :urpx.resolved/tou-period-number int or nil :urpx.resolved/ledgers [LedgerResult]}
where each LedgerResult is: {:urpx.resolved/ledger-id String :urpx.resolved/ledger-name String :urpx.resolved/ledger-type Ref :urpx.resolved/metric-input-id String (when the ledger references one) :urpx.resolved/metric-input-type Ref (e.g. urpx:measuredMetricInput) :urpx.resolved/measurement-kind Ref (e.g. urpx:energy — present when the MetricInput carries one; marginal-unit-rate filters on this) :urpx.resolved/unit-of-measure Ref (e.g. urpx:kWh) :urpx.resolved/tiers [TierResult] :urpx.resolved/unselected-alternatives [String] (present iff prices on this ledger belong to alternatives the caller chose none of) :urpx.resolved/withheld-prices [WithheldPrice] (what those alternatives would have priced, enough of it to say whether a marginal sum is actually short)}
and each TierResult is: {:urpx.resolved/price-definition-id String :urpx.resolved/price-id String :urpx.resolved/price-name String :urpx.resolved/unit-price BigDecimal :urpx.resolved/tier-number int (only on block-tiered ledgers) :urpx.resolved/tier-name String :urpx.resolved/tier-lower-bound BigDecimal :urpx.resolved/tier-upper-bound BigDecimal :urpx.resolved/tier-lower-bound-operator Ref :urpx.resolved/tier-upper-bound-operator Ref :urpx.resolved/dynamic-pricing Map (present iff the source Price carries urpx:hasDynamicPricing — a CPP Event Charge, RTP price, or other event-driven component; consumers that need a non-event marginal rate filter on the absence of this key) :urpx.resolved/condition-gated [Gate] (present iff the source Price or its PriceDefinition carries a urpx:ConditionExpression this library does not evaluate, so the price fired on an ASSUMPTION that its predicate holds. Each Gate carries :gate-id, :gate-name and :gate-on, the last being :price or :price-definition. Not excluded from any aggregate: see marginal-unit-rate)}
For non-tiered ledgers, :tiers has a single entry with no tier-* keys. For block-tiered ledgers (e.g. CPAU E-1), :tiers carries every tier sorted by tier-number — the caller chooses based on cumulative billing-period usage.
Resolve all per-energy unit prices that apply to `rate-plan` at `zdt`
(a java.time.ZonedDateTime in the rate plan's local timezone).
Optional `opts` map:
:holiday? predicate `(LocalDate -> bool)` used to evaluate a bracket's
urpx:includeHolidays / urpx:includeNonHolidays gates. A plain
set of LocalDates works (sets are functions). When omitted,
a predicate is derived from the plan's embedded
urpx:HolidayCalendar via `urpx.holidays/derive-predicate`;
if neither is available, every date is treated as a
non-holiday — so brackets that opt out of non-holidays
(urpx:includeNonHolidays = false, e.g. holiday-only off-peak
overrides) will never match.
:profile-alternatives a {<ProfileAlternatives @id> <ProfileAlternative @id>}
map driving customer-profile-driven price selection, keyed by
the ProfileAlternatives CONTAINER. It scopes both
Prices.hasPriceSetAlternatives (income-tier CARE/FERA/Standard,
vintage-PCIA departure year, voltage configuration) and
PriceDefinition.referencesProfileAlternative.
A dimension the caller says nothing about is NOT skipped: its
prices are withheld, the ledger carries
:urpx.resolved/unselected-alternatives, and marginal-unit-rate
refuses if withholding them would make its sum short. A value
the keyed container does not list is refused outright, since
compared for equality a stale id is indistinguishable from a
deliberate choice of some other cell.
Programmatic ConditionExpression evaluation against a customer
profile is a future enhancement; a consumer names the cell.
Examples of `:holiday?`:
;; Most rate plans embed urpx:HolidayCalendar — no opts needed:
(resolve-prices plan zdt)
;; Static set — overrides any embedded calendar:
(def us-federal-2026
#{(LocalDate/of 2026 1 1) ; New Year's Day
(LocalDate/of 2026 1 19) ; Martin Luther King Jr. Day
(LocalDate/of 2026 2 16) ; Presidents Day
(LocalDate/of 2026 5 25) ; Memorial Day
(LocalDate/of 2026 6 19) ; Juneteenth
(LocalDate/of 2026 7 3) ; Independence Day (observed)
(LocalDate/of 2026 9 7) ; Labor Day
(LocalDate/of 2026 11 11) ; Veterans Day
(LocalDate/of 2026 11 26) ; Thanksgiving
(LocalDate/of 2026 12 25)}) ; Christmas
(resolve-prices plan zdt {:holiday? us-federal-2026})
;; Composed predicate — if you maintain a calendar elsewhere:
(resolve-prices plan zdt {:holiday? (fn [d] (calendar/observed? :us-federal d))})
Returns:
{:urpx.resolved/season-name String or nil
:urpx.resolved/tou-period-name String or nil
:urpx.resolved/tou-period-number int or nil
:urpx.resolved/ledgers [LedgerResult]}
where each LedgerResult is:
{:urpx.resolved/ledger-id String
:urpx.resolved/ledger-name String
:urpx.resolved/ledger-type Ref
:urpx.resolved/metric-input-id String (when the ledger references one)
:urpx.resolved/metric-input-type Ref (e.g. urpx:measuredMetricInput)
:urpx.resolved/measurement-kind Ref (e.g. urpx:energy — present when
the MetricInput carries one;
marginal-unit-rate filters on this)
:urpx.resolved/unit-of-measure Ref (e.g. urpx:kWh)
:urpx.resolved/tiers [TierResult]
:urpx.resolved/unselected-alternatives [String]
(present iff prices on this
ledger belong to alternatives
the caller chose none of)
:urpx.resolved/withheld-prices [WithheldPrice]
(what those alternatives would
have priced, enough of it to
say whether a marginal sum is
actually short)}
and each TierResult is:
{:urpx.resolved/price-definition-id String
:urpx.resolved/price-id String
:urpx.resolved/price-name String
:urpx.resolved/unit-price BigDecimal
:urpx.resolved/tier-number int (only on block-tiered ledgers)
:urpx.resolved/tier-name String
:urpx.resolved/tier-lower-bound BigDecimal
:urpx.resolved/tier-upper-bound BigDecimal
:urpx.resolved/tier-lower-bound-operator Ref
:urpx.resolved/tier-upper-bound-operator Ref
:urpx.resolved/dynamic-pricing Map (present iff the source Price
carries urpx:hasDynamicPricing
— a CPP Event Charge, RTP
price, or other event-driven
component; consumers that
need a non-event marginal
rate filter on the absence
of this key)
:urpx.resolved/condition-gated [Gate] (present iff the source Price
or its PriceDefinition
carries a
urpx:ConditionExpression
this library does not
evaluate, so the price fired
on an ASSUMPTION that its
predicate holds. Each Gate
carries :gate-id, :gate-name
and :gate-on, the last being
:price or :price-definition.
Not excluded from any
aggregate: see
marginal-unit-rate)}
For non-tiered ledgers, :tiers has a single entry with no tier-* keys.
For block-tiered ledgers (e.g. CPAU E-1), :tiers carries every tier sorted
by tier-number — the caller chooses based on cumulative billing-period usage.(resolve-prices-with-modifiers rate-plan modifiers zdt)(resolve-prices-with-modifiers rate-plan modifiers zdt opts)Resolve prices for rate-plan at zdt and append ledger results for
each modifier in modifiers whose :urpx/applicableToRatePlan list
includes the base plan's @id. Modifiers whose target lists don't include
the plan are silently skipped.
Returns the resolve-prices shape with :urpx.resolved/ledgers extended
by the modifier ledgers.
Resolve prices for `rate-plan` at `zdt` and append ledger results for each modifier in `modifiers` whose :urpx/applicableToRatePlan list includes the base plan's @id. Modifiers whose target lists don't include the plan are silently skipped. Returns the `resolve-prices` shape with :urpx.resolved/ledgers extended by the modifier ledgers.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |