Coerce raw URPX parser output into typed Clojure values.
The parser (urpx.core/load-rate-plan) produces a Clojure map with
namespaced keys but all-string leaf values. This namespace applies the
:decode/urpx-jsonld decoders attached to urpx.schema schemas to convert
those strings into BigDecimals, LocalDates, LocalTimes, LocalDateTimes,
Durations, and integer month numbers — and to normalize fields that URPX
permits as either a single map or a vector of maps.
Coerce raw URPX parser output into typed Clojure values. The parser (`urpx.core/load-rate-plan`) produces a Clojure map with namespaced keys but all-string leaf values. This namespace applies the `:decode/urpx-jsonld` decoders attached to `urpx.schema` schemas to convert those strings into BigDecimals, LocalDates, LocalTimes, LocalDateTimes, Durations, and integer month numbers — and to normalize fields that URPX permits as either a single map or a vector of maps.
URPX rate plan loading and price resolution.
Parses URPX JSON-LD rate plan documents into Clojure data structures and resolves prices for any given timestamp and timezone.
URPX rate plan loading and price resolution. Parses URPX JSON-LD rate plan documents into Clojure data structures and resolves prices for any given timestamp and timezone.
Idiomatic Clojure authoring for URPX entities. Output of every builder is
a coerced (typed) entity ready for urpx.price/resolve-prices,
urpx.emit/write-*, or any other consumer of urpx.schema-shaped data.
(require '[urpx.dsl :as dsl])
(dsl/dsl-scope (dsl/season ::summer :name "Summer" :month [5 6 7 8 9 10]) (dsl/season ::winter :name "Winter" :month [11 12 1 2 3 4]) (dsl/price-definition ::pd-summer :name "Commodity Summer" :applies-in-season ::summer :references-ledger ::ledger-commodity))
dsl-scope macro establishing a local-symbol-ref scopemake-builder generator: schema → constructor functionentity-type introspect a schema for its @type literalurpx.schema
(season, metric-input, price-definition,
rate-plan, urpx-document, …)Every builder:
:jsonld/type automatically from the schema (no boilerplate).:urpx/* keys (:urpx/seasonName) or auto-derived kebab-case short
forms (:season-name); both can be mixed in one call.:decode/urpx-jsonld
decoders: number → BigDecimal, ISO string → LocalDate /
LocalDateTime / LocalTime, --MM xsd:gMonth → int month-of-year,
ISO 8601 string → Duration / Period.nil-valued kwargs (so (builder :foo (when … x)) cleanly
omits the field). Explicit false is preserved.ex-info with the Malli explanation on failure.Inside a dsl-scope, builders additionally:
@id (e.g. (season ::summer …) registers ::summer in the
scope and sets :jsonld/id "summer").{:jsonld/id …} Ref maps. Vector-typed kwargs walk
element-wise.Resolution is eager (declare-before-reference): an unknown ref or a
duplicate declaration throws. Each dsl-scope has its own registry —
scopes don't leak.
Idiomatic Clojure authoring for URPX entities. Output of every builder is
a coerced (typed) entity ready for `urpx.price/resolve-prices`,
`urpx.emit/write-*`, or any other consumer of `urpx.schema`-shaped data.
## At a glance
(require '[urpx.dsl :as dsl])
(dsl/dsl-scope
(dsl/season ::summer :name "Summer" :month [5 6 7 8 9 10])
(dsl/season ::winter :name "Winter" :month [11 12 1 2 3 4])
(dsl/price-definition ::pd-summer
:name "Commodity Summer"
:applies-in-season ::summer
:references-ledger ::ledger-commodity))
## Public API
- `dsl-scope` macro establishing a local-symbol-ref scope
- `make-builder` generator: schema → constructor function
- `entity-type` introspect a schema for its @type literal
- 37 named builders one per entity-type schema in `urpx.schema`
(`season`, `metric-input`, `price-definition`,
`rate-plan`, `urpx-document`, …)
## Builder semantics
Every builder:
- Sets `:jsonld/type` automatically from the schema (no boilerplate).
- Accepts kwargs (or a Clojure 1.11+ trailing map) of either canonical
`:urpx/*` keys (`:urpx/seasonName`) or auto-derived kebab-case short
forms (`:season-name`); both can be mixed in one call.
- Auto-coerces raw inputs through the schema's `:decode/urpx-jsonld`
decoders: number → `BigDecimal`, ISO string → `LocalDate` /
`LocalDateTime` / `LocalTime`, `--MM` xsd:gMonth → int month-of-year,
ISO 8601 string → `Duration` / `Period`.
- Drops `nil`-valued kwargs (so `(builder :foo (when … x))` cleanly
omits the field). Explicit `false` is preserved.
- Validates the assembled entity against the schema and throws
`ex-info` with the Malli explanation on failure.
Inside a `dsl-scope`, builders additionally:
- Accept an optional leading qualified-keyword arg as the entity's
local `@id` (e.g. `(season ::summer …)` registers `::summer` in the
scope and sets `:jsonld/id "summer"`).
- Resolve qualified-keyword kwarg values against the scope's registry
to produce `{:jsonld/id …}` Ref maps. Vector-typed kwargs walk
element-wise.
Resolution is eager (declare-before-reference): an unknown ref or a
duplicate declaration throws. Each `dsl-scope` has its own registry —
scopes don't leak.Write-side: emit URPX JSON-LD from coerced Clojure entities.
Symmetric to the existing parse + coerce path. Given a coerced urpx:RatePlan / urpx:RatePlanModifier / urpx:URPXDocument (or an EDN map shaped like one with namespaced-keyword keys + already-string values), emit a SHACL-valid JSON-LD string ready to write to disk or send to a consumer.
Pipeline (per call):
Round-trip success bar: logical/RDF-graph equivalence
(parse → coerce → write → re-parse → re-coerce yields a structurally
equal entity). Byte-for-byte equality with the input is NOT a goal —
key ordering, single-vs-vector property emission, and @context
recreation may differ from the source document. The vendored-fixture
round-trip suite in urpx.emit-test pins the =-equality contract over
every PG&E filing in test/resources/urpx-fixtures/ plus a URPXDocument-
wrapped E-ELEC test case from the URPX upstream.
@context emission: every write-* function accepts an optional opts map
with :context. Three states:
default-context
(URPX prefix declarations + typed-property declarations matching what
vendored fixtures carry).:context map → emit that map verbatim as @context.:context nil → emit no @context (used by round-trip tests
where the input doesn't carry one and =-equality would mismatch).An @context already on the input entity (:jsonld/context, e.g. from
a parsed document) is preserved untouched — the default never clobbers
a context the consumer already supplied.
Write-side: emit URPX JSON-LD from coerced Clojure entities.
Symmetric to the existing parse + coerce path. Given a coerced
urpx:RatePlan / urpx:RatePlanModifier / urpx:URPXDocument (or an EDN
map shaped like one with namespaced-keyword keys + already-string values),
emit a SHACL-valid JSON-LD string ready to write to disk or send to a
consumer.
Pipeline (per call):
1. urpx.coerce/encode runs the schema's :encode/urpx-jsonld transformer
— turns BigDecimal / LocalDate / LocalDateTime / LocalTime / Duration
/ int-month back into the JSON-LD string forms (xsd:decimal,
xsd:date, xsd:dateTime, xsd:time, xsd:duration, xsd:gMonth).
Already-string values pass through unchanged.
2. urpx.core/keys->jsonld walks the result, converting namespaced
keyword keys back to their CURIE / @-reserved JSON-LD forms.
3. clojure.data.json/write-str serializes to JSON.
Round-trip success bar: logical/RDF-graph equivalence
(parse → coerce → write → re-parse → re-coerce yields a structurally
equal entity). Byte-for-byte equality with the input is NOT a goal —
key ordering, single-vs-vector property emission, and @context
recreation may differ from the source document. The vendored-fixture
round-trip suite in urpx.emit-test pins the `=`-equality contract over
every PG&E filing in test/resources/urpx-fixtures/ plus a URPXDocument-
wrapped E-ELEC test case from the URPX upstream.
@context emission: every write-* function accepts an optional opts map
with `:context`. Three states:
- opts omits :context (or doesn't pass opts) → emit `default-context`
(URPX prefix declarations + typed-property declarations matching what
vendored fixtures carry).
- opts has `:context map` → emit that map verbatim as @context.
- opts has `:context nil` → emit no @context (used by round-trip tests
where the input doesn't carry one and `=`-equality would mismatch).
An @context already on the input entity (`:jsonld/context`, e.g. from
a parsed document) is preserved untouched — the default never clobbers
a context the consumer already supplied.Derive a holiday-date predicate from a rate plan's embedded urpx:HolidayCalendar.
URPX rate plans may embed a HolidayCalendar at :urpx/hasRatePlanVersion → :urpx/hasPlanElements → :urpx/hasHolidayCalendar listing each Holiday with a urpx:holidayName plus either:
derive-predicate returns a (java.time.LocalDate -> bool) predicate, or
nil when the plan has no embedded calendar. The predicate covers any year
on demand; per-year date sets are computed once and cached internally so
driving price-schedule across thousands of timestamps doesn't repeat work.
Recognized urpx:holidayRule patterns:
When a Holiday carries an explicit urpx:observanceRule (Ref to one of urpx:actualDateObservance / urpx:nearestWeekdayObservance / urpx:mondayIfWeekendObservance / urpx:fridayIfWeekendObservance) it overrides the implicit observance derived from the rule string.
Unrecognized rules throw ex-info — pass an explicit :holiday? predicate
in urpx.price/resolve-prices opts to override the derived calendar.
Derive a holiday-date predicate from a rate plan's embedded
urpx:HolidayCalendar.
URPX rate plans may embed a HolidayCalendar at
:urpx/hasRatePlanVersion → :urpx/hasPlanElements → :urpx/hasHolidayCalendar
listing each Holiday with a urpx:holidayName plus either:
- urpx:holidayDate — literal xsd:date for one specific instance, OR
- urpx:holidayRule — free-form string like 'Third Monday in February'
or 'January 1 (legally observed)'.
`derive-predicate` returns a `(java.time.LocalDate -> bool)` predicate, or
nil when the plan has no embedded calendar. The predicate covers any year
on demand; per-year date sets are computed once and cached internally so
driving `price-schedule` across thousands of timestamps doesn't repeat work.
Recognized urpx:holidayRule patterns:
- 'First|Second|Third|Fourth|Fifth <Weekday> in <Month>'
- 'Last <Weekday> in <Month>'
- '<Month> <Day>' — observed on the actual date
- '<Month> <Day> (legally observed)' — observed on the nearest weekday
when the actual date falls on
a weekend (Sat → Fri, Sun → Mon)
When a Holiday carries an explicit urpx:observanceRule (Ref to one of
urpx:actualDateObservance / urpx:nearestWeekdayObservance /
urpx:mondayIfWeekendObservance / urpx:fridayIfWeekendObservance) it
overrides the implicit observance derived from the rule string.
Unrecognized rules throw ex-info — pass an explicit `:holiday?` predicate
in `urpx.price/resolve-prices` opts to override the derived calendar.Build and query an entity index for URPX documents.
URPX JSON-LD documents are graphs: most entities carry a :jsonld/id and refer
to other entities by reference shape {:jsonld/id "urpx:foo"}. Coercion
leaves these references intact; consumers dereference them on demand against
an index keyed by id string.
Note: many references point to vocabulary IRIs in the URPX ontology (e.g. urpx:peakTier, urpx:allDays, urpx:bundledLedger) rather than entities defined in the document. Those lookups return nil — callers distinguish internal refs (resolve to a node) from vocabulary terms (do not).
Build and query an entity index for URPX documents.
URPX JSON-LD documents are graphs: most entities carry a :jsonld/id and refer
to other entities by reference shape `{:jsonld/id "urpx:foo"}`. Coercion
leaves these references intact; consumers dereference them on demand against
an index keyed by id string.
Note: many references point to vocabulary IRIs in the URPX ontology
(e.g. urpx:peakTier, urpx:allDays, urpx:bundledLedger) rather than entities
defined in the document. Those lookups return nil — callers distinguish
internal refs (resolve to a node) from vocabulary terms (do not).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.Generate contiguous price-interval schedules from a coerced URPX rate plan.
price-schedule walks a [start, end) instant window in the rate plan's
local timezone, calls urpx.price/resolve-prices at each step, and
merges adjacent steps whose ledger resolutions are identical.
Output is a vector of interval maps each carrying :tick/beginning and
:tick/end (java.time.ZonedDateTime in the resolved zone — DST-correct
end-to-end) plus the full urpx.price resolved structure under
:urpx.interval/resolved. Callers that want plain java.time.Instant
values invoke .toInstant themselves at the call site.
Default step is 1 hour, anchored to the start-of-hour of start. Pass
{:step (Duration/ofMinutes 15)} etc. for finer granularity when a rate
plan defines sub-hour TOU bracket transitions.
Generate contiguous price-interval schedules from a coerced URPX rate plan.
`price-schedule` walks a [start, end) instant window in the rate plan's
local timezone, calls `urpx.price/resolve-prices` at each step, and
merges adjacent steps whose ledger resolutions are identical.
Output is a vector of interval maps each carrying `:tick/beginning` and
`:tick/end` (java.time.ZonedDateTime in the resolved zone — DST-correct
end-to-end) plus the full `urpx.price` resolved structure under
`:urpx.interval/resolved`. Callers that want plain `java.time.Instant`
values invoke `.toInstant` themselves at the call site.
Default step is 1 hour, anchored to the start-of-hour of `start`. Pass
`{:step (Duration/ofMinutes 15)}` etc. for finer granularity when a rate
plan defines sub-hour TOU bracket transitions.Malli schemas for URPX v0.2.0 entities, describing the coerced shape.
Two-layer model (mirroring the clj-oa3 pattern):
Schemas carry :decode/urpx-jsonld decoders that turn the raw parser output
(strings) into the typed shape described here. The transformer is built and
applied in urpx.coerce. Reference resolution keeps :jsonld/id references
intact; consumers dereference via an entity index.
Schemas are bare def vars referenced by symbol — there is no central registry except inside ConditionExpression, where the recursive Comparison/Boolean operand tree uses a local Malli registry.
Malli schemas for URPX v0.2.0 entities, describing the coerced shape.
Two-layer model (mirroring the clj-oa3 pattern):
- urpx.core — raw parse: JSON-LD with namespaced keys, all string values.
- urpx.schema — coerced shape: typed Clojure values (BigDecimal, LocalDate,
LocalTime, LocalDateTime, Duration, integer month numbers).
Schemas carry `:decode/urpx-jsonld` decoders that turn the raw parser output
(strings) into the typed shape described here. The transformer is built and
applied in `urpx.coerce`. Reference resolution keeps :jsonld/id references
intact; consumers dereference via an entity index.
Schemas are bare def vars referenced by symbol — there is no central registry
except inside ConditionExpression, where the recursive Comparison/Boolean
operand tree uses a local Malli registry.Best-effort timezone inference for URPX rate plans whose
urpx:timezoneIdentifier is absent.
This namespace is opt-in — urpx.schedule does not call it
automatically. Callers that want plug-and-play timezone resolution
compose inference with the declared field at the call site:
(or (some-> plan :urpx/timezoneIdentifier ZoneId/of)
(:zone (urpx.tz-inference/infer-zone plan)))
Inference uses utility-identifying fields on the publishing
Organization: urpx:eiaId (preferred — official US energy reporting
code) and urpx:legalName (fallback). When the utility is unknown to
the lookup tables, or known to operate across more than one IANA
zone, infer-zone returns nil so the caller can fail loudly rather
than silently pick one.
The shipped tables only cover utilities used by the clj-urpx test suite; production deployments are expected to supply their own table (e.g. derived from EIA Form 861).
Best-effort timezone inference for URPX rate plans whose
`urpx:timezoneIdentifier` is absent.
This namespace is **opt-in** — `urpx.schedule` does not call it
automatically. Callers that want plug-and-play timezone resolution
compose inference with the declared field at the call site:
(or (some-> plan :urpx/timezoneIdentifier ZoneId/of)
(:zone (urpx.tz-inference/infer-zone plan)))
Inference uses utility-identifying fields on the publishing
Organization: `urpx:eiaId` (preferred — official US energy reporting
code) and `urpx:legalName` (fallback). When the utility is unknown to
the lookup tables, or known to operate across more than one IANA
zone, `infer-zone` returns `nil` so the caller can fail loudly rather
than silently pick one.
The shipped tables only cover utilities used by the clj-urpx test
suite; production deployments are expected to supply their own table
(e.g. derived from EIA Form 861).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 |