openapi: 3.1.1
info:
  title: Demand Flex — Buy Offer Attributes (v2.0)
  version: 2.0.0
  description: >
    Offer attributes for demand-flex contracts. Contains an inputs array
    of DemandFlexRoleInput — one entry per role. At catalog time, entries
    have null participantId. As the contract progresses through init/confirm,
    participantIds and role-specific inputs are filled in.

components:
  schemas:
    DemandFlexBuyOffer:
      type: object
      additionalProperties: true
      required: [inputs]
      x-jsonld:
        "@context": ./context.jsonld
        "@type": DemandFlexBuyOffer
      properties:

        contractAttributes:
          description: >
            Portable DEGContract template. Present only in catalog/discover.
            Promoted to Contract.contractAttributes at select/init.
          type: object
          additionalProperties: true

        inputs:
          type: array
          description: >
            One entry per role. participantId is null until the role is
            bound (e.g. seller is null at catalog, filled at init).
            inputs object is optional when participantId is null,
            required when participantId is set.
          items:
            $ref: "#/components/schemas/DemandFlexRoleInput"

    DemandFlexRoleInput:
      type: object
      required: [role, participantId]
      additionalProperties: true
      properties:
        role:
          type: string
          enum: [buyer, seller]
        participantId:
          type: ["string", "null"]
          description: >
            Participant bound to this role. Null until bound.
        inputs:
          type: object
          additionalProperties: true
          description: >
            Role-specific scalar inputs. Per-slot commercial terms are NOT
            here — they ride the DemandFlexNeed time series (buyer columns
            CAPACITY_REQUESTED / PRICE / SHORTFALL_PENALTY) and the commitment
            series (seller column CAPACITY_OFFERED); these inputs carry only
            what does not vary by interval.
            For buyer: currency and baselineMethodology (a negative PRICE
            column pays for increased consumption, so there is no scalar
            incentive/penalty field).
            For seller: participatingMeters (or participatingMetersRef when the
            cohort is bulk), energyResources (or energyResourcesRef) —
            EnergyResource objects each linked to a
            participatingMeters[*] entry via meterId — and
            reportDescriptors (see below). The seller's per-slot
            CAPACITY_OFFERED is added as a column on
            Commitment.commitmentAttributes at confirm, not here.

            Bulk delivery: the offer is bound at confirm and cannot
            span Beckn messages, so when the seller's cohort would
            stretch a single confirm payload beyond a reasonable wire
            budget (e.g. > 10k meters) the *Ref siblings replace the
            inline arrays. participatingMetersRef / energyResourcesRef
            each carry a BecknResourceRef pointing at a content-
            addressed BPP-hosted bundle. participatingMetersDigest is
            an on-protocol tamper-evidence anchor that survives even
            if the ref URL later goes 404 — Beckn signing of the
            confirm payload commits the contract to that exact cohort.
          properties:
            reportDescriptors:
              description: >
                Seller-side declaration of what telemetry will be
                delivered for this contract. Set to `null` when the
                contract requires no telemetry.

                When non-null, the seller commits to delivering a
                BecknTimeSeries (per device, in performance.meters[].
                telemetry on the on_status reply) whose
                payloadDescriptors cover every entry in this array.

                See BecknReportDescriptors for the canonical payloadType
                / units / cardinality table (USAGE, POWER, SOC_END,
                GPS_LAT, GPS_LON, …).
              oneOf:
                - $ref: "https://schema.nfh.global/BecknReportDescriptors/v1.0/attributes.yaml#/components/schemas/BecknReportDescriptors"
                - type: "null"

            participatingMetersRef:
              description: >
                Optional. Off-protocol delivery of the participatingMeters
                cohort when it's too large to inline. Mutually exclusive
                with `participatingMeters`. Consumers MUST treat the
                fetched body as the authoritative meter list once the
                receiver has verified `contentHash` and `count` against
                the body's canonicalized form.
              $ref: "https://schema.nfh.global/BecknResourceRef/v1.0/attributes.yaml#/components/schemas/BecknResourceRef"

            energyResources:
              type: array
              description: >
                Optional. EnergyResources enrolled in this contract.
                Each entry is an EnergyResource object whose `meterId`
                field references one of the meter URIs in
                `participatingMeters[*]` (many resources MAY share a
                meter). Identity and rated dimensioning live here;
                per-event state lives in BecknTimeSeries telemetry on
                the performance record's meters[]. EnergyResource is
                the canonical, technology-neutral asset class — EV
                chargers, batteries, solar PV, smart HVAC, etc. —
                shared with P2P-trading and any future DEG domain.
              items:
                $ref: "https://schema.nfh.global/EnergyResource/v2.0/attributes.yaml#/components/schemas/EnergyResource"

            energyResourcesRef:
              description: >
                Optional. Off-protocol delivery of the energyResources
                cohort when it's too large to inline. Mutually
                exclusive with `energyResources`. Same verification
                rules as `participatingMetersRef`.
              $ref: "https://schema.nfh.global/BecknResourceRef/v1.0/attributes.yaml#/components/schemas/BecknResourceRef"

            participatingMetersDigest:
              type: object
              additionalProperties: false
              required: [count, sha256OfSortedIds]
              description: >
                Optional on-protocol tamper-evidence anchor for the
                meter cohort, regardless of whether it was delivered
                inline (`participatingMeters`) or by reference
                (`participatingMetersRef`). Computed as SHA-256 of the
                ASCII-sorted, newline-joined meter IDs. Beckn-signing
                of the confirm payload then commits the seller to this
                exact cohort permanently — useful for downstream audit
                long after any off-protocol bundle URL has expired.
              properties:
                count:
                  type: integer
                  minimum: 0
                  description: Total meter count in the cohort.
                sha256OfSortedIds:
                  type: string
                  x-standard: "SHA-256 (FIPS 180-4)"
                  pattern: "^sha256:[0-9a-f]{64}$"
                  description: >
                    `sha256:<64-hex>` of the cohort's meter IDs after
                    they have been ASCII-sorted and joined with a
                    single `\n` separator (no trailing newline).
