Enumerate the service arrangements a set of URPX documents expresses.
A SERVICE ARRANGEMENT is one urpx:RatePlanVersion, plus every urpx:RatePlanModifierVersion compatible with it, plus one urpx:ProfileAlternative for each dimension any of them leaves open. Fully determined: given an instant inside its window it resolves to prices with no further input. If a choice is still outstanding it is not one arrangement but several, and each of those is an arrangement in its own right.
urpx:ServiceArrangement IS NOT A URPX CLASS. It occurs zero times in the
ontology and zero times in the SHACL shapes. It is a name for a thing the
standard describes the parts of and does not name the whole of, and nothing
here writes it into a document or claims a urpx: prefix for it. The concept
and its evidence are set out in urpx-rate-plans doc/service-arrangements.md;
the cardinalities this namespace relies on were checked against the shapes
graph rather than recalled.
WHAT THIS CAN AND CANNOT ENUMERATE. Which compatible modifiers a given CUSTOMER has is a fact about the customer, and URPX places customer data outside its scope: urpx:AccountServiceAgreement carries the skos:note "Service account and agreement data lives in customer data, not URPX." So this enumerates the arrangements the DOCUMENTS EXPRESS, every combination the filings say is permitted. That set is larger than any one customer's and is what a price server can publish.
OPTIONAL BY CONSTRUCTION. This namespace requires urpx.price; urpx.price does not require this. A consumer that wants prices for a composition it already knows how to assemble uses urpx.price directly and never loads this file. Nothing here changes an existing arity or return shape.
Enumerate the service arrangements a set of URPX documents expresses. A SERVICE ARRANGEMENT is one urpx:RatePlanVersion, plus every urpx:RatePlanModifierVersion compatible with it, plus one urpx:ProfileAlternative for each dimension any of them leaves open. Fully determined: given an instant inside its window it resolves to prices with no further input. If a choice is still outstanding it is not one arrangement but several, and each of those is an arrangement in its own right. `urpx:ServiceArrangement` IS NOT A URPX CLASS. It occurs zero times in the ontology and zero times in the SHACL shapes. It is a name for a thing the standard describes the parts of and does not name the whole of, and nothing here writes it into a document or claims a urpx: prefix for it. The concept and its evidence are set out in urpx-rate-plans doc/service-arrangements.md; the cardinalities this namespace relies on were checked against the shapes graph rather than recalled. WHAT THIS CAN AND CANNOT ENUMERATE. Which compatible modifiers a given CUSTOMER has is a fact about the customer, and URPX places customer data outside its scope: urpx:AccountServiceAgreement carries the skos:note "Service account and agreement data lives in customer data, not URPX." So this enumerates the arrangements the DOCUMENTS EXPRESS, every combination the filings say is permitted. That set is larger than any one customer's and is what a price server can publish. OPTIONAL BY CONSTRUCTION. This namespace requires urpx.price; urpx.price does not require this. A consumer that wants prices for a composition it already knows how to assemble uses urpx.price directly and never loads this file. Nothing here changes an existing arity or return shape.
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.
Read what a version snapshot CLAIMS about its own coverage.
Every urpx:RatePlanVersion and urpx:RatePlanModifierVersion carries a urpx:CoverageClaim: a verdict on what can be answered from that snapshot under stated conditions. The verdict is the standard's own answer to "what am I holding?", and this library modeled it and read it nowhere until now.
NOT A JUDGEMENT OF THIS LIBRARY'S. Per the ontology, CoverageClaim instances "are produced by an implementation or publishing workflow", so a claim says what the PUBLISHER asserts about a snapshot, not what clj-urpx found. Nothing here re-derives a verdict or second-guesses one; reading is the whole job.
OPTIONAL BY CONSTRUCTION. This requires urpx.index and nothing else of the library: not urpx.price, not urpx.arrangements. Nothing in src/ requires it, which a test asserts over the whole source tree.
WHAT THE CORPUS ACTUALLY SAYS, measured at urpx-rate-plans 9deae4dbaa8b over 80 distinct version nodes, because it shapes how much a verdict is worth:
completenessResult partial 76, complete 4; NOT ONE of the 40 base versions claims complete selfContained true on all 80, and VACUOUS: it is defined as "false iff any PriceDefinitionReference is referenceInclusion retrievedElsewhere", and there are zero PriceDefinitionReference nodes in the corpus or the fixtures, so the existential ranges over an empty set confidence absent on all 80 stale absent on all 80 fallbackSourced absent on all 80 explainedBy present on 76 of 80 TemporalScope ZERO, of any scopeKind, so no verdict anywhere states the window it covers
Two consequences worth carrying to a call site. selfContained true means
"nothing here is fetched from elsewhere", which is not "complete" and must
not be read as reassurance. And a verdict with no scope is the normal case, so
a consumer publishing a horizon cannot assume a verdict covers it.
The vendored fixtures are NOT representative on this axis: 6 of their 13 base versions claim complete where 0 of the corpus's 40 do. A test written only against fixtures shows a picture the production corpus never exhibits.
Read what a version snapshot CLAIMS about its own coverage.
Every urpx:RatePlanVersion and urpx:RatePlanModifierVersion carries a
urpx:CoverageClaim: a verdict on what can be answered from that snapshot under
stated conditions. The verdict is the standard's own answer to "what am I
holding?", and this library modeled it and read it nowhere until now.
NOT A JUDGEMENT OF THIS LIBRARY'S. Per the ontology, CoverageClaim instances
"are produced by an implementation or publishing workflow", so a claim says
what the PUBLISHER asserts about a snapshot, not what clj-urpx found. Nothing
here re-derives a verdict or second-guesses one; reading is the whole job.
OPTIONAL BY CONSTRUCTION. This requires urpx.index and nothing else of the
library: not urpx.price, not urpx.arrangements. Nothing in src/ requires it,
which a test asserts over the whole source tree.
WHAT THE CORPUS ACTUALLY SAYS, measured at urpx-rate-plans 9deae4dbaa8b over
80 distinct version nodes, because it shapes how much a verdict is worth:
completenessResult partial 76, complete 4; NOT ONE of the 40 base versions
claims complete
selfContained true on all 80, and VACUOUS: it is defined as "false
iff any PriceDefinitionReference is referenceInclusion
retrievedElsewhere", and there are zero
PriceDefinitionReference nodes in the corpus or the
fixtures, so the existential ranges over an empty set
confidence absent on all 80
stale absent on all 80
fallbackSourced absent on all 80
explainedBy present on 76 of 80
TemporalScope ZERO, of any scopeKind, so no verdict anywhere states
the window it covers
Two consequences worth carrying to a call site. `selfContained true` means
"nothing here is fetched from elsewhere", which is not "complete" and must
not be read as reassurance. And a verdict with no scope is the normal case, so
a consumer publishing a horizon cannot assume a verdict covers it.
The vendored fixtures are NOT representative on this axis: 6 of their 13 base
versions claim complete where 0 of the corpus's 40 do. A test written only
against fixtures shows a picture the production corpus never exhibits.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-package, coverage-claim, …),
with no exceptions. Where a schema dual-accepts
two @type spellings (source-publication), the
builder sets the v0.5.1 one; the schema still
validates the v0.2.x spelling on parse.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
- a named builder for every entity-type schema in `urpx.schema`
(`season`, `metric-input`, `price-definition`,
`rate-plan`, `urpx-package`, `coverage-claim`, …),
with no exceptions. Where a schema dual-accepts
two @type spellings (`source-publication`), the
builder sets the v0.5.1 one; the schema still
validates the v0.2.x spelling on parse.
## 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 / urpx:URPXPackage (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 / urpx:URPXPackage
(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).Turn SOURCES into coerced URPX documents.
A source is anything clojure.java.io/reader coerces: a File, URL, Reader,
InputStream or path string. Where those sources come from is the caller's
business and deliberately not this library's: a corpus repository is a one-off
and there will never be a layout everyone adopts, so walking a tree is
client-specific while turning what it finds into documents is not.
WHY THIS NAMESPACE EXISTS AT ALL. The pipeline it names was previously a
four-step form in the README that every consumer composed by hand, and the
first consumer to do so got it wrong in the one way that does not fail loudly:
mapv where mapcat belongs, and no coercion step, which returned a
plausible number rather than an error. Naming it makes that composition
unavailable to make rather than documented against. Nothing here is new
behavior; it is the same three functions in the one order that is correct.
It lives in its own namespace because it cannot live in either of the two it
spans. urpx.core requires only data.json, java.io and string, and
urpx.coerce requires only malli and urpx.schema; neither requires the
other. Putting this in core would drag Malli into the parse layer, and putting
it in coerce would make a coercion-only consumer pull in the parser.
Turn SOURCES into coerced URPX documents. A source is anything `clojure.java.io/reader` coerces: a File, URL, Reader, InputStream or path string. Where those sources come from is the caller's business and deliberately not this library's: a corpus repository is a one-off and there will never be a layout everyone adopts, so walking a tree is client-specific while turning what it finds into documents is not. WHY THIS NAMESPACE EXISTS AT ALL. The pipeline it names was previously a four-step form in the README that every consumer composed by hand, and the first consumer to do so got it wrong in the one way that does not fail loudly: `mapv` where `mapcat` belongs, and no coercion step, which returned a plausible number rather than an error. Naming it makes that composition unavailable to make rather than documented against. Nothing here is new behavior; it is the same three functions in the one order that is correct. It lives in its own namespace because it cannot live in either of the two it spans. `urpx.core` requires only data.json, java.io and string, and `urpx.coerce` requires only malli and `urpx.schema`; neither requires the other. Putting this in core would drag Malli into the parse layer, and putting it in coerce would make a coercion-only consumer pull in the parser.
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, so interior
boundaries fall on clock hours. Intervals are CLIPPED to the requested
window: the first begins at start and the last ends at end, however the
grid falls. 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`, so interior
boundaries fall on clock hours. Intervals are CLIPPED to the requested
window: the first begins at `start` and the last ends at `end`, however the
grid falls. Pass `{:step (Duration/ofMinutes 15)}` etc. for finer granularity
when a rate plan defines sub-hour TOU bracket transitions.Malli schemas for URPX entities, describing the coerced shape.
Tracks the ontology snapshot the fixtures pin via urpx:targetURPXVersion, currently v0.5.1. scripts/shacl-drift.sh reports where these have fallen behind the upstream shapes.
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 entities, describing the coerced shape.
Tracks the ontology snapshot the fixtures pin via urpx:targetURPXVersion,
currently v0.5.1. scripts/shacl-drift.sh reports where these have fallen
behind the upstream shapes.
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).Command-line validator for a single URPX JSON-LD file.
Complements, rather than replaces, the corpus gate that lives in the urpx-rate-plans repo: that one walks a fixed tree, takes no path argument, and is named for a single URPX version. This checks any one file, which is what a per-wave conversion smoke test needs while a corpus migrates utility by utility and carries two vocabularies at once.
clojure -M:validate path/to/filing.jsonld [more.jsonld ...]
Exit codes are tri-state, so a run that checked nothing cannot read as success:
0 every entity in every file coerced and validated
1 at least one entity failed validation
2 refused: a path was unreadable, or a file yielded no typed entity
Wrappers are descended, so a urpx:URPXDocument or urpx:URPXPackage is checked through to the urpx:RatePlan inside it rather than at the wrapper metadata alone.
Command-line validator for a single URPX JSON-LD file.
Complements, rather than replaces, the corpus gate that lives in the
urpx-rate-plans repo: that one walks a fixed tree, takes no path argument,
and is named for a single URPX version. This checks any one file, which is
what a per-wave conversion smoke test needs while a corpus migrates utility
by utility and carries two vocabularies at once.
clojure -M:validate path/to/filing.jsonld [more.jsonld ...]
Exit codes are tri-state, so a run that checked nothing cannot read as
success:
0 every entity in every file coerced and validated
1 at least one entity failed validation
2 refused: a path was unreadable, or a file yielded no typed entity
Wrappers are descended, so a urpx:URPXDocument or urpx:URPXPackage is
checked through to the urpx:RatePlan inside it rather than at the wrapper
metadata alone.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 |