All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project follows Semantic Versioning. While the library is in early development (0.x), breaking changes may appear between minor versions when needed to fix correctness issues or align with the OpenADR 3.1.0 specification.
0.5.0 — 2026-05-28
Event.resolved_intervals()(POA3-9p7). Applies the OpenADR 3.1.0intervalPeriodinheritance rules and returns a new list ofIntervalobjects with the resolved values. Per the spec,event.intervalPeriod"sets default start time and duration of intervals" and a per-intervalintervalPeriod"may set temporal aspects of interval or override event.intervalPeriod" — butEvent.intervals[].interval_periodis exposed as a raw passthrough, so without this helper every downstream consumer had to reimplement the inheritance and risked getting it wrong. The gap was first observed in theopenadr3-ven-hassintegration, where missing per-intervalintervalPeriodwas silently filled with a hardcoded 60-minute default instead of falling back to event-level. Rules: per-intervaldurationfalls back toevent.interval_period.duration; per-intervalstartfalls back toevent.interval_period.startadvanced by the sum of all prior intervals' resolved durations;randomize_startis per-interval only (no event-level fallback). OriginalEvent.intervalsis never mutated.
0.4.0 — 2026-05-25
- Payload type case is now preserved from the wire (POA3-bp6).
coerce_payloadpreviously lowercased everypayload.type(hardcoded"price"/"usage"for PRICE / USAGE, andraw["type"].lower()for all other types), while the descriptor side (EventPayloadDescriptor.payload_type,ReportPayloadDescriptor.payload_type) passed the wire value through unchanged. The result was a within-event mismatch:event.payload_descriptors[0].payload_type == "PRICE"butevent.intervals[0].payloads[0].type == "price". Consumers joining per-interval payload rows against descriptor metadata by string-keying on the type field saw silent dict-lookup failures. The fix preserves the wire case throughout, so descriptor and payload-side type fields now agree. The OpenADR 3.1.0 spec usesUPPER_SNAKE_CASEfor payload types (PRICE,EXPORT_PRICE,GHG,USAGE, etc.), so spec-compliant VTNs will see uppercase strings everywhere. Surfaced by theopenadr3-ven-hass0.4.0 → 0.4.1 incident; that integration's.upper()workaround at the row-construction site becomes a harmless no-op againstopenadr3>=0.4.0.
- Breaking (observable):
payload.typeandattributes[].typestrings now preserve the wire case (typicallyUPPER_SNAKE_CASEper OA3 spec) instead of being lowercased. Any consumer comparing these strings to lowercase literals ("price","usage") must update to the wire case, or normalize at the comparison site. The_rawdict on every coerced entity was already wire-case and is unchanged.
0.3.0 — 2026-05-25
user_agentparameter onOpenADRClientand factory functions for VTN server-side log identification (commit).- Round-trip wire-offset preservation tests covering
Z,+00:00, and non-UTC offsets for datetimes and durations (commit). CONTRIBUTING.mdand README contributing section (commit).
- Time-handling contract pinned via README guarantees:
parse_datetimeandparse_durationpreserve the wire offset string;to_zonedis the only operation that intentionally changes the wire offset.
Initial published versions. Two-layer raw/coerced Pydantic data model, pendulum-based time types, httpx client with bearer auth, payload-type dispatch for PRICE / USAGE → Decimal, OpenAPI spec validation, notification coercion for both spec-compliant camelCase and VTN-RI snake_case shapes.