Skip to content

Add ESPI-native Meter Data and Billing Summary Verifiable Credential schemas#186

Merged
ameetdesh merged 29 commits intomainfrom
issue-185-spec-vc-for-meter-data
Apr 22, 2026
Merged

Add ESPI-native Meter Data and Billing Summary Verifiable Credential schemas#186
ameetdesh merged 29 commits intomainfrom
issue-185-spec-vc-for-meter-data

Conversation

@ameetdesh
Copy link
Copy Markdown
Collaborator

@ameetdesh ameetdesh commented Feb 16, 2026

Summary

Closes #185

Adds two energy Verifiable Credentials — Meter Data and Billing Summary — that use ESPI/Green Button types natively, plus the full ESPI reference schema and CIM meter telemetry extension. All schemas are published-ready for schema.beckn.io.

Credentials

  • MeterDataCredential (meterDataVC/v1.0/) — historical interval meter readings (15-min, hourly) using ESPI IntervalBlock, ReadingType, IntervalReading with native numeric/string enum codes
  • BillingSummaryCredential (billingSummaryVC/v1.0/) — aggregated monthly billing data using ESPI UsageSummary, SummaryMeasurement, LineItem with hundred-thousandths (1e-5) cost encoding

Both import shared identity from EnergyCustomerProfile (aligned with PR #208's CustomerCredential) and ESPI types from espiGreenButton.

ESPI Reference Schemas

  • espiGreenButton/ — full OpenAPI 3.1.1 representation of NAESB ESPI XSD (~30 types), JSON-LD context mapping to http://naesb.org/espi# IRIs with skos:exactMatch links, and comprehensive README with enum lookup tables. Code fields (commodity, flowDirection, uom, accumulationBehaviour, currency, kind, phase, qualityOfReading, itemKind) accept both ESPI integer codes and human-readable camelCase string aliases via oneOf unions; timestamp fields accept both Unix epoch integers and ISO-8601 date-time strings.
  • espiGreenButtonWithCIM/ — optional CIM (IEC 61968/61970) extension: device models, SCADA telemetry, instrument transformers, events (38 classes)

Publishable Schema Packs (beckn/schemas format)

Under specification/schema/:

  • EnergyCustomerProfile/v1.0/customerNumber, meterNumber, meterType (enum: AMR, AMI, Electromechanical, ...), idRef — shared with PR Unify energy VCs into Electricity Credentials #208
  • EnergyMeterDataGB/v1.0/ServiceCategory, timeZone, ReadingType, IntervalBlock, qualityOfReading
  • EnergyBillingSummary/v1.0/ServiceCategory, timeZone, currency, UsageSummary[]

Each pack has: attributes.yaml, context.jsonld, vocab.jsonld, README.md — ready for schema.beckn.io deployment.

Key design decisions

  • OpenAPI 3.1.1 for every attributes.yaml, single source of truth — all schema packs and the ESPI reference schemas are OpenAPI 3.1.1 documents (with jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema) so they render in Swagger UI and serve as the canonical input for schema validation. No parallel standalone schema.json files; cross-refs use relative-file $refs with #/components/schemas/<Name> JSON Pointer fragments so they resolve locally in both Swagger and validators. Canonical identity preserved via $id on each schema object.
  • ESPI-native with human-readable aliases: the oneOf unions live in espiGreenButton/attributes.yaml itself so DEG credentials referencing the base ESPI types inherit the relaxation without needing overlay schemas. Integer codes remain ESPI-faithful; string aliases follow ESPI camelCase naming.
  • Cost encoding: type: number — integer = hundred-thousandths (1e-5) of currency (ESPI native); float (decimal point) = exact currency amount. Convention documented, not schema-enforceable (JSON has one number type)
  • ISO 8601 timestamps with optional timezone offset (+05:30) accepted alongside ESPI Unix epoch integers via oneOf
  • Shared identity imported from EnergyCustomerProfile/context.jsonld using the deg: namespace (https://schema.beckn.io/deg/), aligning with the prefix convention used in schema.beckn.io/P2PTrade/2.0/vocab.jsonld
  • PR Unify energy VCs into Electricity Credentials #208 alignment: customerProfile, customerNumber, meterNumber, meterType, idRef terms resolve to same namespace. maskedIdType/maskedIdNumber removed (replaced by idRef). meterType enum matches PR Unify energy VCs into Electricity Credentials #208
  • Versioned VC schemas: Both meterDataVC/ and billingSummaryVC/ use v1.0/ subdirectories, with all schema URLs updated to include /v1.0/ (e.g., https://schema.beckn.io/EnergyMeterDataGB/v1.0/context.jsonld)

Files added/modified

Directory Files Purpose
meterDataVC/v1.0/ attributes.yaml, context.jsonld, vocab.jsonld, examples/*, ndjson-transport.md, README.md Interval meter data VC
billingSummaryVC/v1.0/ attributes.yaml, context.jsonld, vocab.jsonld, examples/*, README.md Billing summary VC
energy-credentials/ readme.md Credential family overview
external/schema/espiGreenButton/ attributes.yaml, context.jsonld, vocab.jsonld, README.md Full ESPI/NAESB schema with DEG relaxations
external/schema/espiGreenButtonWithCIM/ attributes.yaml, context.jsonld, vocab.jsonld, README.md CIM meter telemetry extension
specification/schema/EnergyCustomerProfile/ v1.0/{attributes.yaml, context.jsonld, vocab.jsonld, README.md} Shared customer identity schema pack
specification/schema/EnergyMeterDataGB/ v1.0/{attributes.yaml, context.jsonld, vocab.jsonld, README.md} Meter data schema pack
specification/schema/EnergyBillingSummary/ v1.0/{attributes.yaml, context.jsonld, vocab.jsonld, README.md} Billing summary schema pack
scripts/validate_vc_examples.py validate_vc_examples.py Bundle-and-validate harness: redocly bundle --dereferenced + jsonschema Draft 2020-12

Validation harness

scripts/validate_vc_examples.py replaces the old standalone schema.json + pytest harness. It bundles any attributes.yaml via @redocly/cli bundle --dereferenced (inlining cross-schema $refs into one self-contained document) and validates each example with jsonschema. Any residual remote $refs Redocly leaves unresolved are fetched lazily via a referencing.Registry retrieve callable.

python3 scripts/validate_vc_examples.py \
  specification/schema/EnergyMeterDataCredential/v1.0/attributes.yaml

Examples auto-discovered from <attributes-dir>/examples/; schema picked as the single entry in components.schemas or the one matching info.title (override with --schema).

Test plan

  • JSON-LD context keyword trace verified — all ESPI terms resolve to espi: IRIs, identity terms to deg: IRIs
  • CIM extension types reviewed against IEC 61968-9 / IEC 61970 class definitions
  • All schema $id and @context URLs use versioned /v1.0/ paths
  • Every attributes.yaml validates as OpenAPI 3.1.1 via @redocly/cli lint (in-PR refs resolve; residual errors in the 2 VC envelopes are transitive through upstream schema.beckn.io/Credential/v2.0, unrelated to this PR)
  • scripts/validate_vc_examples.py wired up end-to-end against both credentials
  • All 6 examples (5 for MeterDataCredential: example.json + example-decimal.json + example.ndjson line 1/2/3; 1 for BillingSummaryCredential) now pass validation after:
    • Loosening espiGreenButton/attributes.yaml code fields to oneOf: [integer, string] unions (commodity, uom, currency, flowDirection, accumulationBehaviour, kind, phase, qualityOfReading, itemKind) and timestamp fields to oneOf: [integer epoch, ISO-8601 string] (DateTimeInterval.start, statusTimeStamp, timeStamp, LineItem.dateTime); loosening IntervalReading.value/cost and SummaryMeasurement.value from integer to number; and array-wrapping IntervalBlock.IntervalReading and UsageSummary.costAdditionalDetailLastPeriod (XSD maxOccurs=unbounded elements the generator had emitted as single $refs).
    • Populating issuer.licenseNumber (required upstream) and changing credentialStatus.type literal from "dedi" to "dediregistry" in all examples; adding statusTimeStamp to each UsageSummary entry in the billing summary example.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

@Anusree-J Anusree-J left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments:

  1. Seems like https://naesb.org/espi/ is not an open URL; schema validation will fail
  2. Terms like serviceKind, intervalBlocks, intervalBlock are mapped to class IRIs (espi:ServiceKind, espi:IntervalBlock) instead of property IRIs. In JSON-LD, context terms must map to properties, not
    classes. Fix mappings to use the correct property IRIs (e.g., meter:serviceKind, meter:intervalBlocks).
  3. Fix coveragePeriod structure mismatch
  • attributes.yaml defines coveragePeriod with start + end
  • vocab.jsonld declares its range as meter:DateTimeInterval
  • But DateTimeInterval (from Green Button) uses start + duration, not start + end
  1. credential subject id is seperate from DID - i think we used customer id in other VCs
  2. intervalBlocks can be moved underreadingType instead of credentialSubject
  3. do we need interval length since we have interval blocks?
  4. Little confused about the reasoning behind keeping value as int and having powerOfTenMultiplier property

@ameetdesh ameetdesh changed the title Add VC schema for timeseries meter data Add Meter Data and Billing Summary Verifiable Credential schemas Mar 21, 2026
@ameetdesh
Copy link
Copy Markdown
Collaborator Author

ameetdesh commented Mar 21, 2026

Some comments:

  1. Seems like https://naesb.org/espi/ is not an open URL; schema validation will fail

replaced this by https://nfh-trust-labs.github.io/vc-schemas/energy-credentials/billing-summary-vc/context.jsonld

  1. Terms like serviceKind, intervalBlocks, intervalBlock are mapped to class IRIs (espi:ServiceKind, espi:IntervalBlock) instead of property IRIs. In JSON-LD, context terms must map to properties, not
    classes. Fix mappings to use the correct property IRIs (e.g., meter:serviceKind, meter:intervalBlocks).

fixed this.

  1. Fix coveragePeriod structure mismatch
  • attributes.yaml defines coveragePeriod with start + end
  • vocab.jsonld declares its range as meter:DateTimeInterval

fixed.

  • But DateTimeInterval (from Green Button) uses start + duration, not start + end

Interval blocks now use start + duration

  1. credential subject id is seperate from DID - i think we used customer id in other VCs

Can you check if this resolves it? I did not understand this fully.

  1. intervalBlocks can be moved underreadingType instead of credentialSubject

Per Green button examples that Vish shared of his actual bills,intervalBlocks and readingType are in parallel, and not nested.

  1. do we need interval length since we have interval blocks?

https://utilityapi.com/docs/greenbutton/xml#ReadingType If intervalLength is not provided in interval blcoks, one from ReadingType seems to be the default one.

  1. Little confused about the reasoning behind keeping value as int and having powerOfTenMultiplier property

Now keeping both float & int values, at slight deviation from GB schema, but making it backward compatible. Thanks for suggesting.

- logicallyDisconnected
- physicallyDisconnected

AmiBillingReadyKind:
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Anusree-J this was the closest to the smart meter type I found.

See AMR vs. AMI difference here.

@ameetdesh ameetdesh marked this pull request as draft March 31, 2026 05:24
@ameetdesh ameetdesh marked this pull request as ready for review April 1, 2026 06:26
@ameetdesh ameetdesh changed the title Add Meter Data and Billing Summary Verifiable Credential schemas Add ESPI-native Meter Data and Billing Summary Verifiable Credential schemas Apr 1, 2026
@ameetdesh ameetdesh self-assigned this Apr 1, 2026
@ameetdesh ameetdesh requested a review from Anusree-J April 2, 2026 03:43
ameetdesh and others added 8 commits April 9, 2026 08:47
…nergy: prefix to deg:

- Wrap the 5 JSON Schema attributes.yaml files (EnergyCustomerProfile,
  EnergyMeterDataGB, EnergyBillingSummary, EnergyMeterDataCredential,
  EnergyBillingSummaryCredential) as OpenAPI 3.1.1 documents with
  components.schemas so they render in Swagger UI and serve as canonical
  schema-validation inputs. Canonical identity preserved via $id on each
  schema object; jsonSchemaDialect pins JSON Schema 2020-12.
- Bump espiGreenButton and espiGreenButtonWithCIM from OpenAPI 3.0.0 to
  3.1.1 and add jsonSchemaDialect.
- Switch in-PR cross-refs from absolute schema.beckn.io URIs to relative
  file refs with #/components/schemas/<Name> JSON Pointers so they
  resolve locally in Swagger and validators.
- Rename JSON-LD prefix energy: to deg: across all vocab.jsonld,
  context.jsonld, x-jsonld annotations and README references. Namespace
  base updated from https://schema.beckn.io/energy/ to
  https://schema.beckn.io/deg/ to align with the deg: convention used in
  schema.beckn.io/P2PTrade/2.0/vocab.jsonld.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
attributes.yaml (OpenAPI 3.1.1 with JSON Schema 2020-12 dialect) is the
canonical schema. The standalone schema.json files duplicated that
content with envelope fields inlined locally, and the pytest harness
existed only to validate examples against schema.json. Remove both, plus
the __pycache__ artifacts, and update the Files section of each
credential README to point at attributes.yaml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ributes.yaml

Bundles attributes.yaml with @redocly/cli (dereferencing cross-schema
$refs into a self-contained document), then validates each example
JSON/NDJSON file against the chosen components.schemas entry with
jsonschema Draft 2020-12. Remote $refs that Redocly does not resolve
(e.g. upstream schema.beckn.io docs) are fetched lazily via a
referencing.Registry retrieve callable.

Usage:
  python3 scripts/validate_vc_examples.py \\
    specification/schema/EnergyMeterDataCredential/v1.0/attributes.yaml

Examples auto-discovered from <attributes-dir>/examples/; schema name
defaults to the single entry in components.schemas or the one matching
info.title. Add a Validation section to each credential README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…le drift

Validation harness surfaced drift in two shapes:

1. espiGreenButton/attributes.yaml, mechanically generated from the
   NAESB ESPI XSD, declares every code field as integer-only and every
   timestamp as integer (Unix epoch). DEG credentials use ESPI-native
   values but also accept the human-readable camelCase aliases
   documented in the PR, and ISO-8601 timestamps. The old standalone
   schema.json inlined loosened `oneOf: [integer, string]` unions for
   those fields; removing schema.json dropped the loosening.

   Loosen in espiGreenButton directly:
     - ReadingType: commodity, flowDirection, uom, accumulationBehaviour,
       currency, kind, phase -> oneOf [integer-enum, string-enum]
     - UsageSummary: commodity, currency, qualityOfReading -> same
     - ReadingQuality.quality, SummaryMeasurement.uom, LineItem.itemKind
       -> same
     - DateTimeInterval.start, UsageSummary.statusTimeStamp,
       SummaryMeasurement.timeStamp, LineItem.dateTime
       -> oneOf [integer epoch, ISO-8601 date-time string]
     - IntervalReading.value/cost, SummaryMeasurement.value
       -> type number (accept both integer and decimal)
     - IntervalBlock.IntervalReading,
       UsageSummary.costAdditionalDetailLastPeriod
       -> array wrappers (fix XSD maxOccurs=unbounded that the generator
          emitted as a single $ref)

   String enums mirror the DEG mapping from the removed schema.json.
   Existing descriptions and x-enum-descriptions preserved.

2. Example envelope drift vs upstream EnergyCredential/v2.0:
     - issuer.licenseNumber is required upstream; populated from the
       existing subjectId regulatory ID value.
     - credentialStatus.type must be the literal "dediregistry" (not
       "dedi") per the upstream const.
     - UsageSummary.statusTimeStamp is required per ESPI; populated per
       entry from billingPeriod.start.

All 6 examples (EnergyMeterDataCredential: example.json,
example-decimal.json, example.ndjson x3; EnergyBillingSummaryCredential:
example.json) now pass validation via
`scripts/validate_vc_examples.py`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ameetdesh ameetdesh merged commit a84ad59 into main Apr 22, 2026
@ameetdesh ameetdesh deleted the issue-185-spec-vc-for-meter-data branch April 22, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Propose VC schema for credentialed customer's meter data

3 participants