diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..2f06638d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,410 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Repository Overview + +This is the **Digital Energy Grid (DEG)** specification repository - a decentralized digital infrastructure for energy transactions built on the Beckn protocol. DEG enables interoperable energy contracts, transactions, and fulfillment across diverse stakeholders (EVs, charging stations, prosumers, utilities, etc.). + +**Current Version:** 0.4.0 + +## Related Repository: protocol-specifications-new + +**IMPORTANT:** This DEG repository is co-developed with the **Beckn Protocol v2.0 specification repository** located at `../protocol-specifications-new/`. The two repositories work together: + +- **protocol-specifications-new** - Contains the core Beckn Protocol v2.0 schemas, JSON-LD contexts, and vocabulary definitions that DEG implementations use +- **DEG (this repo)** - Contains energy-specific implementation guides, examples, and devkits built on top of Beckn Protocol v2.0 + +### Key Protocol Specification Files + +@../protocol-specifications-new/README.md + +**Schema Structure:** +- Core schemas: @../protocol-specifications-new/schema/core/v2/ + - `attributes.yaml` - Core Beckn entities (Order, Offer, Item, Provider, etc.) + - `context.jsonld` - JSON-LD context mapping to schema.org + - `vocab.jsonld` - Beckn vocabulary definitions +- Domain schemas: @../protocol-specifications-new/schema/ + - `EvChargingOffer/v1/` - EV charging attribute schemas + - `EvChargingSession/v1/` - Charging session schemas + - `EnergyTradeOffer/v1/` - P2P energy trading schemas + - `EnergyEnrollment/v1/` - Enrollment/onboarding schemas + +### Schema Development Workflow + +When working across both repositories: + +1. **Schema changes** happen in `protocol-specifications-new` +2. **Example JSONs** in this repo reference those schemas via `@context` URLs +3. **Validation** ensures examples comply with schemas +4. Use `@../protocol-specifications-new/schema/...` to reference schema files when needed + +### Beckn Protocol v2.0 Key Changes + +DEG is built on Beckn Protocol v2.0, which introduced major architectural changes from v1.x: + +1. **JSON-LD + schema.org alignment** - All entities use `@context` and `@type` for global semantic interoperability +2. **Core + Attributes model** - Core schemas (Order, Offer, Item) are extended via composable `*Attributes` fields containing domain-specific schemas +3. **CDS (Catalog Discovery Service)** - Replaces Beckn Gateway; BPPs push catalogs to CDS, BAPs query CDS for discovery +4. **DeDi-compliant Registry** - Network registries now use Decentralized Directory protocol instead of Beckn-specific lookup/subscribe +5. **Modular schema packs** - Domain schemas (EvChargingOffer, EnergyTradeOffer) are independent, versioned bundles + +**Impact on DEG:** +- All JSON examples use JSON-LD format with `@context` and `@type` +- Energy-specific fields live in `*Attributes` objects (e.g., `offerAttributes`, `itemAttributes`) +- Schemas loaded from `@context` URLs determine validation rules + +## Core Architecture + +DEG is built on six fundamental primitives (see `architecture/` directory): + +1. **Energy Resource** - Physical/logical entities (solar panels, EVs, batteries, utilities) +2. **Energy Resource Address (ERA)** - Globally unique identifiers for resources +3. **Energy Credentials** - Digital attestations for trust and verification +4. **Energy Intent** - Consumer demand specifications +5. **Energy Catalogue** - Provider supply listings +6. **Energy Contract** - Formalized agreements when intent matches catalogue + +**Transaction Flow:** Resources → ERAs → Credentials → Intents/Catalogues → Contract → Fulfillment + +## Key Technologies + +- **Language:** Python 3.14+ +- **Protocol:** Beckn Protocol v2.0 (JSON-LD based) +- **Schema Validation:** jsonschema, referencing library, YAML schemas +- **Testing:** Postman collections, Docker-based devkits + +## Repository Structure + +``` +architecture/ - Core primitive documentation +docs/implementation-guides/v2/ - Implementation guides for use cases + ├── EV_Charging_V0.8-draft.md + └── P2P_Trading/ +examples/ - JSON example flows for each use case + ├── ev-charging/v2/ + ├── p2p-trading/v2/ + └── enrollment/v2/ +scripts/ - Validation and generation tools +testnet/ - Devkit configurations and testing + ├── ev-charging-devkit/ + └── p2p-energy-trading-devkit/ +``` + +## Common Commands + +### Schema Validation + +Validate JSON payloads against Beckn protocol schemas: + +```bash +# Validate a single file +python3 scripts/validate_schema.py examples/ev-charging/v2/03_select/time-based-ev-charging-slot-select.json + +# Validate multiple files (glob patterns) +python3 scripts/validate_schema.py examples/ev-charging/v2/**/*.json + +# Validate only core Beckn objects (skip domain-specific attributes) +python3 scripts/validate_schema.py --core-only examples/ev-charging/v2/03_select/time-based-ev-charging-slot-select.json +``` + +**How validation works:** +- Schemas are auto-discovered from `@context` URLs in JSON-LD objects +- Core Beckn objects (beckn:Order, beckn:Offer) use `core/v2/attributes.yaml` +- Domain-specific attributes (ChargingOffer, ChargingSession) use domain schemas (e.g., `EvChargingOffer/v1/attributes.yaml`) +- Schemas are loaded on-demand from GitHub URLs and cached +- Uses `referencing` library for $ref resolution across schema files + +### Generate Postman Collections + +Build Postman collections from example JSON flows for testing: + +```bash +python3 scripts/generate_postman_collection.py \ + --devkit ev-charging \ + --role BAP \ + --output-dir testnet/ev-charging-devkit/postman \ + --examples examples/ev-charging/v2 \ + --name ev-charging:BAP-DEG \ + --description "EV Charging BAP flows" \ + --validate +``` + +**Arguments:** +- `--devkit`: Devkit key (ev-charging, p2p-trading) +- `--role`: BAP (Beckn Application Platform) or BPP (Beckn Provider Platform) +- `--output-dir`: Where to write the collection +- `--validate`: Run schema validation on generated collection + +### Embed Example JSONs in Documentation + +Automatically populate `
` blocks in markdown files with referenced JSON contents: + +```bash +python3 scripts/embed_example_json.py path/to/markdown_file.md +``` + +Markdown should contain: +```markdown +
+ Example + +
+``` + +## JSON-LD Schema Structure + +All DEG payloads are JSON-LD documents with: + +- **@context**: Schema URL (determines which schema to validate against) +- **@type**: Object type (beckn:Order, beckn:Offer, ChargingOffer, etc.) +- **beckn: prefix**: Core Beckn protocol fields +- **schema: prefix**: Schema.org fields +- **Domain-specific objects**: Embedded in `*Attributes` fields + +Example structure (PURE architecture — DEGContract is the direct `@type`): +```json +{ + "context": { "domain": "beckn.one:deg:ev-charging:2.0.0", ... }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/.../core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:buyerAttributes": { "@type": "EnergyBuyer", "vehicle": { ... } }, + "beckn:orderAttributes": { "@type": "DEGContract", "contractState": "ACTIVE", ... }, + "beckn:fulfillment": { + "beckn:deliveryAttributes": { "@type": "EnergyDelivery", "status": "ACTIVE", ... } + } + } + } +} +``` + +## Use Case Implementations + +Three primary use cases are implemented: + +1. **EV Charging** - Charge point discovery, reservation, session management + - Guide: `docs/implementation-guides/v2/EV_Charging/EV_Charging.md` + - Examples: `examples/ev-charging/v2/` + - Devkit: `testnet/ev-charging-devkit/` + +2. **P2P Energy Trading** - Prosumer-to-consumer energy marketplace + - Guide: `docs/implementation-guides/v2/P2P_Trading/P2P_Trading_implementation_guide_draft.md` + - Examples: `examples/p2p-trading/v2/` + - Devkit: `testnet/p2p-energy-trading-devkit/` + +3. **Enrollment/Onboarding** - User and resource registration + - Examples: `examples/enrollment/v2/` + +## Beckn Protocol Actions + +Core protocol flow (applies to all use cases): + +1. **discover** - Search for available resources/services +2. **on_discover** - Return catalogue of offerings +3. **select** - Choose specific offer +4. **on_select** - Confirm selection with updated pricing +5. **init** - Initialize order (billing, customer details) +6. **on_init** - Return order draft with payment terms +7. **confirm** - Confirm the order +8. **on_confirm** - Order confirmation +9. **status** - Query order status +10. **on_status** - Status updates (can be asynchronous) +11. **update** - Modify order (e.g., start/stop charging) +12. **on_update** - Update acknowledgment +13. **track** - Track fulfillment progress +14. **on_track** - Real-time tracking data +15. **cancel** - Cancel order +16. **on_cancel** - Cancellation confirmation +17. **rating** - Submit feedback +18. **on_rating** - Rating acknowledgment +19. **support** - Get support info +20. **on_support** - Support details + +## Git Workflow + +- **Main branch:** `main` +- **Current working branch:** `p2p-trading` (as of conversation start) +- Use conventional commit messages following existing patterns +- Recent commits focus on schema improvements, implementation guides, and Postman collection updates + +## Working with Schemas + +**Schema Location:** Schemas are defined in the sister repository `../protocol-specifications-new/schema/` + +When modifying or adding schemas: + +1. **Schema definitions** are in `../protocol-specifications-new/schema/` + - Core: `../protocol-specifications-new/schema/core/v2/attributes.yaml` + - Domain-specific: `../protocol-specifications-new/schema/{DomainName}/v1/attributes.yaml` +2. **Example JSONs** in this repo (`examples/`) reference schemas via `@context` URLs pointing to GitHub +3. **Validation workflow:** + - Make schema changes in `../protocol-specifications-new/` + - Update/create example JSONs in this repo + - Run `python3 scripts/validate_schema.py` to validate examples against schemas +4. The `fix_schema.py` script can help migrate old schema formats to new ones (fixes `beckn:items` → `beckn:orderItems`, etc.) + +**Schema Development Pattern:** +- Use `@../protocol-specifications-new/schema/core/v2/attributes.yaml` to view/edit core schemas +- Use `@../protocol-specifications-new/schema/EvChargingOffer/v1/` for EV charging schemas +- Use `@../protocol-specifications-new/schema/EnergyTradeOffer/v1/` for P2P trading schemas + +## Important Files + +**In this repository (DEG):** +- `architecture/README.md` - Core primitives overview +- `docs/implementation-guides/v2/EV_Charging/EV_Charging.md` - Comprehensive EV charging guide +- `docs/implementation-guides/v2/P2P_Trading/` - P2P energy trading documentation +- `scripts/validate_schema.py` - Primary validation tool +- `scripts/generate_postman_collection.py` - Collection generator +- `examples/*/v2/` - Reference implementations +- `fix_schema.py` - Schema migration utility + +**In protocol-specifications-new:** +- `../protocol-specifications-new/README.md` - Beckn Protocol v2.0 overview +- `../protocol-specifications-new/schema/README.md` - Schema registry documentation +- `../protocol-specifications-new/schema/core/v2/attributes.yaml` - Core Beckn schemas +- `../protocol-specifications-new/schema/{Domain}/v1/` - Domain-specific attribute schemas + +## DEGContract-as-Code (Generalized Energy Contracts) + +**Status:** Active development (Feb 2026) +**Architecture:** PURE — DEGContract is the top-level `@type` in offerAttributes/orderAttributes + +### Concept + +DEGContract is a **composable, multi-party contract schema** that unifies EV charging, P2P trading, and demand flexibility into a single structure. Instead of separate domain schemas per use case, a DEGContract defines **roles**, **terms**, **energy specs**, and **revenue flows** — allowing composite contracts (e.g., EV charging + V2G demand flexibility in one transaction). + +Key idea: **Contract-as-Code** — contract terms reference executable OPA/Rego policies for complex business logic (pricing, eligibility, penalties), while simple conditions use inline expressions or structured rule trees. + +### Pure Beckn Slot Mapping + +DEGContract is the **primary `@type`** in `offerAttributes` and `orderAttributes` (no wrapper schemas). Domain-specific data lives in dedicated Beckn attribute slots using 4 first-principles schemas. + +| Beckn Slot | `@type` | Purpose | First Appears | +|------------|---------|---------|--------------| +| `itemAttributes` | **EnergyResource** | Physical resource description (charger, solar, battery) | `on_discover` | +| `offerAttributes` | **DEGContract** (TEMPLATE) | Contract template with open role slots | `on_discover` | +| `providerAttributes` | **EnergyProvider** | Provider identity, registration, grid account | `on_discover` | +| `buyerAttributes` | **EnergyBuyer** | Buyer vehicle info or grid account | `select` | +| `orderAttributes` | **DEGContract** (NEGOTIATED→SETTLED) | Contract through full lifecycle | `select` onwards | +| `deliveryAttributes` | **EnergyDelivery** | Runtime telemetry, session state, V2G events | `on_confirm` | +| `paymentAttributes` | **PaymentSettlement** | Settlement accounts & reconciliation | `init` | + +### 4 Domain Schemas (First Principles) + +**EnergyResource** (`itemAttributes`) — One unified schema for ALL energy resources. Uses `resourceType` as discriminator: +- `resourceType`: EV_CHARGER | GENERATION_PLANT | BATTERY_STORAGE | GRID_CONNECTION +- `sourceType`: GRID | SOLAR | WIND | BATTERY | HYBRID +- `deliveryMode`: EV_CHARGING | GRID_INJECTION | V2G | BATTERY_SWAP +- `capacity`, `connector`, `metering`, `capabilities`, `location`, `amenities`, `certification`, `rating` + +**EnergyDelivery** (`deliveryAttributes`) — One unified schema for ALL runtime fulfillment. Uses sparse sub-objects: +- `status`: PENDING | ACTIVE | PAUSED | COMPLETED | FAILED +- `deliveryMode`: EV_CHARGING | GRID_INJECTION | V2G +- `energyFlow` (direction, delivered, discharged, net, curtailed) +- `telemetry[]`, `meterReadings[]` (P2P), `v2g` (V2G events), `vehicle`, `grid`, `summary` + +**EnergyProvider** (`providerAttributes`) — Unified provider identity (CPO, prosumer, utility): +- `operatorName`, `operatorCode`, `identifier`, `contact`, `registration` +- `gridAccount` (for prosumers: meterId, utilityId, sanctionedLoad, connectionType) + +**EnergyBuyer** (`buyerAttributes`) — Sparse, domain-specific buyer info (core identity stays in `beckn:Buyer`): +- `vehicle` (registration, makeModel, batteryKwh, connectorType) — for EV use cases +- `gridAccount` (meterId, utilityId, sanctionedLoad) — for P2P use cases + +### Core Design Patterns + +1. **Progressive Role Filling** — In the catalog (TEMPLATE), provider roles are pre-filled; consumer roles have `party: null` and unfilled inputs. As the Beckn transaction progresses (select → init → confirm), buyer fills negotiation-phase inputs, then commitment-phase inputs, until all roles are complete. + +2. **3-Tier Policy Expressions** — Contract terms use `condition` and `obligation` fields with three escalation levels: + - `expr`: Simple string expression (`"deliveredKwh > 0"`) + - `rule`: Structured operator tree (`{ operator: "AND", operands: [...] }`) + - `policyRef`: External OPA/Rego policy (`{ type: "REGO", package: "deg.contracts.ev_charging", entrypoint: "total_charge" }`) + +3. **Multi-Party Revenue Model with Net-Zero Invariant** — Every contract has a `revenueModel` with directed `flows[]` (from → to). At discovery, flows show formulas; at settlement, flows show `computedAmount`. For every party, `sum(inflows) - sum(outflows)` across ALL parties = 0. + +4. **Composite Contracts** — `contractType: "COMPOSITE"` with `composedOf: ["EV_CHARGING", "DEMAND_FLEXIBILITY"]` allows combining multiple use case logics in one contract. + +### DEGContract Schema Components + +- **DEGContract** — Top-level: contractType, contractState, roles[], terms[], energySpec, fulfillmentSpec, settlementSpec, revenueModel +- **DEGRole** — roleId, roleType, party (simplified: partyId, partyType, platformId), inputs[], obligations[] +- **RoleInput** — inputId, inputType, providedAt (TEMPLATE|NEGOTIATION|COMMITMENT|FULFILLMENT), value +- **DEGTerm** — termId, termType, condition (PolicyExpression), obligation, appliesTo[] +- **RevenueModel** — flows[] (RevenueFlow), netZeroCheck +- **RevenueFlow** — flowId, from, to, flowType, formula, computedAmount, currency + +### File Layout + +``` +specification/ +├── schema/ +│ └── deg_contract_schema.yaml # OpenAPI 3.1.1 formal schema +├── policies/ # OPA/Rego policy packages +│ ├── ev_charging.rego # Connector compat, pricing, cancellation +│ ├── p2p_trade.rego # Delivery compliance, deviation penalty +│ ├── demand_flex.rego # Trigger conditions, curtailment, incentives +│ ├── v2g.rego # V2G eligibility, discharge decisions +│ └── revenue.rego # Generic net-zero revenue computation +└── deg_contract_beckn_flow.arazzo.yaml # Arazzo spec: 3 workflows + +examples/deg_contract/ +├── README.md # Overview + Pure Beckn slot mapping +├── ev_charging/ # 9 files: discover → settlement +│ ├── on_discover.json # EnergyResource + EnergyProvider + DEGContract +│ └── on_update_complete.json # EnergyDelivery (COMPLETED) + DEGContract (SETTLED) +├── p2p-trading-interdiscom/ # 11 files: discover → settlement (incl. cascaded) +│ ├── on_discover.json # GENERATION_PLANT resource, 4 roles + 6 revenue flows +│ ├── cascaded_init.json # P2P BPP acts as BAP to utility BPP +│ └── on_status_completed.json # Full settlement with net-zero verified +└── smart_ev_charging/ # 10 files: discover → V2G event → settlement + ├── on_discover.json # V2G-capable EV_CHARGER, COMPOSITE contract, 3 roles + ├── on_update_v2g_event.json # Grid stress V2G activation with Rego eval + └── on_status_complete.json # 4-party revenue: ev_owner, CPO, grid_operator, platform +``` + +### Three Implemented Use Cases + +**1. EV Charging** (`ev_charging/`) +- 2 roles: `cpo` (CPO) + `ev_driver` (CONSUMER) +- Revenue: charging_payment + platform_fee + GST + idle_fee - refund_overcharge +- Rego: `deg.contracts.ev_charging` (connector compat, time-of-day pricing, cancellation) + +**2. P2P Inter-Discom Trading** (`p2p-trading-interdiscom/`) +- 4 roles: `seller` (PRODUCER) + `buyer` (CONSUMER) + `source_utility` (DISCOM) + `destination_utility` (DISCOM) +- Revenue: energy_payment + wheeling_charge(×2) + platform_fee(×2) + deviation_penalty +- Cascaded init: P2P BPP acts as BAP to utility BPP for wheeling/open-access approval +- Rego: `deg.contracts.p2p_trade` (delivery compliance, deviation penalty, sanctioned load) + +**3. Smart EV Charging + V2G** (`smart_ev_charging/`) +- 3 roles: `ev_owner` (PROSUMER) + `cpo_aggregator` (AGGREGATOR) + `grid_operator` (GRID_OPERATOR) +- Revenue: charging_payment - v2g_earnings + grid_v2g_payment + platform_fee +- V2G triggers on grid frequency < 49.5 Hz, evaluates `deg.contracts.v2g` Rego policy +- Composite: `composedOf: ["EV_CHARGING", "DEMAND_FLEXIBILITY"]` + +### Working with DEGContract Examples + +- All 30 JSON files validated with `python3 -c "import json; json.load(open(f))"` +- **PURE architecture**: DEGContract is the direct `@type` in offerAttributes/orderAttributes — no wrapper schemas (ChargingOffer, EnergyTradeOffer etc. are NOT used) +- Domain data in dedicated Beckn slots: EnergyResource in itemAttributes, EnergyDelivery in deliveryAttributes, EnergyProvider in providerAttributes, EnergyBuyer in buyerAttributes +- Revenue model `computedAmount` values only appear in SETTLED state; TEMPLATE state has `computedAmount: null` +- Net-zero verified: `sum(all per-party net positions) == 0` in all 3 settled examples + +### Reference Files (Original Working Docs) + +The original generalization work lives in `ref_docs/generalize_deg_shcemas/`: +- `prompt_generalize_deg_schemas.md` — Original prompt/requirements +- `deg_contract_schema.yaml` — Original schema (copied to specification/schema/) +- `DEG_Contract_Specification.md` — Original spec (copied to docs/) +- `Mapping_Existing_Schemas.md` — Original mapping (copied to docs/) +- `example_rego_policies.rego` — Original combined Rego (split into specification/policies/) + +## Development Notes + +- All Python scripts use Python 3.14+ (system has 3.14.0b3) +- Required Python packages: json, yaml, jsonschema, referencing, requests +- JSON files follow strict JSON-LD format with @context and @type fields +- Schema validation is critical - always validate before committing examples +- Postman collections use environment variables: `{{bap_id}}`, `{{bap_uri}}`, `{{bpp_id}}`, `{{bpp_uri}}` diff --git a/docs/implementation-guides/deg_contract/DEG_Contract_Specification.md b/docs/implementation-guides/deg_contract/DEG_Contract_Specification.md new file mode 100644 index 00000000..247a4557 --- /dev/null +++ b/docs/implementation-guides/deg_contract/DEG_Contract_Specification.md @@ -0,0 +1,760 @@ +# DEG Contract-as-Code Specification + +**Version:** 0.1.0-draft +**Status:** Proposal +**Scope:** Generalized contract model for Digital Energy Grid (DEG) use cases + +--- + +## 1. Problem Statement + +Today, EV Charging, P2P Energy Trading, and Demand Flexibility each have separate schemas with duplicated concepts: + +| Concept | EV Charging | P2P Trading | Demand Flexibility | +|---------|------------|-------------|-------------------| +| **What** is traded | kWh of charge | kWh of energy | Load curtailment/shift | +| **Who** participates | EV driver + CPO | Prosumer + Consumer + Utility | Aggregator + Consumer + Grid Operator | +| **How** fulfilled | Physical charging session | Grid injection + meter reading | Signal-driven load adjustment | +| **Terms** | Price/kWh, connector type | Price/kWh, delivery window, deviation penalty | Baseline, trigger signal, incentive | + +Yet the **transactional structure** is identical: parties discover each other, negotiate terms, commit to obligations, fulfill them, and settle. A generalized "contract" model captures this. + +--- + +## 2. Core Idea: Energy Contract as a Partially-Filled Template + +An **Energy Contract** is a structured agreement between N roles, published as a partially-filled template on a Beckn catalog. Each role has: + +- **Inputs** it must provide (parameters, credentials, signals) +- **Obligations** it must fulfill (deliver energy, curtail load, pay) +- **Conditions** under which obligations activate or change (time windows, grid signals, thresholds) + +The contract progresses through Beckn's transaction lifecycle: + +``` +Template (Catalog/Offer) → Negotiated (Select) → Committed (Confirm) → Active (Fulfillment) → Settled (Post) + ↑ ↑ ↑ ↑ + Provider fills Consumer fills Both sign Signals/meters + their role inputs their role inputs off drive execution +``` + +--- + +## 3. Contract Structure + +### 3.1 DEGContract Schema (Minimal) + +```yaml +DEGContract: + type: object + required: [contractType, version, roles, terms] + properties: + + contractType: + type: string + enum: [EV_CHARGING, P2P_ENERGY_TRADE, DEMAND_FLEXIBILITY, COMPOSITE] + description: Primary contract classification. COMPOSITE for multi-use-case contracts. + + version: + type: string + pattern: '^\d+\.\d+\.\d+$' + description: Semantic version of this contract template. + + roles: + type: array + minItems: 2 + items: + $ref: '#/DEGRole' + description: Participating roles with their required inputs and obligations. + + terms: + type: array + items: + $ref: '#/DEGTerm' + description: Contract terms — each is a named condition-obligation pair. + + energySpec: + $ref: '#/EnergySpecification' + description: What energy product is being contracted. + + fulfillmentSpec: + $ref: '#/FulfillmentSpecification' + description: How the contract is physically fulfilled. + + settlementSpec: + $ref: '#/SettlementSpecification' + description: How the contract is financially settled. +``` + +### 3.2 DEGRole + +```yaml +DEGRole: + type: object + required: [roleId, roleType, inputs] + properties: + + roleId: + type: string + description: Unique role identifier within this contract. + example: "seller" + + roleType: + type: string + enum: [PRODUCER, CONSUMER, AGGREGATOR, GRID_OPERATOR, CPO, UTILITY, PROSUMER] + description: Functional role classification. + + party: + type: object + description: > + Filled when a party accepts this role. Maps to Beckn Provider or Buyer. + Empty in template = open for acceptance. + properties: + partyId: { type: string } + partyType: { type: string, enum: [PROVIDER, BUYER] } + platformId: { type: string, description: "BAP or BPP subscriber ID" } + attributes: + $ref: 'core/v2/attributes.yaml#/components/schemas/Attributes' + description: "Role-specific attributes (EnergyCustomer, etc.)" + + inputs: + type: array + items: + $ref: '#/RoleInput' + description: > + Parameters this role must supply. Some filled by template author, + others filled by accepting party. Unfilled inputs = open slots. + + obligations: + type: array + items: + type: string + description: References to DEGTerm.termId where this role has an obligation. +``` + +### 3.3 RoleInput + +```yaml +RoleInput: + type: object + required: [inputId, inputType, required] + properties: + + inputId: + type: string + description: Unique input identifier. + example: "source_meter_id" + + inputType: + type: string + enum: [PARAMETER, ITEM_REF, CREDENTIAL, SIGNAL, METER_ID, LOCATION] + description: Classification of this input. + + required: + type: boolean + default: true + + description: + type: string + + schema: + type: object + description: JSON Schema for validating the input value. + + value: + description: > + The actual value. null/absent = unfilled slot (to be provided by accepting party). + # any type + + filledBy: + type: string + description: roleId of the party that fills this input. If absent, filled by the role owner. + + providedAt: + type: string + enum: [TEMPLATE, NEGOTIATION, COMMITMENT, FULFILLMENT] + description: When in the lifecycle this input must be provided. +``` + +### 3.4 DEGTerm (Condition-Obligation Pairs) + +This is where Rego/OPA-style policy logic enters. Each term defines: +- **When** it activates (condition) +- **What** must happen (obligation) +- **What if** it's violated (consequence) + +```yaml +DEGTerm: + type: object + required: [termId, termType, condition, obligation] + properties: + + termId: + type: string + example: "delivery_obligation" + + termType: + type: string + enum: [DELIVERY, PAYMENT, SIGNAL_RESPONSE, QUALITY, DEVIATION_PENALTY, CANCELLATION] + + condition: + $ref: '#/PolicyExpression' + description: > + When this term activates. Expressed as a declarative rule. + Evaluated against contract state + external signals. + + obligation: + $ref: '#/PolicyExpression' + description: What must be true for this term to be satisfied. + + consequence: + $ref: '#/PolicyExpression' + description: What happens if the obligation is violated (penalty, cancellation, etc.) + + appliesTo: + type: array + items: { type: string } + description: roleIds this term applies to. +``` + +### 3.5 PolicyExpression (Rego-Inspired) + +Rather than embedding a full Rego runtime, we define a declarative expression format inspired by OPA's data model. The key insight from OPA: + +- **Input**: the transaction/event being evaluated (a Beckn message, meter reading, grid signal) +- **Data**: reference data (contract terms, thresholds, rate cards) +- **Output**: a decision (compliant/violated, calculated price, triggered action) + +```yaml +PolicyExpression: + type: object + description: > + Declarative condition/rule. Can be simple (field comparison) or + reference an external policy (Rego file, Arazzo workflow step). + properties: + + # Simple inline expression + expr: + type: string + description: > + JSONPath-like expression over contract state. + Examples: + "deliveredQuantity >= terms.minQuantity" + "currentTime within deliveryWindow" + "gridFrequency < 49.5" + example: "deliveredQuantity >= 10.0 AND deliveredQuantity <= 50.0" + + # Structured rule (for more complex conditions) + rule: + type: object + properties: + operator: + type: string + enum: [AND, OR, NOT, GTE, LTE, EQ, NEQ, WITHIN, BETWEEN, IF_THEN] + operands: + type: array + items: + oneOf: + - $ref: '#/PolicyExpression' + - type: object + properties: + field: { type: string } + value: {} + + # External policy reference (for complex logic) + policyRef: + type: object + properties: + type: + type: string + enum: [REGO, ARAZZO, WEBHOOK] + uri: + type: string + format: uri + description: URI to the policy file or endpoint. + package: + type: string + description: For Rego — the package path (e.g., "deg.contracts.ev_charging") + entrypoint: + type: string + description: For Rego — the rule to evaluate (e.g., "delivery_compliant") +``` + +### 3.6 EnergySpecification + +```yaml +EnergySpecification: + type: object + description: What energy product is being contracted. + properties: + + energyType: + type: string + enum: [ELECTRICAL, THERMAL, GAS] + + sourceType: + type: string + enum: [SOLAR, WIND, HYDRO, GRID, BATTERY, ANY] + + quantity: + $ref: 'core/v2/attributes.yaml#/components/schemas/Quantity' + + qualityConstraints: + type: object + properties: + greenCertRequired: { type: boolean } + maxCarbonIntensity: { type: number, description: "gCO2/kWh" } + powerFactor: { type: number, minimum: 0, maximum: 1 } +``` + +### 3.7 FulfillmentSpecification + +```yaml +FulfillmentSpecification: + type: object + properties: + + deliveryMode: + type: string + enum: [EV_CHARGING, BATTERY_SWAP, GRID_INJECTION, V2G, LOAD_CURTAILMENT, LOAD_SHIFT, VIRTUAL] + + deliveryWindow: + $ref: 'core/v2/attributes.yaml#/components/schemas/TimePeriod' + + location: + $ref: 'core/v2/attributes.yaml#/components/schemas/Location' + + meterIds: + type: array + items: { type: string } + description: Meter identifiers for measuring fulfillment. + + signals: + type: array + items: + type: object + properties: + signalType: { type: string, enum: [GRID_FREQUENCY, PRICE_SIGNAL, CURTAILMENT_COMMAND, START_STOP] } + source: { type: string, description: "URI of signal source" } + protocol: { type: string, enum: [MQTT, HTTP_WEBHOOK, OCPP, OPENADR] } + description: Real-time signals used during fulfillment. +``` + +### 3.8 RevenueModel (Multi-Party Net-Zero Flows) + +Every energy contract involves money moving between roles. The **RevenueModel** makes this explicit with two invariants: + +1. **Net-zero**: The sum of all flows across all roles = 0 (money is conserved) +2. **Per-role legibility**: Any agent acting on behalf of a role can compute its net position by filtering flows where it is `from` or `to` + +```yaml +RevenueModel: + type: object + required: [flows] + description: > + Declares all monetary flows between roles. The sum of all flow amounts + MUST equal zero at settlement (net-zero invariant). Each flow has a + formula that computes its value from contract state. + properties: + + flows: + type: array + minItems: 1 + items: + $ref: '#/RevenueFlow' + description: > + Directed monetary flows. Positive amount = transfer from → to. + The sum of all flow amounts must be zero. + + netZeroCheck: + $ref: '#/PolicyExpression' + description: > + Optional Rego/expr that validates the net-zero invariant. + Default: "sum(flows[].computedAmount) == 0" + +RevenueFlow: + type: object + required: [flowId, from, to, flowType, formula] + properties: + + flowId: + type: string + description: Unique flow identifier. + example: "energy_payment" + + from: + type: string + description: roleId of the paying party. + + to: + type: string + description: roleId of the receiving party. + + flowType: + type: string + enum: [ENERGY_PAYMENT, WHEELING_CHARGE, PLATFORM_FEE, INCENTIVE, + DEVIATION_PENALTY, CANCELLATION_FEE, TAX, CREDIT, ESCROW_RELEASE] + description: Classification of the monetary flow. + + formula: + $ref: '#/PolicyExpression' + description: > + Expression that computes the flow amount from contract state. + Examples: + "deliveredKwh * pricePerKwh" + "totalPayment * 0.02" (2% platform fee) + "shortfall * deviationPenaltyPerKwh" + + currency: + type: string + default: "INR" + + computedAmount: + type: number + description: > + Filled at settlement time. The evaluated result of the formula. + Positive = transfer in the from→to direction. + + triggeredBy: + type: string + description: > + termId that triggers this flow. Links revenue to contract terms. +``` + +#### How an Agent Reads Its Revenue Position + +Any agent acting for roleId `R` computes: + +``` +netPosition(R) = sum(flow.computedAmount for flow where flow.to == R) + - sum(flow.computedAmount for flow where flow.from == R) +``` + +- **Positive** net = role R **receives** money (revenue) +- **Negative** net = role R **pays** money (cost) +- **Sum of all netPosition across all roles = 0** (the invariant) + +#### Example: P2P Trade Revenue Model + +```yaml +revenueModel: + flows: + - flowId: energy_payment + from: buyer + to: seller + flowType: ENERGY_PAYMENT + formula: + expr: "deliveredKwh * pricePerKwh" + triggeredBy: pricing + + - flowId: wheeling_charge + from: buyer + to: utility + flowType: WHEELING_CHARGE + formula: + expr: "deliveredKwh * wheelingRatePerKwh" + triggeredBy: utility_registration + + - flowId: platform_fee_buyer + from: buyer + to: platform + flowType: PLATFORM_FEE + formula: + expr: "energyPayment * 0.01" # 1% from buyer side + + - flowId: platform_fee_seller + from: seller + to: platform + flowType: PLATFORM_FEE + formula: + expr: "energyPayment * 0.01" # 1% from seller side + + - flowId: deviation_penalty + from: seller + to: buyer + flowType: DEVIATION_PENALTY + formula: + policyRef: + type: REGO + package: "deg.contracts.p2p_trade" + entrypoint: "deviation_penalty" + triggeredBy: delivery_obligation +``` + +**Per-role view (at settlement with 10 kWh @ INR 4.50/kWh, 0.50 wheeling, no deviation):** + +| Role | Inflows | Outflows | Net Position | +|------|---------|----------|-------------| +| **buyer** | - | 45.00 (energy) + 5.00 (wheeling) + 0.45 (fee) = **50.45** | **-50.45** (pays) | +| **seller** | 45.00 (energy) | 0.45 (fee) = **0.45** | **+44.55** (earns) | +| **utility** | 5.00 (wheeling) | - | **+5.00** (earns) | +| **platform** | 0.45 + 0.45 = 0.90 | - | **+0.90** (earns) | +| **Total** | | | **0.00** | + +#### Example: Demand Flexibility Revenue Model + +```yaml +revenueModel: + flows: + - flowId: grid_savings + from: grid_operator + to: aggregator + flowType: ENERGY_PAYMENT + formula: + expr: "totalCurtailmentKwh * gridSavingsRatePerKwh" + + - flowId: participant_incentive + from: aggregator + to: participant + flowType: INCENTIVE + formula: + expr: "participantCurtailmentKwh * incentiveRate * eventDurationHours" + triggeredBy: incentive_payment + + - flowId: aggregator_margin + from: aggregator + to: aggregator + flowType: PLATFORM_FEE + formula: + expr: "gridSavings - participantIncentive" # implicit: aggregator keeps the spread + # Note: this is a self-loop that represents retained margin + # An alternative is to simply not model it, and let + # netPosition(aggregator) = gridSavings - incentives naturally. +``` + +**Per-role view (100 kWh curtailed, grid saves INR 8/kWh, incentive INR 5/kWh, 2hr event):** + +| Role | Inflows | Outflows | Net Position | +|------|---------|----------|-------------| +| **grid_operator** | - | 800 (savings payout) | **-800** (pays, but saves more in avoided procurement) | +| **aggregator** | 800 (from grid) | 500 (incentive to participant) | **+300** (margin) | +| **participant** | 500 (incentive) | - | **+500** (earns) | +| **Total** | | | **0.00** | + +#### Example: EV Charging Revenue Model + +```yaml +revenueModel: + flows: + - flowId: charging_payment + from: ev_driver + to: cpo + flowType: ENERGY_PAYMENT + formula: + policyRef: + type: REGO + package: "deg.contracts.ev_charging" + entrypoint: "total_charge" + + - flowId: tax + from: ev_driver + to: tax_authority + flowType: TAX + formula: + expr: "chargingPayment * gstRate" + + - flowId: cancellation_refund + from: cpo + to: ev_driver + flowType: CANCELLATION_FEE + formula: + expr: "-(paidAmount - cancellationFee)" # Negative = refund net of fee + triggeredBy: cancellation +``` + +#### Example: V2G Composite Revenue Model + +```yaml +revenueModel: + flows: + # Charging direction: EV owner pays + - flowId: charging_payment + from: ev_owner + to: cpo_aggregator + flowType: ENERGY_PAYMENT + formula: + expr: "chargedKwh * chargingRate" + + # V2G direction: EV owner receives + - flowId: v2g_payment + from: cpo_aggregator + to: ev_owner + flowType: INCENTIVE + formula: + expr: "dischargedKwh * v2gRate" + + # Grid pays for V2G services + - flowId: grid_v2g_payment + from: grid_operator + to: cpo_aggregator + flowType: ENERGY_PAYMENT + formula: + expr: "dischargedKwh * gridV2gRate" +``` + +**Net payment for EV owner:** +``` +netPosition(ev_owner) = v2gPayment - chargingPayment +``` +If `dischargedKwh * v2gRate > chargedKwh * chargingRate`, the EV owner **earns money** from the transaction. + +--- + +## 4. Mapping to Beckn Slots + +The DEGContract maps to Beckn v2 core entities through attribute packs: + +| DEGContract Part | Beckn Slot | When Used | +|-----------------|------------|-----------| +| `contractType` + `energySpec` | `Item.itemAttributes` | Catalog (on_search) | +| `roles[seller]` + partial `terms` | `Offer.offerAttributes` | Catalog (on_search) | +| `revenueModel.flows` (estimated) | `Offer.price` + `PriceSpecification.components` | Catalog (on_search) | +| `roles[buyer].inputs` filled | `Order.buyer.buyerAttributes` | Init, Confirm | +| Full `terms` + both roles filled | `Order.orderAttributes` | Confirm, Status | +| `revenueModel` (committed) | `Order.orderAttributes.revenueModel` | Confirm | +| `fulfillmentSpec` + delivery state | `OrderItem.orderItemAttributes.fulfillmentAttributes` | Status, Update | +| `revenueModel.flows[].computedAmount` | `Invoice` line items / `Payment` breakdown | Post-fulfillment | +| `settlementSpec` | `Payment` + `Invoice` | Post-fulfillment | + +### 4.1 Lifecycle Mapping + +``` +Beckn Action │ Contract State │ What Happens +────────────────┼─────────────────────────┼────────────────────────────── +search │ - │ BAP queries by contractType, energySpec +on_search │ TEMPLATE │ BPP returns catalog with partially-filled contracts +select │ TEMPLATE → NEGOTIATED │ BAP selects contract, fills buyer role inputs +on_select │ NEGOTIATED │ BPP returns quote with evaluated terms +init │ NEGOTIATED → COMMITTED │ BAP provides credentials, meter IDs +on_init │ COMMITTED │ BPP verifies, returns final terms + payment link +confirm │ COMMITTED → ACTIVE │ Both parties sign off +on_confirm │ ACTIVE │ Contract is live; fulfillment begins +status │ ACTIVE │ Check fulfillment progress, term compliance +on_status │ ACTIVE │ Return meter readings, signal state, compliance +update │ ACTIVE │ Modify parameters (e.g., increase quantity) +on_update │ ACTIVE │ Confirm modification +on_status(final)│ ACTIVE → SETTLED │ All terms satisfied, settlement complete +cancel │ * → CANCELLED │ Cancellation terms apply +``` + +--- + +## 5. Inspirations and Parallels + +### 5.1 From Arazzo (OpenAPI Workflows) + +Arazzo defines multi-step API workflows with: +- **Workflows** with named steps, each referencing an API operation +- **Inputs/Outputs** declared per workflow and per step +- **Dependencies** between steps via `dependsOn` +- **Success/Failure criteria** and actions + +**Parallel to DEG Contract:** +- Each Beckn action pair (search/on_search, select/on_select, etc.) is a "workflow step" +- Contract `terms` are like Arazzo's `successCriteria` — conditions that must be met for the step to succeed +- `RoleInput.providedAt` maps to Arazzo's concept of step-level inputs that flow from previous steps +- An Arazzo-style workflow description could formally specify the full DEG transaction flow + +### 5.2 From OPA/Rego (Policy as Code) + +OPA's key architectural insight: **decouple policy from the system being governed.** + +``` + ┌──────────┐ +Input → │ OPA │ → Decision + │ (Rego │ +Data → │ Rules) │ + └──────────┘ +``` + +**Parallel to DEG Contract terms:** + +```rego +# Example: Rego policy for a P2P energy delivery term +package deg.contracts.p2p_trade + +import rego.v1 + +# The delivery obligation is satisfied when: +delivery_compliant if { + input.deliveredQuantity >= data.contract.terms.minQuantity + input.deliveredQuantity <= data.contract.terms.maxQuantity + time.parse_rfc3339_ns(input.deliveryTime) >= time.parse_rfc3339_ns(data.contract.deliveryWindow.start) + time.parse_rfc3339_ns(input.deliveryTime) <= time.parse_rfc3339_ns(data.contract.deliveryWindow.end) +} + +# Deviation penalty calculation +deviation_penalty_inr := penalty if { + not delivery_compliant + shortfall := data.contract.terms.minQuantity - input.deliveredQuantity + shortfall > 0 + penalty := shortfall * data.contract.terms.deviationPenaltyPerKwh +} + +# Demand flexibility: trigger condition +demand_response_triggered if { + input.gridFrequency < 49.5 +} +demand_response_triggered if { + input.priceSignal > data.contract.terms.priceThreshold +} +``` + +**In the DEGContract schema, this translates to:** + +```yaml +terms: + - termId: delivery_obligation + termType: DELIVERY + condition: + expr: "currentTime WITHIN deliveryWindow" + obligation: + rule: + operator: AND + operands: + - { field: "deliveredQuantity", operator: GTE, value: 10.0 } + - { field: "deliveredQuantity", operator: LTE, value: 50.0 } + consequence: + expr: "penalty = shortfall * deviationPenaltyPerKwh" + appliesTo: ["seller"] +``` + +For complex scenarios, the `policyRef` field allows pointing to actual Rego files: + +```yaml +terms: + - termId: demand_response_trigger + termType: SIGNAL_RESPONSE + condition: + policyRef: + type: REGO + uri: "https://policies.deg-network.io/demand_flexibility/v1.rego" + package: "deg.contracts.demand_flex" + entrypoint: "demand_response_triggered" + obligation: + expr: "curtailLoad BY curtailmentTarget WITHIN responseWindow" + appliesTo: ["consumer"] +``` + +### 5.3 From Ricardian Contracts + +A Ricardian contract is both human-readable AND machine-parseable. The DEGContract achieves this: +- `terms[].description` (not shown above, but easy to add) provides human-readable text +- `terms[].condition/obligation` provides machine-parseable logic +- The `@context` JSON-LD mechanism from Beckn v2 provides semantic grounding + +--- + +## 6. Design Principles + +1. **Partially-filled templates**: The same contract structure works as a template (Offer) and as a committed agreement (Order). Empty `party` fields and null `value` fields in RoleInputs mark open slots. + +2. **Progressive disclosure**: Simple contracts (fixed-price EV charging) need only `expr` strings. Complex contracts (multi-party demand flexibility with grid signals) can reference external Rego policies. + +3. **Composability**: A COMPOSITE contract can combine terms from EV_CHARGING and DEMAND_FLEXIBILITY — e.g., "charge my EV but only during off-peak hours, and participate in V2G during grid stress." + +4. **Evolvability**: New `contractType`, `roleType`, `termType`, `signalType` enums can be added without changing the schema structure. New policy expression types can be added to `PolicyExpression`. + +5. **Beckn-native**: Every part maps to an existing Beckn v2 slot. No new API actions needed — only new attribute schemas. + +6. **Net-zero revenue flows**: Every contract declares explicit monetary flows between roles. The invariant `sum(all flows) == 0` is enforced. Any agent can compute its role's net position in O(flows) time — no need to understand the whole contract, just filter by your roleId. diff --git a/docs/implementation-guides/deg_contract/Mapping_Existing_Schemas.md b/docs/implementation-guides/deg_contract/Mapping_Existing_Schemas.md new file mode 100644 index 00000000..8bce0805 --- /dev/null +++ b/docs/implementation-guides/deg_contract/Mapping_Existing_Schemas.md @@ -0,0 +1,695 @@ +# Mapping DEGContract to Existing Schemas + +This document shows how the generalized DEGContract model maps to the existing `EvChargingService`, `EnergyTrade`, and `EnergyEnrollment` schemas from `protocol-specifications-v2/schema/`. + +--- + +## 1. EV Charging as a DEGContract + +### Current Schema Structure (EvChargingService v1) + +``` +Catalog + └─ providers[] + ├─ descriptor (name, images) + ├─ locations[] (GPS coordinates) + ├─ categories[] (green-tariff) + ├─ fulfillments[] (type: CHARGING, stops, tags with connector specs) + └─ items[] + ├─ descriptor (code: "energy") + ├─ price (value, currency: "INR/kWH") + ├─ quantity.available (kWh) + └─ fulfillment_ids, category_ids, location_ids +``` + +### As a DEGContract + +```yaml +contractType: EV_CHARGING +version: "1.0.0" + +roles: + - roleId: cpo # Charge Point Operator + roleType: CPO + party: # Filled in catalog (they publish the template) + partyId: "chargezone.in" + partyType: PROVIDER + platformId: "chargezone-energy-bpp.com" + attributes: + "@type": "ChargingService" # from EvChargingService/v1 + connectorType: "CCS2" + maxPowerKW: 60 + chargingStation: + id: "IN-ECO-MDY-STATION-01" + serviceLocation: { geo: { type: Point, coordinates: [77.6104, 12.9153] } } + inputs: + - inputId: charger_type + inputType: PARAMETER + value: "AC" # Pre-filled by CPO + providedAt: TEMPLATE + - inputId: connector_type + inputType: PARAMETER + value: "CCS2" + providedAt: TEMPLATE + - inputId: power_rating_kw + inputType: PARAMETER + value: 60 + providedAt: TEMPLATE + + - roleId: ev_driver + roleType: CONSUMER + party: null # Empty = open slot in catalog + inputs: + - inputId: requested_kwh + inputType: PARAMETER + required: true + schema: { type: number, minimum: 1 } + providedAt: NEGOTIATION # Consumer fills during select + - inputId: vehicle_details + inputType: PARAMETER + required: false + providedAt: COMMITMENT # Optional, during init + - inputId: billing_info + inputType: PARAMETER + required: true + providedAt: COMMITMENT + +energySpec: + energyType: ELECTRICAL + sourceType: ANY + quantity: + unitText: "kWh" + maxQuantity: 100 # Available capacity + +fulfillmentSpec: + deliveryMode: EV_CHARGING + location: + geo: { type: Point, coordinates: [77.6104, 12.9153] } + signals: + - signalType: START_STOP + protocol: OCPP + +terms: + - termId: pricing + termType: PAYMENT + condition: + expr: "true" # Always applies + obligation: + expr: "payment = requestedKwh * pricePerKwh" + appliesTo: ["ev_driver"] + + - termId: delivery + termType: DELIVERY + condition: + expr: "paymentStatus == PAID" + obligation: + expr: "deliverEnergy(requestedKwh) AT selectedFulfillment" + appliesTo: ["cpo"] + + - termId: cancellation + termType: CANCELLATION + condition: + expr: "fulfillmentState IN [charging-start, charging-in-progress]" + consequence: + expr: "cancellationFee = 30%" + appliesTo: ["ev_driver"] + +settlementSpec: + paymentType: PRE-ORDER + currency: "INR" +``` + +### Field Mapping: Existing → DEGContract + +| Existing EvCharging Field | DEGContract Location | +|--------------------------|---------------------| +| `provider.descriptor` | `roles[cpo].party.attributes.descriptor` | +| `provider.locations[]` | `fulfillmentSpec.location` | +| `items[].price` | `terms[pricing].obligation` | +| `items[].quantity.available` | `energySpec.quantity` | +| `fulfillments[].type: CHARGING` | `fulfillmentSpec.deliveryMode: EV_CHARGING` | +| `fulfillments[].tags` (connector specs) | `roles[cpo].inputs[]` | +| `fulfillments[].stops` | `fulfillmentSpec.deliveryWindow` | +| `order.billing` | `roles[ev_driver].inputs[billing_info]` | +| `cancellation_terms[]` | `terms[cancellation]` | +| `payments[].type: PRE-ORDER` | `settlementSpec.paymentType` | + +--- + +## 2. P2P Energy Trade as a DEGContract + +### Current Schema Structure (EnergyTrade v0.3) + +``` +Catalog + └─ items[] + ├─ itemAttributes: EnergyResource (sourceType, meterId, capacity) + ├─ provider.providerAttributes: EnergyCustomer (meterId, utilityId) + └─ offers[] + ├─ price (PriceSpecification with applicableQuantity) + └─ offerAttributes: EnergyTradeOffer (pricingModel, deliveryWindow) + +Order + ├─ orderAttributes: EnergyTradeOrder (bap_id, bpp_id, total_quantity) + ├─ buyer.buyerAttributes: EnergyCustomer (meterId, utilityId) + └─ orderItems[] + └─ orderItemAttributes: EnergyOrderItem + ├─ providerAttributes: EnergyCustomer (destination meter) + └─ fulfillmentAttributes: EnergyTradeDelivery (meterReadings, deliveredQuantity) +``` + +### As a DEGContract + +```yaml +contractType: P2P_ENERGY_TRADE +version: "0.3.0" + +roles: + - roleId: seller + roleType: PROSUMER + party: + partyId: "solar-farm-001" + partyType: PROVIDER + platformId: "bpp.solarprovider.io" + attributes: + "@type": "EnergyCustomer" + meterId: "der://meter/100200300" + utilityId: "BESCOM-KA" + sanctionedLoad: 25.0 + inputs: + - inputId: source_meter + inputType: METER_ID + value: "der://meter/100200300" # Pre-filled + providedAt: TEMPLATE + - inputId: energy_source + inputType: PARAMETER + value: "SOLAR" + providedAt: TEMPLATE + - inputId: available_kwh + inputType: PARAMETER + value: 30.5 + providedAt: TEMPLATE + + - roleId: buyer + roleType: CONSUMER + party: null # Open slot + inputs: + - inputId: destination_meter + inputType: METER_ID + required: true + providedAt: NEGOTIATION + - inputId: requested_kwh + inputType: PARAMETER + required: true + schema: { type: number, minimum: 1, maximum: 30.5 } + providedAt: NEGOTIATION + - inputId: buyer_utility_id + inputType: PARAMETER + required: true + providedAt: COMMITMENT + + - roleId: utility + roleType: UTILITY + party: null # Resolved via cascaded init + inputs: + - inputId: wheeling_charge + inputType: PARAMETER + providedAt: COMMITMENT # Utility provides during cascaded init + filledBy: "utility" + +energySpec: + energyType: ELECTRICAL + sourceType: SOLAR + quantity: + unitText: "kWh" + unitQuantity: 30.5 + qualityConstraints: + greenCertRequired: true + +fulfillmentSpec: + deliveryMode: GRID_INJECTION + deliveryWindow: + "@type": "beckn:TimePeriod" + schema:startTime: "2025-06-15T09:00:00Z" + schema:endTime: "2025-06-15T17:00:00Z" + meterIds: ["der://meter/100200300"] # Seller's meter (buyer's added at negotiation) + signals: [] # No real-time signals for basic P2P + +terms: + - termId: delivery_obligation + termType: DELIVERY + condition: + expr: "currentTime WITHIN deliveryWindow" + obligation: + rule: + operator: AND + operands: + - { field: "deliveredQuantity", operator: GTE, value: "requestedKwh * 0.9" } + - { field: "deliveredQuantity", operator: LTE, value: "requestedKwh * 1.1" } + consequence: + expr: "deviationPenalty = abs(deliveredQuantity - requestedKwh) * deviationPenaltyPerKwh" + appliesTo: ["seller"] + + - termId: pricing + termType: PAYMENT + condition: + expr: "true" + obligation: + expr: "payment = deliveredQuantity * pricePerKwh + wheelingCharge" + appliesTo: ["buyer"] + + - termId: utility_registration + termType: QUALITY + condition: + expr: "contractState == COMMITTED" + obligation: + policyRef: + type: REGO + package: "deg.contracts.p2p_trade" + entrypoint: "sanctioned_load_check" + appliesTo: ["utility"] + +settlementSpec: + paymentType: POST_DELIVERY + currency: "INR" + settlementCycle: "15_MIN" +``` + +### Field Mapping: Existing → DEGContract + +| Existing EnergyTrade Field | DEGContract Location | +|---------------------------|---------------------| +| `EnergyResource.sourceType` | `energySpec.sourceType` / `roles[seller].inputs[energy_source]` | +| `EnergyResource.meterId` | `roles[seller].inputs[source_meter]` | +| `EnergyTradeOffer.pricingModel` | `terms[pricing]` | +| `EnergyTradeOffer.deliveryWindow` | `fulfillmentSpec.deliveryWindow` | +| `EnergyTradeOrder.bap_id` | `roles[buyer].party.platformId` | +| `EnergyTradeOrder.bpp_id` | `roles[seller].party.platformId` | +| `EnergyTradeOrder.total_quantity` | `energySpec.quantity` | +| `EnergyCustomer.meterId` (buyer) | `roles[buyer].inputs[destination_meter]` | +| `EnergyCustomer.utilityId` | `roles[buyer].inputs[buyer_utility_id]` | +| `EnergyTradeDelivery.deliveredQuantity` | Fulfillment state during `on_status` | +| `EnergyTradeDelivery.meterReadings` | Fulfillment state during `on_status` | + +--- + +## 3. Demand Flexibility as a DEGContract + +### Concept (No existing schema yet) + +Demand flexibility involves a grid operator or aggregator signaling consumers to curtail or shift load in response to grid conditions (frequency, price, congestion). + +### As a DEGContract + +```yaml +contractType: DEMAND_FLEXIBILITY +version: "0.1.0" + +roles: + - roleId: aggregator + roleType: AGGREGATOR + party: + partyId: "flex-agg-001" + partyType: PROVIDER + platformId: "bpp.flexaggregator.io" + inputs: + - inputId: program_name + inputType: PARAMETER + value: "Peak Shaving Q3 2025" + providedAt: TEMPLATE + - inputId: incentive_rate + inputType: PARAMETER + value: 5.0 # INR per kWh curtailed + providedAt: TEMPLATE + - inputId: max_events_per_month + inputType: PARAMETER + value: 10 + providedAt: TEMPLATE + + - roleId: participant + roleType: CONSUMER + party: null + inputs: + - inputId: participant_meter + inputType: METER_ID + required: true + providedAt: COMMITMENT + - inputId: baseline_load_kw + inputType: PARAMETER + required: true + schema: { type: number, minimum: 0 } + providedAt: COMMITMENT + - inputId: curtailment_capacity_kw + inputType: PARAMETER + required: true + providedAt: COMMITMENT + - inputId: enrollment_credential + inputType: CREDENTIAL + required: true + providedAt: COMMITMENT + + - roleId: grid_operator + roleType: GRID_OPERATOR + party: + partyId: "BESCOM" + partyType: PROVIDER + platformId: "bpp.bescom.gov.in" + inputs: + - inputId: grid_frequency_feed + inputType: SIGNAL + value: "mqtt://grid.bescom.gov.in/frequency" + providedAt: TEMPLATE + - inputId: curtailment_signal_topic + inputType: SIGNAL + value: "mqtt://grid.bescom.gov.in/curtailment" + providedAt: TEMPLATE + +energySpec: + energyType: ELECTRICAL + sourceType: ANY + quantity: + unitText: "kW" + unitQuantity: 0 # Demand response = negative supply + qualityConstraints: {} + +fulfillmentSpec: + deliveryMode: LOAD_CURTAILMENT + deliveryWindow: + "@type": "beckn:TimePeriod" + schema:startDate: "2025-07-01T00:00:00Z" + schema:endDate: "2025-09-30T23:59:59Z" + meterIds: [] # Filled when participant enrolls + signals: + - signalType: GRID_FREQUENCY + source: "mqtt://grid.bescom.gov.in/frequency" + protocol: MQTT + - signalType: CURTAILMENT_COMMAND + source: "mqtt://grid.bescom.gov.in/curtailment" + protocol: MQTT + - signalType: PRICE_SIGNAL + source: "https://api.iex.in/spot-price" + protocol: HTTP_WEBHOOK + +terms: + - termId: trigger_condition + termType: SIGNAL_RESPONSE + condition: + policyRef: + type: REGO + package: "deg.contracts.demand_flex" + entrypoint: "demand_response_triggered" + obligation: + expr: "curtailLoad BY curtailmentCapacity WITHIN 15 minutes" + appliesTo: ["participant"] + + - termId: measurement + termType: DELIVERY + condition: + expr: "eventTriggered == true" + obligation: + rule: + operator: GTE + operands: + - { field: "actualCurtailment" } + - { field: "curtailmentCapacity * 0.8" } + appliesTo: ["participant"] + + - termId: incentive_payment + termType: PAYMENT + condition: + expr: "measurement.satisfied == true" + obligation: + expr: "incentive = actualCurtailment * incentiveRate * eventDurationHours" + appliesTo: ["aggregator"] + + - termId: event_limit + termType: QUALITY + condition: + expr: "eventsThisMonth >= maxEventsPerMonth" + obligation: + expr: "noMoreEvents UNTIL nextMonth" + appliesTo: ["grid_operator"] + +settlementSpec: + paymentType: POST_DELIVERY + currency: "INR" + settlementCycle: "MONTHLY" +``` + +### Corresponding Rego Policy + +```rego +package deg.contracts.demand_flex + +import rego.v1 + +# Trigger: grid frequency drops below threshold +demand_response_triggered if { + input.gridFrequency < 49.5 +} + +# Trigger: spot price exceeds threshold +demand_response_triggered if { + input.spotPrice > data.contract.terms.priceThreshold +} + +# Trigger: explicit curtailment signal received +demand_response_triggered if { + input.curtailmentSignal == "ACTIVE" +} + +# Measurement: was the curtailment sufficient? +curtailment_compliant if { + reduction := input.baselineLoad - input.actualLoad + reduction >= data.contract.inputs.curtailmentCapacity * 0.8 +} + +# Incentive calculation +incentive_amount := amount if { + curtailment_compliant + reduction := input.baselineLoad - input.actualLoad + hours := (input.eventEnd - input.eventStart) / 3600 + amount := reduction * data.contract.inputs.incentiveRate * hours +} +``` + +--- + +## 4. Composite Contract Example + +A V2G (Vehicle-to-Grid) scenario combines EV Charging + Demand Flexibility: + +```yaml +contractType: COMPOSITE +version: "0.1.0" + +roles: + - roleId: ev_owner + roleType: PROSUMER # Both consumes (charging) and produces (V2G) + inputs: + - inputId: vehicle_battery_kwh + inputType: PARAMETER + required: true + providedAt: NEGOTIATION + - inputId: min_charge_level_pct + inputType: PARAMETER + value: 30 # Never drain below 30% + providedAt: NEGOTIATION + + - roleId: cpo_aggregator + roleType: AGGREGATOR # Both CPO and demand flexibility aggregator + inputs: + - inputId: v2g_rate_per_kwh + inputType: PARAMETER + value: 6.0 + providedAt: TEMPLATE + +terms: + # EV Charging terms + - termId: charging + termType: DELIVERY + condition: + expr: "gridStress == false" + obligation: + expr: "chargeVehicle AT requestedRate" + appliesTo: ["cpo_aggregator"] + + # V2G discharge terms + - termId: v2g_discharge + termType: SIGNAL_RESPONSE + condition: + rule: + operator: AND + operands: + - { field: "gridStress", operator: EQ, value: true } + - { field: "vehicleBatteryPct", operator: GTE, value: "minChargeLevelPct" } + obligation: + expr: "dischargeToGrid AT maxAvailableRate" + appliesTo: ["ev_owner"] + + # Payment flows both ways + - termId: charging_payment + termType: PAYMENT + condition: + expr: "energyFlowDirection == TO_VEHICLE" + obligation: + expr: "evOwner PAYS chargingRate * kwhDelivered" + appliesTo: ["ev_owner"] + + - termId: v2g_payment + termType: PAYMENT + condition: + expr: "energyFlowDirection == FROM_VEHICLE" + obligation: + expr: "cpoAggregator PAYS v2gRate * kwhDischarged" + appliesTo: ["cpo_aggregator"] + +revenueModel: + flows: + # Normal charging: EV owner pays CPO + - flowId: charging_payment + from: ev_owner + to: cpo_aggregator + flowType: ENERGY_PAYMENT + formula: + expr: "chargedKwh * chargingRate" + triggeredBy: charging_payment + + # V2G discharge: CPO pays EV owner + - flowId: v2g_payment + from: cpo_aggregator + to: ev_owner + flowType: INCENTIVE + formula: + expr: "dischargedKwh * v2gRate" + triggeredBy: v2g_payment + + # Grid operator pays CPO for V2G ancillary services + - flowId: grid_ancillary_payment + from: grid_operator + to: cpo_aggregator + flowType: GRID_SERVICE_FEE + formula: + expr: "dischargedKwh * gridAncillaryRate" + + # Platform fee + - flowId: platform_fee + from: cpo_aggregator + to: platform + flowType: PLATFORM_FEE + formula: + expr: "(chargingPayment + gridAncillaryPayment) * 0.015" +``` + +**Per-role revenue view (charged 20 kWh @ INR 8, discharged 5 kWh @ INR 12 V2G, grid pays INR 15/kWh):** + +| Role | Inflows | Outflows | Net Position | Direction | +|------|---------|----------|-------------|-----------| +| **ev_owner** | 60 (V2G) | 160 (charging) | **-100** | PAYS | +| **cpo_aggregator** | 160 (charging) + 75 (grid) = 235 | 60 (V2G) + 3.53 (fee) = 63.53 | **+171.47** | RECEIVES | +| **grid_operator** | - | 75 (ancillary) | **-75** | PAYS | +| **platform** | 3.53 (fee) | - | **+3.53** | RECEIVES | +| **Total** | | | **0.00** | | + +An agent for `ev_owner` instantly sees: "I pay INR 100 net, but get 5 kWh discharged credit worth INR 60. My effective charging cost is INR 100 for 20 kWh = INR 5/kWh instead of INR 8/kWh." + +--- + +## 5. How Agents Query Their Revenue Incentive + +Each agent acting on behalf of a role needs to answer one question: **"What's in it for me?"** + +### 5.1 Query Pattern + +``` +GET /settlement/my-position?roleId={roleId}&contractId={contractId} +``` + +Returns (computed by Rego `deg.revenue.net_position`): + +```json +{ + "roleId": "seller", + "contractId": "contract-p2p-001", + "position": { + "inflows": [ + { "flowId": "energy_payment", "from": "buyer", "amount": 45.00, "type": "ENERGY_PAYMENT" } + ], + "outflows": [ + { "flowId": "platform_fee_seller", "to": "platform", "amount": 0.45, "type": "PLATFORM_FEE" } + ], + "netAmount": 44.55, + "currency": "INR", + "direction": "RECEIVES" + }, + "incentiveSummary": "You earn INR 44.55 for delivering 10 kWh of solar energy." +} +``` + +### 5.2 Pre-Commitment Revenue Preview + +Before a party accepts a role, they can preview their revenue position from the template: + +``` +POST /contract/preview-revenue +Body: { contractId, roleId, estimatedInputs: { requestedKwh: 10, ... } } +``` + +This evaluates the `revenueModel.flows` with estimated values, so the agent knows: +- "If I accept this contract and deliver 10 kWh, I'll earn approximately INR 44.55" +- "If I curtail 5 kW for 2 hours, I'll earn approximately INR 50" + +### 5.3 Multi-Contract Portfolio View + +An agent managing multiple contracts can aggregate across all of them: + +```json +{ + "agentId": "solar-farm-001", + "roleId": "seller", + "portfolio": [ + { "contractId": "p2p-001", "net": 44.55, "status": "ACTIVE" }, + { "contractId": "p2p-002", "net": 89.10, "status": "ACTIVE" }, + { "contractId": "flex-001", "net": 150.00, "status": "SETTLED" } + ], + "totalNetPosition": 283.65, + "currency": "INR" +} +``` + +--- + +## 6. Migration Strategy + +The generalized DEGContract does NOT replace existing schemas. Instead: + +1. **Existing schemas remain** as the concrete attribute packs (ChargingService, EnergyResource, EnergyCustomer, etc.) +2. **DEGContract** is a **new Offer/Order-level attribute pack** that wraps the existing ones with a contract-oriented view +3. **Incremental adoption**: Platforms can start using DEGContract for new use cases (demand flexibility) while keeping existing schemas for established flows + +``` + ┌──────────────────────────────────┐ + │ DEGContract │ + │ (Order.orderAttributes) │ + │ │ + │ roles[] ──→ Provider/Buyer │ + │ terms[] ──→ PolicyExpr/Rego │ + │ energySpec ──→ Item.attrs │ + │ fulfillmentSpec ──→ Fulfmt │ + │ revenueModel ──→ Net-zero flows │ + │ │ │ + │ ├─ flow[0]: buyer→seller │ + │ ├─ flow[1]: buyer→utility │ + │ └─ Σ(all flows) == 0 │ + └──────────┬───────────────────────┘ + │ references + ┌──────────┴──────────────────┐ + │ Existing Attribute Packs │ + │ │ + │ ChargingService (Item) │ + │ EnergyResource (Item) │ + │ EnergyCustomer (Buyer/Prov) │ + │ EnergyTradeDelivery (Fulfmt) │ + │ EnergyEnrollment (Fulfmt) │ + └──────────────────────────────┘ +``` diff --git a/examples/deg_contract/README.md b/examples/deg_contract/README.md new file mode 100644 index 00000000..f6e4738f --- /dev/null +++ b/examples/deg_contract/README.md @@ -0,0 +1,106 @@ +# DEG Contract Examples + +Example Beckn Protocol v2 message flows showing the **DEGContract-as-Code** schema in action. Each subdirectory contains a complete transaction lifecycle from discovery through settlement. + +## Directory Structure + +``` +deg_contract/ +├── ev_charging/ # Standard EV charging contract (9 files) +├── p2p-trading-interdiscom/ # Inter-discom P2P energy trade with cascaded utility init (11 files) +└── smart_ev_charging/ # Composite: EV charging + V2G demand flexibility (10 files) +``` + +## Pure Beckn Slot Mapping + +DEGContract is the **primary `@type`** in `offerAttributes` and `orderAttributes`. Domain-specific data lives in dedicated Beckn attribute slots using first-principles schemas. + +| Beckn Slot | `@type` | Purpose | First Appears | +|------------|---------|---------|--------------| +| `itemAttributes` | **EnergyResource** | Physical resource: charger, solar plant, battery | `on_discover` | +| `offerAttributes` | **DEGContract** (TEMPLATE) | Contract template with open role slots | `on_discover` | +| `providerAttributes` | **EnergyProvider** | Provider identity, registration, grid account | `on_discover` | +| `buyerAttributes` | **EnergyBuyer** | Buyer vehicle info or grid account | `select` | +| `orderAttributes` | **DEGContract** (NEGOTIATED→SETTLED) | Contract through full lifecycle | `select` onwards | +| `deliveryAttributes` | **EnergyDelivery** | Runtime telemetry, session state, V2G events | `on_confirm` | +| `paymentAttributes` | **PaymentSettlement** | Settlement accounts & reconciliation | `init` | + +### Schema Design Principles + +- **One unified `EnergyResource`** with `resourceType` enum (EV_CHARGER, GENERATION_PLANT, BATTERY_STORAGE, GRID_CONNECTION) — not separate schemas per domain +- **One unified `EnergyDelivery`** with sparse sub-objects (`v2g`, `grid`, `vehicle`, `meterReadings`) — consumers check `deliveryMode` to know which are present +- **DEGContract absorbs commercial terms** — tariffModel, idleFeePolicy, pricingModel etc. are expressed in `terms[]` and `revenueModel.flows[]`, not in wrapper schemas +- **Buyer billing stays in `beckn:Buyer`** — EnergyBuyer only adds domain-specific info (vehicle, grid account) + +## How DEGContract Maps to Beckn Lifecycle + +| Beckn Action | DEGContract State | What Changes | +|-------------|-------------------|-------------| +| `on_discover` (catalog) | **TEMPLATE** | Provider roles filled, buyer roles open (`party: null`) | +| `select` | TEMPLATE → **NEGOTIATED** | Buyer fills quantity, preferences | +| `on_select` (quote) | **NEGOTIATED** | BPP returns computed pricing | +| `init` / `on_init` | **COMMITTED** | Buyer fills meter ID, billing, credentials | +| `confirm` / `on_confirm` | **ACTIVE** | All roles filled, `deliveryAttributes` appears (PENDING) | +| `on_status` | **ACTIVE** | `deliveryAttributes` updated with telemetry | +| `on_update` | **ACTIVE** | V2G events, demand flex triggers | +| `on_status` (final) / `on_update` (complete) | **SETTLED** | `computedAmount` in all revenue flows, net-zero verified | + +## Key Patterns + +### 1. Progressive Role Filling +In the catalog (`on_discover`), the DEGContract is a **template** with: +- Provider/seller roles **filled** (their inputs pre-populated) +- Consumer/buyer roles **open** (`party: null`, inputs unfilled) + +As the transaction progresses, the buyer progressively fills their role inputs. + +### 2. Revenue Model & Net-Zero Invariant +Every contract includes a `revenueModel` with directed monetary flows: +- At discovery: flows show **formulas** (how amounts are computed) +- At settlement: flows show **computedAmount** (actual INR values) +- **Conservation law**: for every party, `sum(inflows) - sum(outflows)` across ALL parties = 0 + +### 3. Policy References (Contract-as-Code) +Contract terms reference OPA/Rego policies for complex logic: +```json +{ + "condition": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.ev_charging", + "entrypoint": "connector_compatible" + } + } +} +``` +See `specification/policies/` for the Rego policy files. + +### 4. Composite Contracts +Smart EV charging uses `contractType: "COMPOSITE"` with `composedOf: ["EV_CHARGING", "DEMAND_FLEXIBILITY"]` — combining EV charging and V2G grid services in one contract. + +## Use Cases + +### EV Charging (`ev_charging/`, 9 files) +- **Roles**: `cpo` (CPO) + `ev_driver` (CONSUMER) +- **Revenue**: charging_payment + platform_fee + GST + idle_fee - refund_overcharge +- **Resource**: EV_CHARGER, CCS2 DC fast charger at ChargeZone Bangalore + +### P2P Inter-Discom Trading (`p2p-trading-interdiscom/`, 11 files) +- **Roles**: `seller` (PRODUCER) + `buyer` (CONSUMER) + `source_utility` (DISCOM) + `destination_utility` (DISCOM) +- **Revenue**: energy_payment + wheeling_charge(×2) + platform_fee(×2) + deviation_penalty +- **Resource**: GENERATION_PLANT, 10 kW rooftop solar in Delhi +- **Special**: Cascaded init (P2P BPP acts as BAP to utility BPP for wheeling approval) + +### Smart EV Charging + V2G (`smart_ev_charging/`, 10 files) +- **Roles**: `ev_owner` (PROSUMER) + `cpo_aggregator` (AGGREGATOR) + `grid_operator` (GRID_OPERATOR) +- **Revenue**: charging_payment - v2g_earnings + grid_v2g_payment + platform_fee +- **Resource**: EV_CHARGER with V2G capability, Tata EZ Charge Hub Mumbai +- **Special**: V2G dispatch on grid frequency < 49.5 Hz, Rego policy evaluation inline + +## Related Files + +- **Schema**: `specification/schema/deg_contract_schema.yaml` +- **Policies**: `specification/policies/*.rego` +- **Arazzo Workflow**: `specification/deg_contract_beckn_flow.arazzo.yaml` +- **Specification**: `docs/implementation-guides/deg_contract/DEG_Contract_Specification.md` +- **Mapping Guide**: `docs/implementation-guides/deg_contract/Mapping_Existing_Schemas.md` diff --git a/examples/deg_contract/ev_charging/confirm.json b/examples/deg_contract/ev_charging/confirm.json new file mode 100644 index 00000000..1033d798 --- /dev/null +++ b/examples/deg_contract/ev_charging/confirm.json @@ -0,0 +1,356 @@ +{ + "context": { + "version": "2.0.0", + "action": "confirm", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-confirm-001", + "timestamp": "2026-01-15T10:33:00Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-ev-cz-001", + "beckn:orderStatus": "PENDING", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456", + "beckn:role": "BUYER", + "beckn:displayName": "Ravi Shankar", + "beckn:telephone": "+91-9845012345", + "beckn:email": "ravi.shankar@gmail.com", + "beckn:taxID": "GSTIN29AABCS1234D1Z5" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + }, + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 649.35, + "components": [ + { + "type": "UNIT", + "value": 540.0, + "currency": "INR", + "description": "Base charging cost (18 INR/kWh x 30 kWh)" + }, + { + "type": "FEE", + "value": 13.5, + "currency": "INR", + "description": "Platform fee (2.5%)" + }, + { + "type": "SURCHARGE", + "value": 99.63, + "currency": "INR", + "description": "GST @ 18%" + }, + { + "type": "FEE", + "value": 11.22, + "currency": "INR", + "description": "Overcharge buffer estimation (2%)" + }, + { + "type": "DISCOUNT", + "value": -15.0, + "currency": "INR", + "description": "New user promotional discount" + } + ] + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-cz-ev-001", + "beckn:amount": { + "currency": "INR", + "value": 649.35 + }, + "beckn:paymentURL": "https://payments.chargezone.in/pay?txn_id=txn-ev-001&amount=649.35¤cy=INR", + "beckn:txnRef": "TXN-CZ-2026011501", + "beckn:paidAt": "2026-01-15T10:32:45Z", + "beckn:beneficiary": "BPP", + "beckn:paymentStatus": "COMPLETED" + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "ACTIVE", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation", "quality_obligation"] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "value": { + "displayName": "Ravi Shankar", + "taxID": "GSTIN29AABCS1234D1Z5", + "telephone": "+91-9845012345", + "email": "ravi.shankar@gmail.com" + }, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "FULFILLMENT" + } + ], + "obligations": ["payment_obligation", "cancellation_terms"] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy charged at 18 INR/kWh.", + "condition": { "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" }, + "obligation": { "expr": "payment.amount == 18.0 * fulfillment.deliveredEnergy" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO must deliver 30 kWh within the agreed time window.", + "condition": { "expr": "contractState == 'ACTIVE'" }, + "obligation": { "expr": "fulfillment.deliveredEnergy >= 30" }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "Payment of 649.35 INR completed.", + "condition": { "expr": "contractState == 'COMMITTED'" }, + "obligation": { "expr": "payment.status == 'COMPLETED'" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "cancellation_terms", + "termType": "CANCELLATION", + "description": "Free cancellation before 2026-01-15T10:45:00Z. Late cancellation fee: 162.34 INR.", + "condition": { "expr": "cancellation.requested == true" }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { "expr": "cancellation.time < '2026-01-15T10:45:00Z'" }, + { "expr": "cancellation.fee == 0" } + ] + } + }, + "consequence": { "expr": "cancellation.fee == 162.34" }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T12:30:00Z" + }, + "location": { + "geo": { "type": "Point", "coordinates": [77.6104, 12.9352] } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { "expr": "tariff_rate * delivered_kwh" }, + "currency": "INR", + "computedAmount": 540.0, + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { "expr": "base_charge * platform_fee_pct" }, + "currency": "INR", + "computedAmount": 13.5, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { "expr": "(base_charge + platform_fee_amount) * gst_rate" }, + "currency": "INR", + "computedAmount": 99.63, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/init.json b/examples/deg_contract/ev_charging/init.json new file mode 100644 index 00000000..40699728 --- /dev/null +++ b/examples/deg_contract/ev_charging/init.json @@ -0,0 +1,406 @@ +{ + "context": { + "version": "2.0.0", + "action": "init", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-init-001", + "timestamp": "2026-01-15T10:32:00Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-ev-cz-001", + "beckn:orderStatus": "CREATED", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456", + "beckn:role": "BUYER", + "beckn:displayName": "Ravi Shankar", + "beckn:telephone": "+91-9845012345", + "beckn:email": "ravi.shankar@gmail.com", + "beckn:taxID": "GSTIN29AABCS1234D1Z5" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": null, + "makeModel": null, + "batteryKwh": null, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + }, + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 649.35, + "components": [ + { + "type": "UNIT", + "value": 540.0, + "currency": "INR", + "description": "Base charging cost (18 INR/kWh x 30 kWh)" + }, + { + "type": "FEE", + "value": 13.5, + "currency": "INR", + "description": "Platform fee (2.5%)" + }, + { + "type": "SURCHARGE", + "value": 99.63, + "currency": "INR", + "description": "GST @ 18%" + }, + { + "type": "FEE", + "value": 11.22, + "currency": "INR", + "description": "Overcharge buffer estimation (2%)" + }, + { + "type": "DISCOUNT", + "value": -15.0, + "currency": "INR", + "description": "New user promotional discount" + } + ] + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-cz-ev-001", + "beckn:amount": { + "currency": "INR", + "value": 649.35 + }, + "beckn:beneficiary": "BAP", + "beckn:paymentStatus": "INITIATED", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/PaymentSettlement/v1/context.jsonld", + "@type": "PaymentSettlement", + "settlementAccounts": [ + { + "beneficiaryId": "ev-bap.mobility-app.in", + "accountHolderName": "MobilityApp Solutions Pvt Ltd", + "accountNumber": "5012345678901", + "ifscCode": "ICIC0001234", + "bankName": "ICICI Bank", + "vpa": "mobilityapp@icici" + } + ] + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "COMMITTED", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + } + ], + "obligations": [ + "delivery_obligation", + "quality_obligation" + ] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_start_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T11:00:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_end_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T12:30:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "value": { + "displayName": "Ravi Shankar", + "taxID": "GSTIN29AABCS1234D1Z5", + "telephone": "+91-9845012345", + "email": "ravi.shankar@gmail.com", + "billingAddress": { + "streetAddress": "42, 3rd Cross, HSR Layout Sector 2", + "addressLocality": "Bengaluru", + "addressRegion": "Karnataka", + "postalCode": "560102", + "addressCountry": "IN" + } + }, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "FULFILLMENT" + } + ], + "obligations": [ + "payment_obligation", + "cancellation_terms" + ] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy charged at 18 INR/kWh. Estimated total for 30 kWh = 540 INR.", + "condition": { "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" }, + "obligation": { "expr": "payment.amount == 18.0 * fulfillment.deliveredEnergy" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO must deliver 30 kWh within the agreed time window.", + "condition": { "expr": "contractState == 'ACTIVE'" }, + "obligation": { "expr": "fulfillment.deliveredEnergy >= 30" }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "EV driver must complete payment of 649.35 INR before session start.", + "condition": { "expr": "contractState == 'COMMITTED'" }, + "obligation": { "expr": "payment.status == 'COMPLETED' AND payment.amount >= 649.35" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "cancellation_terms", + "termType": "CANCELLATION", + "description": "Free cancellation before 2026-01-15T10:45:00Z. Late cancellation incurs 162.34 INR (25%).", + "condition": { "expr": "cancellation.requested == true" }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { "expr": "cancellation.time < '2026-01-15T10:45:00Z'" }, + { "expr": "cancellation.fee == 0" } + ] + } + }, + "consequence": { "expr": "cancellation.fee == 162.34" }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T12:30:00Z" + }, + "location": { + "geo": { "type": "Point", "coordinates": [77.6104, 12.9352] } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { "expr": "tariff_rate * delivered_kwh" }, + "currency": "INR", + "computedAmount": 540.0, + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { "expr": "base_charge * platform_fee_pct" }, + "currency": "INR", + "computedAmount": 13.5, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { "expr": "(base_charge + platform_fee_amount) * gst_rate" }, + "currency": "INR", + "computedAmount": 99.63, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/on_confirm.json b/examples/deg_contract/ev_charging/on_confirm.json new file mode 100644 index 00000000..26e258a7 --- /dev/null +++ b/examples/deg_contract/ev_charging/on_confirm.json @@ -0,0 +1,383 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_confirm", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-on_confirm-001", + "timestamp": "2026-01-15T10:33:05Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-ev-cz-001", + "beckn:orderStatus": "CONFIRMED", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456", + "beckn:role": "BUYER", + "beckn:displayName": "Ravi Shankar", + "beckn:telephone": "+91-9845012345", + "beckn:email": "ravi.shankar@gmail.com", + "beckn:taxID": "GSTIN29AABCS1234D1Z5" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + }, + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 649.35, + "components": [ + { + "type": "UNIT", + "value": 540.0, + "currency": "INR", + "description": "Base charging cost (18 INR/kWh x 30 kWh)" + }, + { + "type": "FEE", + "value": 13.5, + "currency": "INR", + "description": "Platform fee (2.5%)" + }, + { + "type": "SURCHARGE", + "value": 99.63, + "currency": "INR", + "description": "GST @ 18%" + }, + { + "type": "FEE", + "value": 11.22, + "currency": "INR", + "description": "Overcharge buffer estimation (2%)" + }, + { + "type": "DISCOUNT", + "value": -15.0, + "currency": "INR", + "description": "New user promotional discount" + } + ] + }, + "beckn:fulfillment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-cz-001", + "beckn:mode": "RESERVATION", + "beckn:deliveryAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "PENDING", + "deliveryMode": "EV_CHARGING", + "energyFlow": { + "direction": "TO_VEHICLE", + "delivered": { + "value": 0, + "unitCode": "KWH" + } + }, + "timing": { + "startTime": null, + "elapsed": null + }, + "connector": { + "type": "CCS2", + "maxPowerKW": 60 + }, + "telemetry": [], + "lastUpdated": "2026-01-15T10:33:05Z" + } + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-cz-ev-001", + "beckn:amount": { + "currency": "INR", + "value": 649.35 + }, + "beckn:paymentURL": "https://payments.chargezone.in/pay?txn_id=txn-ev-001&amount=649.35¤cy=INR", + "beckn:txnRef": "TXN-CZ-2026011501", + "beckn:paidAt": "2026-01-15T10:32:45Z", + "beckn:beneficiary": "BPP", + "beckn:paymentStatus": "COMPLETED" + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "ACTIVE", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation", "quality_obligation"] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "value": { + "displayName": "Ravi Shankar", + "taxID": "GSTIN29AABCS1234D1Z5" + }, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "FULFILLMENT" + } + ], + "obligations": ["payment_obligation", "cancellation_terms"] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy charged at 18 INR/kWh.", + "condition": { "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" }, + "obligation": { "expr": "payment.amount == 18.0 * fulfillment.deliveredEnergy" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO must deliver 30 kWh within the agreed time window.", + "condition": { "expr": "contractState == 'ACTIVE'" }, + "obligation": { "expr": "fulfillment.deliveredEnergy >= 30" }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "Payment of 649.35 INR completed via UPI.", + "condition": { "expr": "contractState == 'COMMITTED'" }, + "obligation": { "expr": "payment.status == 'COMPLETED'" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "cancellation_terms", + "termType": "CANCELLATION", + "description": "Free cancellation before 2026-01-15T10:45:00Z. Late cancellation fee: 162.34 INR.", + "condition": { "expr": "cancellation.requested == true" }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { "expr": "cancellation.time < '2026-01-15T10:45:00Z'" }, + { "expr": "cancellation.fee == 0" } + ] + } + }, + "consequence": { "expr": "cancellation.fee == 162.34" }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T12:30:00Z" + }, + "location": { + "geo": { "type": "Point", "coordinates": [77.6104, 12.9352] } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { "expr": "tariff_rate * delivered_kwh" }, + "currency": "INR", + "computedAmount": 540.0, + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { "expr": "base_charge * platform_fee_pct" }, + "currency": "INR", + "computedAmount": 13.5, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { "expr": "(base_charge + platform_fee_amount) * gst_rate" }, + "currency": "INR", + "computedAmount": 99.63, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/on_discover.json b/examples/deg_contract/ev_charging/on_discover.json new file mode 100644 index 00000000..98c2fcac --- /dev/null +++ b/examples/deg_contract/ev_charging/on_discover.json @@ -0,0 +1,604 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_discover", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-on_discover-001", + "timestamp": "2026-01-15T10:30:05Z", + "ttl": "PT30S" + }, + "message": { + "catalogs": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Catalog", + "beckn:id": "catalog-chargezone-blr-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "ChargeZone EV Charging Network", + "beckn:shortDesc": "Premium fast charging stations across Bengaluru with CCS2 and Type2 connectors" + }, + "beckn:bppId": "chargezone-bpp.in", + "beckn:bppUri": "https://api.chargezone-bpp.in/beckn/v2", + "beckn:providers": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Provider", + "beckn:id": "chargezone-blr", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "ChargeZone Pvt Ltd" + }, + "beckn:providerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_provider_context.jsonld", + "@type": "EnergyProvider", + "operatorName": "ChargeZone Pvt Ltd", + "operatorCode": "CHARGEZONE", + "identifier": "chargezone-blr", + "contact": { + "email": "support@chargezone.in", + "phone": "+91-1800-123-4567" + }, + "registration": { + "gstNumber": "29AABCC1234D1Z5", + "cin": "U40100KA2020PTC139012", + "cpoLicense": "KERC/CPO/2024/0031" + } + } + } + ], + "beckn:items": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Item", + "beckn:id": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "DC Fast Charger - CCS2 (60kW)", + "beckn:shortDesc": "High-speed DC charging station with CCS2 connector at Koramangala hub", + "beckn:longDesc": "Premium DC fast charging station supporting CCS2 connector type with 60kW maximum power output. Located at ChargeZone Koramangala Hub with restaurant, restrooms, and free Wi-Fi." + }, + "beckn:category": { + "@type": "schema:CategoryCode", + "schema:codeValue": "ev-charging", + "schema:name": "EV Charging" + }, + "beckn:availabilityWindow": [ + { + "@type": "beckn:TimePeriod", + "schema:startTime": "00:00:00", + "schema:endTime": "23:59:59" + } + ], + "beckn:rateable": true, + "beckn:rating": { + "@type": "beckn:Rating", + "beckn:ratingValue": 4.6, + "beckn:ratingCount": 214 + }, + "beckn:isActive": true, + "beckn:provider": { + "beckn:id": "chargezone-blr", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "ChargeZone Pvt Ltd" + } + }, + "beckn:itemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_resource_context.jsonld", + "@type": "EnergyResource", + "resourceType": "EV_CHARGER", + "sourceType": "GRID", + "deliveryMode": "EV_CHARGING", + "capacity": { + "installed": { + "value": 60, + "unitCode": "KW" + }, + "power": { + "min": { + "value": 5, + "unitCode": "KW" + }, + "max": { + "value": 60, + "unitCode": "KW" + } + } + }, + "connector": { + "type": "CCS2", + "format": "CABLE", + "powerType": "DC", + "chargingSpeed": "FAST" + }, + "metering": { + "evseId": "IN*CZ*KOR*01*CCS2*A", + "meterId": "IN*CZ*KOR*01*CCS2*A*METER" + }, + "capabilities": { + "reservationSupported": true, + "vehicleType": "4-WHEELER" + }, + "location": { + "stationId": "IN-CZ-KOR-STATION-01", + "geo": { + "type": "Point", + "coordinates": [77.6104, 12.9352] + }, + "address": { + "streetAddress": "ChargeZone Hub, 80 Feet Road, Koramangala 4th Block", + "addressLocality": "Bengaluru", + "addressRegion": "Karnataka", + "postalCode": "560034", + "addressCountry": "IN" + }, + "parkingType": "Mall" + }, + "amenities": ["RESTAURANT", "RESTROOM", "WI-FI", "PARKING"], + "roamingNetwork": null, + "rating": { + "value": 4.6, + "count": 214 + } + } + } + ], + "beckn:offers": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-cz-ccs2-60kw-kwh", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Per-kWh Tariff - CCS2 60kW DC Fast Charging" + }, + "beckn:items": [ + "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A" + ], + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + }, + "beckn:validity": { + "@type": "beckn:TimePeriod", + "schema:startDate": "2026-01-01T00:00:00Z", + "schema:endDate": "2026-06-30T23:59:59Z" + }, + "beckn:acceptedPaymentMethod": [ + "UPI", + "CREDIT_CARD", + "WALLET" + ], + "beckn:offerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "TEMPLATE", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "description": "EVSE identifier for the charging point", + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "station_location", + "inputType": "LOCATION", + "required": true, + "description": "Physical location of the charging station", + "value": { + "geo": { + "type": "Point", + "coordinates": [77.6104, 12.9352] + }, + "address": { + "streetAddress": "ChargeZone Hub, 80 Feet Road, Koramangala 4th Block", + "addressLocality": "Bengaluru", + "addressRegion": "Karnataka", + "postalCode": "560034", + "addressCountry": "IN" + } + }, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "description": "Per-kWh tariff rate in INR", + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "description": "Maximum power output of the EVSE in kW", + "value": 60, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "ocpp_endpoint", + "inputType": "SIGNAL", + "required": true, + "description": "OCPP websocket endpoint for charge point management", + "value": "wss://ocpp.chargezone.in/cs/IN-CZ-KOR-01", + "providedAt": "TEMPLATE" + } + ], + "obligations": [ + "delivery_obligation", + "quality_obligation" + ] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": null, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "description": "Requested energy in kWh", + "schema": { + "type": "number", + "minimum": 1, + "maximum": 100 + }, + "value": null, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "description": "Vehicle connector type compatibility", + "schema": { + "type": "string", + "enum": ["CCS2", "Type2", "CHAdeMO"] + }, + "value": null, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_start_time", + "inputType": "PARAMETER", + "required": false, + "description": "Preferred charging start time (ISO 8601)", + "value": null, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_end_time", + "inputType": "PARAMETER", + "required": false, + "description": "Preferred charging end time (ISO 8601)", + "value": null, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "description": "Buyer billing details (name, GST, contact)", + "value": null, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "description": "Starting meter reading at session begin", + "value": null, + "providedAt": "FULFILLMENT" + } + ], + "obligations": [ + "payment_obligation", + "cancellation_terms" + ] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy is charged at the per-kWh tariff rate. Total cost equals tariff rate multiplied by actual energy consumed.", + "condition": { + "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" + }, + "obligation": { + "expr": "payment.amount == inputs.tariff_rate * fulfillment.deliveredEnergy" + }, + "consequence": { + "expr": "SUSPEND_SESSION AND NOTIFY_PARTIES" + }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO must deliver requested energy within the agreed time window at the specified EVSE location.", + "condition": { + "expr": "contractState == 'ACTIVE'" + }, + "obligation": { + "expr": "fulfillment.deliveredEnergy >= inputs.requested_kwh AND fulfillment.location == inputs.station_location" + }, + "consequence": { + "expr": "REFUND_PROPORTIONAL AND LOG_SLA_BREACH" + }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "quality_obligation", + "termType": "QUALITY", + "description": "Charging power must remain within min-max range. Voltage must be within safe operating limits for CCS2.", + "condition": { + "expr": "contractState == 'ACTIVE' AND sessionStatus == 'ACTIVE'" + }, + "obligation": { + "rule": { + "operator": "AND", + "operands": [ + { + "rule": { + "operator": "GTE", + "operands": [ + { "field": "telemetry.power" }, + { "value": 5 } + ] + } + }, + { + "rule": { + "operator": "LTE", + "operands": [ + { "field": "telemetry.power" }, + { "value": 60 } + ] + } + } + ] + } + }, + "consequence": { + "expr": "NOTIFY_DRIVER AND ADJUST_SESSION_RATE" + }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "EV driver must complete payment before or at session start. Payment covers base energy cost plus applicable fees and taxes.", + "condition": { + "expr": "contractState == 'COMMITTED'" + }, + "obligation": { + "expr": "payment.status == 'COMPLETED' AND payment.amount >= orderValue.value" + }, + "consequence": { + "expr": "CANCEL_RESERVATION AND RELEASE_SLOT" + }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "cancellation_terms", + "termType": "CANCELLATION", + "description": "Free cancellation up to 15 minutes before reserved slot. Late cancellation incurs 25% fee of estimated order value.", + "condition": { + "expr": "contractState IN ['COMMITTED', 'ACTIVE'] AND cancellation.requested == true" + }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { + "expr": "cancellation.time < (fulfillment.startTime - PT15M)" + }, + { + "expr": "cancellation.fee == 0" + } + ] + } + }, + "consequence": { + "expr": "cancellation.fee == orderValue.value * 0.25" + }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "minQuantity": 1, + "maxQuantity": 100 + }, + "qualityConstraints": { + "voltageRange": { + "min": 200, + "max": 920 + }, + "powerFactor": 0.95 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "location": { + "geo": { + "type": "Point", + "coordinates": [77.6104, 12.9352] + } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"], + "signals": [ + { + "@type": "SignalSpec", + "signalType": "METER_READING", + "source": "wss://ocpp.chargezone.in/cs/IN-CZ-KOR-01", + "protocol": "OCPP", + "sampleRate": "PT30S" + }, + { + "@type": "SignalSpec", + "signalType": "START_STOP", + "source": "wss://ocpp.chargezone.in/cs/IN-CZ-KOR-01", + "protocol": "OCPP", + "sampleRate": "PT1S" + } + ] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "escrowRequired": false, + "acceptedPaymentMethods": [ + "UPI", + "CREDIT_CARD", + "WALLET" + ] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "tariff_rate * delivered_kwh" + }, + "currency": "INR", + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "base_charge * platform_fee_pct" + }, + "currency": "INR", + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { + "expr": "(base_charge + platform_fee_amount) * gst_rate" + }, + "currency": "INR", + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ], + "netZeroCheck": { + "expr": "sum(flows[*].computedAmount WHERE from == 'ev_driver') == sum(flows[*].computedAmount WHERE to != 'ev_driver')" + } + } + }, + "beckn:provider": "chargezone-blr" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/on_init.json b/examples/deg_contract/ev_charging/on_init.json new file mode 100644 index 00000000..e6c4fa25 --- /dev/null +++ b/examples/deg_contract/ev_charging/on_init.json @@ -0,0 +1,405 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_init", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-on_init-001", + "timestamp": "2026-01-15T10:32:05Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-ev-cz-001", + "beckn:orderStatus": "CREATED", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456", + "beckn:role": "BUYER", + "beckn:displayName": "Ravi Shankar", + "beckn:telephone": "+91-9845012345", + "beckn:email": "ravi.shankar@gmail.com", + "beckn:taxID": "GSTIN29AABCS1234D1Z5" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + }, + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 649.35, + "components": [ + { + "type": "UNIT", + "value": 540.0, + "currency": "INR", + "description": "Base charging cost (18 INR/kWh x 30 kWh)" + }, + { + "type": "FEE", + "value": 13.5, + "currency": "INR", + "description": "Platform fee (2.5%)" + }, + { + "type": "SURCHARGE", + "value": 99.63, + "currency": "INR", + "description": "GST @ 18%" + }, + { + "type": "FEE", + "value": 11.22, + "currency": "INR", + "description": "Overcharge buffer estimation (2%)" + }, + { + "type": "DISCOUNT", + "value": -15.0, + "currency": "INR", + "description": "New user promotional discount" + } + ] + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-cz-ev-001", + "beckn:amount": { + "currency": "INR", + "value": 649.35 + }, + "beckn:paymentURL": "https://payments.chargezone.in/pay?txn_id=txn-ev-001&amount=649.35¤cy=INR", + "beckn:txnRef": "TXN-CZ-2026011501", + "beckn:beneficiary": "BPP", + "beckn:acceptedPaymentMethod": [ + "UPI", + "CREDIT_CARD", + "WALLET" + ], + "beckn:paymentStatus": "INITIATED", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/PaymentSettlement/v1/context.jsonld", + "@type": "PaymentSettlement", + "settlementAccounts": [ + { + "beneficiaryId": "ev-bap.mobility-app.in", + "accountHolderName": "MobilityApp Solutions Pvt Ltd", + "accountNumber": "5012345678901", + "ifscCode": "ICIC0001234", + "bankName": "ICICI Bank", + "vpa": "mobilityapp@icici" + }, + { + "beneficiaryId": "chargezone-bpp.in", + "accountHolderName": "ChargeZone Energy Pvt Ltd", + "accountNumber": "1098765432101", + "ifscCode": "HDFC0005678", + "bankName": "HDFC Bank", + "vpa": "chargezone@hdfc" + } + ] + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "COMMITTED", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation", "quality_obligation"] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_start_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T11:00:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_end_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T12:30:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "value": { + "displayName": "Ravi Shankar", + "taxID": "GSTIN29AABCS1234D1Z5", + "telephone": "+91-9845012345", + "email": "ravi.shankar@gmail.com", + "billingAddress": { + "streetAddress": "42, 3rd Cross, HSR Layout Sector 2", + "addressLocality": "Bengaluru", + "addressRegion": "Karnataka", + "postalCode": "560102", + "addressCountry": "IN" + } + }, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "FULFILLMENT" + } + ], + "obligations": ["payment_obligation", "cancellation_terms"] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy charged at 18 INR/kWh. Estimated total for 30 kWh = 540 INR.", + "condition": { "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" }, + "obligation": { "expr": "payment.amount == 18.0 * fulfillment.deliveredEnergy" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO must deliver 30 kWh within the agreed time window.", + "condition": { "expr": "contractState == 'ACTIVE'" }, + "obligation": { "expr": "fulfillment.deliveredEnergy >= 30" }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "EV driver must complete payment of 649.35 INR.", + "condition": { "expr": "contractState == 'COMMITTED'" }, + "obligation": { "expr": "payment.status == 'COMPLETED' AND payment.amount >= 649.35" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "cancellation_terms", + "termType": "CANCELLATION", + "description": "Free cancellation before 2026-01-15T10:45:00Z. Late cancellation fee: 162.34 INR.", + "condition": { "expr": "cancellation.requested == true" }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { "expr": "cancellation.time < '2026-01-15T10:45:00Z'" }, + { "expr": "cancellation.fee == 0" } + ] + } + }, + "consequence": { "expr": "cancellation.fee == 162.34" }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T12:30:00Z" + }, + "location": { + "geo": { "type": "Point", "coordinates": [77.6104, 12.9352] } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { "expr": "tariff_rate * delivered_kwh" }, + "currency": "INR", + "computedAmount": 540.0, + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { "expr": "base_charge * platform_fee_pct" }, + "currency": "INR", + "computedAmount": 13.5, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { "expr": "(base_charge + platform_fee_amount) * gst_rate" }, + "currency": "INR", + "computedAmount": 99.63, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/on_select.json b/examples/deg_contract/ev_charging/on_select.json new file mode 100644 index 00000000..d2ddf6bc --- /dev/null +++ b/examples/deg_contract/ev_charging/on_select.json @@ -0,0 +1,404 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_select", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-on_select-001", + "timestamp": "2026-01-15T10:31:05Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:orderStatus": "CREATED", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456", + "beckn:role": "BUYER", + "beckn:displayName": "Ravi Shankar", + "beckn:telephone": "+91-9845012345", + "beckn:email": "ravi.shankar@gmail.com" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + }, + "beckn:acceptedOffer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-cz-ccs2-60kw-kwh", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Per-kWh Tariff - CCS2 60kW DC Fast Charging" + }, + "beckn:items": [ + "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A" + ], + "beckn:provider": "chargezone-blr", + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + }, + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 649.35, + "components": [ + { + "type": "UNIT", + "value": 540.0, + "currency": "INR", + "description": "Base charging cost (18 INR/kWh x 30 kWh)" + }, + { + "type": "FEE", + "value": 13.5, + "currency": "INR", + "description": "Platform fee (2.5%)" + }, + { + "type": "SURCHARGE", + "value": 99.63, + "currency": "INR", + "description": "GST @ 18% on (base + platform fee)" + }, + { + "type": "FEE", + "value": 11.22, + "currency": "INR", + "description": "Overcharge buffer estimation (2%)" + }, + { + "type": "DISCOUNT", + "value": -15.0, + "currency": "INR", + "description": "New user promotional discount" + } + ] + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "NEGOTIATED", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + } + ], + "obligations": [ + "delivery_obligation", + "quality_obligation" + ] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_start_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T11:00:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_end_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T12:30:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "FULFILLMENT" + } + ], + "obligations": [ + "payment_obligation", + "cancellation_terms" + ] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy is charged at 18 INR/kWh. Estimated total for 30 kWh = 540 INR.", + "condition": { + "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" + }, + "obligation": { + "expr": "payment.amount == 18.0 * fulfillment.deliveredEnergy" + }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO must deliver 30 kWh within 2026-01-15T11:00 to 2026-01-15T12:30.", + "condition": { + "expr": "contractState == 'ACTIVE'" + }, + "obligation": { + "expr": "fulfillment.deliveredEnergy >= 30" + }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "EV driver must complete payment of 649.35 INR before session start.", + "condition": { + "expr": "contractState == 'COMMITTED'" + }, + "obligation": { + "expr": "payment.status == 'COMPLETED' AND payment.amount >= 649.35" + }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "cancellation_terms", + "termType": "CANCELLATION", + "description": "Free cancellation before 2026-01-15T10:45:00Z. Late cancellation incurs 162.34 INR (25%).", + "condition": { + "expr": "cancellation.requested == true" + }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { "expr": "cancellation.time < '2026-01-15T10:45:00Z'" }, + { "expr": "cancellation.fee == 0" } + ] + } + }, + "consequence": { + "expr": "cancellation.fee == 162.34" + }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T12:30:00Z" + }, + "location": { + "geo": { + "type": "Point", + "coordinates": [77.6104, 12.9352] + } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "tariff_rate * delivered_kwh" + }, + "currency": "INR", + "computedAmount": 540.0, + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "base_charge * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": 13.5, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { + "expr": "(base_charge + platform_fee_amount) * gst_rate" + }, + "currency": "INR", + "computedAmount": 99.63, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/on_status.json b/examples/deg_contract/ev_charging/on_status.json new file mode 100644 index 00000000..3473512b --- /dev/null +++ b/examples/deg_contract/ev_charging/on_status.json @@ -0,0 +1,362 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_status", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-on_status-001", + "timestamp": "2026-01-15T11:15:00Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-ev-cz-001", + "beckn:orderStatus": "INPROGRESS", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A" + } + ], + "beckn:fulfillment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-cz-001", + "beckn:mode": "RESERVATION", + "trackingAction": { + "@type": "beckn:TrackAction", + "target": { + "@type": "schema:EntryPoint", + "url": "https://track.chargezone.in/session/SESSION-CZ-2026011501" + } + }, + "beckn:deliveryAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "ACTIVE", + "deliveryMode": "EV_CHARGING", + "energyFlow": { + "direction": "TO_VEHICLE", + "delivered": { + "value": 9.4, + "unitCode": "KWH" + } + }, + "timing": { + "startTime": "2026-01-15T11:00:00Z", + "elapsed": "PT15M" + }, + "connector": { + "type": "CCS2", + "maxPowerKW": 60 + }, + "telemetry": [ + { + "eventTime": "2026-01-15T11:10:00Z", + "metrics": [ + { + "name": "STATE_OF_CHARGE", + "value": 42.0, + "unitCode": "PERCENTAGE" + }, + { + "name": "POWER", + "value": 52.3, + "unitCode": "KW" + }, + { + "name": "ENERGY", + "value": 4.8, + "unitCode": "KWH" + }, + { + "name": "VOLTAGE", + "value": 412, + "unitCode": "VLT" + }, + { + "name": "CURRENT", + "value": 127.0, + "unitCode": "AMP" + } + ] + }, + { + "eventTime": "2026-01-15T11:15:00Z", + "metrics": [ + { + "name": "STATE_OF_CHARGE", + "value": 48.5, + "unitCode": "PERCENTAGE" + }, + { + "name": "POWER", + "value": 55.1, + "unitCode": "KW" + }, + { + "name": "ENERGY", + "value": 9.4, + "unitCode": "KWH" + }, + { + "name": "VOLTAGE", + "value": 418, + "unitCode": "VLT" + }, + { + "name": "CURRENT", + "value": 131.8, + "unitCode": "AMP" + } + ] + } + ], + "lastUpdated": "2026-01-15T11:15:00Z" + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "ACTIVE", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation", "quality_obligation"] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": 15230.4, + "providedAt": "FULFILLMENT" + } + ], + "obligations": ["payment_obligation"] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy charged at 18 INR/kWh. Current consumed: 9.4 kWh. Running cost: 169.20 INR.", + "condition": { "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" }, + "obligation": { "expr": "payment.amount == 18.0 * fulfillment.deliveredEnergy" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO delivering energy. Progress: 9.4 / 30 kWh (31.3%).", + "condition": { "expr": "contractState == 'ACTIVE'" }, + "obligation": { "expr": "fulfillment.deliveredEnergy >= 30" }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "quality_obligation", + "termType": "QUALITY", + "description": "Power output within range. Current: 55.1 kW (max 60 kW). Voltage: 418V within safe range.", + "condition": { "expr": "contractState == 'ACTIVE' AND sessionStatus == 'ACTIVE'" }, + "obligation": { + "rule": { + "operator": "AND", + "operands": [ + { + "rule": { + "operator": "BETWEEN", + "operands": [ + { "field": "telemetry.power" }, + { "value": 5 }, + { "value": 60 } + ] + } + }, + { + "rule": { + "operator": "BETWEEN", + "operands": [ + { "field": "telemetry.voltage" }, + { "value": 200 }, + { "value": 920 } + ] + } + } + ] + } + }, + "appliesTo": ["cpo"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T12:30:00Z" + }, + "location": { + "geo": { "type": "Point", "coordinates": [77.6104, 12.9352] } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"], + "signals": [ + { + "@type": "SignalSpec", + "signalType": "METER_READING", + "source": "wss://ocpp.chargezone.in/cs/IN-CZ-KOR-01", + "protocol": "OCPP", + "sampleRate": "PT30S" + } + ] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { "expr": "tariff_rate * delivered_kwh" }, + "currency": "INR", + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { "expr": "base_charge * platform_fee_pct" }, + "currency": "INR", + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { "expr": "(base_charge + platform_fee_amount) * gst_rate" }, + "currency": "INR", + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/on_update_complete.json b/examples/deg_contract/ev_charging/on_update_complete.json new file mode 100644 index 00000000..954f6f8c --- /dev/null +++ b/examples/deg_contract/ev_charging/on_update_complete.json @@ -0,0 +1,564 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_update", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-on_update-001", + "timestamp": "2026-01-15T11:48:00Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-ev-cz-001", + "beckn:orderStatus": "COMPLETED", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456", + "beckn:role": "BUYER", + "beckn:displayName": "Ravi Shankar", + "beckn:telephone": "+91-9845012345", + "beckn:email": "ravi.shankar@gmail.com", + "beckn:taxID": "GSTIN29AABCS1234D1Z5" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A" + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 637.16, + "components": [ + { + "type": "UNIT", + "value": 529.2, + "currency": "INR", + "description": "Actual charging cost (18 INR/kWh x 29.4 kWh delivered)" + }, + { + "type": "FEE", + "value": 13.23, + "currency": "INR", + "description": "Platform fee (2.5% of base)" + }, + { + "type": "SURCHARGE", + "value": 97.64, + "currency": "INR", + "description": "GST @ 18% on (base + platform fee)" + }, + { + "type": "DISCOUNT", + "value": -15.0, + "currency": "INR", + "description": "New user promotional discount" + }, + { + "type": "FEE", + "value": 12.09, + "currency": "INR", + "description": "Idle fee (2.5 INR per 10 min x ~48 min idle after session)" + } + ] + }, + "beckn:fulfillment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-cz-001", + "beckn:mode": "RESERVATION", + "beckn:deliveryAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "COMPLETED", + "deliveryMode": "EV_CHARGING", + "energyFlow": { + "direction": "TO_VEHICLE", + "delivered": { + "value": 29.4, + "unitCode": "KWH" + } + }, + "timing": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T11:48:00Z", + "elapsed": "PT48M" + }, + "connector": { + "type": "CCS2", + "maxPowerKW": 60 + }, + "telemetry": [ + { + "eventTime": "2026-01-15T11:00:00Z", + "metrics": [ + { + "name": "STATE_OF_CHARGE", + "value": 35.0, + "unitCode": "PERCENTAGE" + }, + { + "name": "POWER", + "value": 0.0, + "unitCode": "KW" + }, + { + "name": "ENERGY", + "value": 0.0, + "unitCode": "KWH" + } + ] + }, + { + "eventTime": "2026-01-15T11:15:00Z", + "metrics": [ + { + "name": "STATE_OF_CHARGE", + "value": 48.5, + "unitCode": "PERCENTAGE" + }, + { + "name": "POWER", + "value": 55.1, + "unitCode": "KW" + }, + { + "name": "ENERGY", + "value": 9.4, + "unitCode": "KWH" + } + ] + }, + { + "eventTime": "2026-01-15T11:30:00Z", + "metrics": [ + { + "name": "STATE_OF_CHARGE", + "value": 65.2, + "unitCode": "PERCENTAGE" + }, + { + "name": "POWER", + "value": 48.7, + "unitCode": "KW" + }, + { + "name": "ENERGY", + "value": 20.1, + "unitCode": "KWH" + } + ] + }, + { + "eventTime": "2026-01-15T11:45:00Z", + "metrics": [ + { + "name": "STATE_OF_CHARGE", + "value": 78.8, + "unitCode": "PERCENTAGE" + }, + { + "name": "POWER", + "value": 32.4, + "unitCode": "KW" + }, + { + "name": "ENERGY", + "value": 29.4, + "unitCode": "KWH" + } + ] + }, + { + "eventTime": "2026-01-15T11:48:00Z", + "metrics": [ + { + "name": "STATE_OF_CHARGE", + "value": 80.0, + "unitCode": "PERCENTAGE" + }, + { + "name": "POWER", + "value": 0.0, + "unitCode": "KW" + }, + { + "name": "ENERGY", + "value": 29.4, + "unitCode": "KWH" + } + ] + } + ], + "lastUpdated": "2026-01-15T11:48:00Z" + } + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-cz-ev-001", + "beckn:amount": { + "currency": "INR", + "value": 649.35 + }, + "beckn:paymentURL": "https://payments.chargezone.in/pay?txn_id=txn-ev-001&amount=649.35¤cy=INR", + "beckn:txnRef": "TXN-CZ-2026011501", + "beckn:paidAt": "2026-01-15T10:32:45Z", + "beckn:beneficiary": "BPP", + "beckn:paymentStatus": "COMPLETED", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/PaymentSettlement/v1/context.jsonld", + "@type": "PaymentSettlement", + "settlementAccounts": [ + { + "beneficiaryId": "ev-bap.mobility-app.in", + "accountHolderName": "MobilityApp Solutions Pvt Ltd", + "accountNumber": "5012345678901", + "ifscCode": "ICIC0001234", + "bankName": "ICICI Bank", + "vpa": "mobilityapp@icici" + }, + { + "beneficiaryId": "chargezone-bpp.in", + "accountHolderName": "ChargeZone Energy Pvt Ltd", + "accountNumber": "1098765432101", + "ifscCode": "HDFC0005678", + "bankName": "HDFC Bank", + "vpa": "chargezone@hdfc" + } + ], + "reconciliation": { + "prepaidAmount": 649.35, + "actualAmount": 637.16, + "refundDue": 12.19, + "refundStatus": "INITIATED", + "refundTxnRef": "RFND-CZ-2026011501" + } + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "SETTLED", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation", "quality_obligation"] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "value": { + "displayName": "Ravi Shankar", + "taxID": "GSTIN29AABCS1234D1Z5" + }, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": 15230.4, + "providedAt": "FULFILLMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_end", + "inputType": "METER_ID", + "required": true, + "value": 15259.8, + "providedAt": "FULFILLMENT" + } + ], + "obligations": ["payment_obligation"] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy charged at 18 INR/kWh. Actual consumed: 29.4 kWh. Final base cost: 529.20 INR.", + "condition": { "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" }, + "obligation": { "expr": "payment.amount == 18.0 * 29.4" }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO delivered 29.4 kWh of requested 30 kWh (98%). Vehicle reached target SoC of 80% - session ended by driver.", + "condition": { "expr": "contractState == 'ACTIVE'" }, + "obligation": { "expr": "fulfillment.deliveredEnergy >= 30 OR session.endedByDriver == true" }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "Payment completed. Prepaid 649.35 INR. Actual cost 637.16 INR. Refund of 12.19 INR initiated.", + "condition": { "expr": "contractState == 'SETTLED'" }, + "obligation": { "expr": "payment.status == 'COMPLETED' AND refund.status == 'INITIATED'" }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 29.4 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T11:48:00Z" + }, + "location": { + "geo": { "type": "Point", "coordinates": [77.6104, 12.9352] } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh", + "resolvedValue": 18.0 + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh", + "resolvedValue": 29.4 + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR", + "resolvedValue": 529.2 + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction", + "resolvedValue": 0.025 + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR", + "resolvedValue": 13.23 + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction", + "resolvedValue": 0.18 + }, + { + "term": "idle_fee_rate", + "description": "Idle fee per 10-minute block", + "source": "constant", + "value": 2.5, + "unit": "INR/block", + "resolvedValue": 2.5 + }, + { + "term": "idle_block_size", + "description": "Idle fee block size", + "source": "constant", + "value": 10, + "unit": "minutes", + "resolvedValue": 10 + }, + { + "term": "idle_minutes", + "description": "Minutes vehicle remained idle after charging", + "source": "delivery:timing.idleMinutes", + "unit": "minutes", + "resolvedValue": 48.36 + }, + { + "term": "prepaid_amount", + "description": "Pre-order payment amount collected", + "source": "payment:prepaidAmount", + "unit": "INR", + "resolvedValue": 649.35 + }, + { + "term": "actual_total", + "description": "Sum of all charges (base + platform + GST + idle)", + "source": "flow:total_charges", + "unit": "INR", + "resolvedValue": 637.16 + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { "expr": "tariff_rate * delivered_kwh" }, + "currency": "INR", + "computedAmount": 529.2, + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { "expr": "base_charge * platform_fee_pct" }, + "currency": "INR", + "computedAmount": 13.23, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { "expr": "(base_charge + platform_fee_amount) * gst_rate" }, + "currency": "INR", + "computedAmount": 97.64, + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "idle_fee", + "from": "ev_driver", + "to": "cpo", + "flowType": "GRID_SERVICE_FEE", + "formula": { "expr": "(idle_minutes / idle_block_size) * idle_fee_rate" }, + "currency": "INR", + "computedAmount": 12.09, + "triggeredBy": "delivery_obligation", + "schedule": "ON_SETTLEMENT" + }, + { + "@type": "RevenueFlow", + "flowId": "refund_overcharge", + "from": "cpo", + "to": "ev_driver", + "flowType": "CREDIT", + "formula": { "expr": "prepaid_amount - actual_total" }, + "currency": "INR", + "computedAmount": 12.19, + "triggeredBy": "payment_obligation", + "schedule": "ON_SETTLEMENT" + } + ], + "netPositions": { + "ev_driver": { "net": -639.97, "direction": "PAYS" }, + "cpo": { "net": 529.10, "direction": "RECEIVES" }, + "platform": { "net": 13.23, "direction": "RECEIVES" }, + "tax_authority": { "net": 97.64, "direction": "RECEIVES" } + }, + "netZeroCheck": { "sum": 0.00, "valid": true } + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/ev_charging/select.json b/examples/deg_contract/ev_charging/select.json new file mode 100644 index 00000000..bda13b16 --- /dev/null +++ b/examples/deg_contract/ev_charging/select.json @@ -0,0 +1,368 @@ +{ + "context": { + "version": "2.0.0", + "action": "select", + "domain": "beckn.one:deg:ev-charging:2.0.0", + "bap_id": "ev-bap.mobility-app.in", + "bap_uri": "https://ev-bap.mobility-app.in/beckn/v2", + "bpp_id": "chargezone-bpp.in", + "bpp_uri": "https://api.chargezone-bpp.in/beckn/v2", + "transaction_id": "txn-ev-001", + "message_id": "msg-select-001", + "timestamp": "2026-01-15T10:31:00Z", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:orderStatus": "CREATED", + "beckn:seller": "chargezone-blr", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "user-ravi-456", + "beckn:role": "BUYER", + "beckn:displayName": "Ravi Shankar", + "beckn:telephone": "+91-9845012345", + "beckn:email": "ravi.shankar@gmail.com" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": null, + "makeModel": null, + "batteryKwh": null, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + }, + "beckn:acceptedOffer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-cz-ccs2-60kw-kwh", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Per-kWh Tariff - CCS2 60kW DC Fast Charging" + }, + "beckn:items": [ + "IND*chargezone*cs-blr-01*IN*CZ*KOR*01*CCS2*A" + ], + "beckn:provider": "chargezone-blr", + "beckn:price": { + "currency": "INR", + "value": 18.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "EV_CHARGING", + "version": "1.0.0", + "contractState": "NEGOTIATED", + "roles": [ + { + "@type": "DEGRole", + "roleId": "cpo", + "roleType": "CPO", + "party": { + "partyId": "chargezone-blr", + "partyType": "PROVIDER", + "platformId": "chargezone-bpp.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "evse_id", + "inputType": "DER_ID", + "required": true, + "value": "IN*CZ*KOR*01*CCS2*A", + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "tariff_rate", + "inputType": "PARAMETER", + "required": true, + "value": 18.0, + "providedAt": "TEMPLATE" + }, + { + "@type": "RoleInput", + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + } + ], + "obligations": [ + "delivery_obligation", + "quality_obligation" + ] + }, + { + "@type": "DEGRole", + "roleId": "ev_driver", + "roleType": "CONSUMER", + "party": { + "partyId": "user-ravi-456", + "partyType": "BUYER", + "platformId": "ev-bap.mobility-app.in" + }, + "inputs": [ + { + "@type": "RoleInput", + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "description": "Requested energy in kWh", + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "vehicle_connector", + "inputType": "PARAMETER", + "required": true, + "description": "Vehicle connector type compatibility", + "value": "CCS2", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_start_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T11:00:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "preferred_end_time", + "inputType": "PARAMETER", + "required": false, + "value": "2026-01-15T12:30:00Z", + "providedAt": "NEGOTIATION" + }, + { + "@type": "RoleInput", + "inputId": "billing_info", + "inputType": "CREDENTIAL", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + }, + { + "@type": "RoleInput", + "inputId": "meter_reading_start", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "FULFILLMENT" + } + ], + "obligations": [ + "payment_obligation", + "cancellation_terms" + ] + } + ], + "terms": [ + { + "@type": "DEGTerm", + "termId": "pricing_term", + "termType": "PAYMENT", + "description": "Energy is charged at the per-kWh tariff rate. Total cost equals tariff rate multiplied by actual energy consumed.", + "condition": { + "expr": "contractState == 'ACTIVE' AND deliveredEnergy > 0" + }, + "obligation": { + "expr": "payment.amount == inputs.tariff_rate * fulfillment.deliveredEnergy" + }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "delivery_obligation", + "termType": "DELIVERY", + "description": "CPO must deliver requested energy within the agreed time window.", + "condition": { + "expr": "contractState == 'ACTIVE'" + }, + "obligation": { + "expr": "fulfillment.deliveredEnergy >= inputs.requested_kwh" + }, + "appliesTo": ["cpo"] + }, + { + "@type": "DEGTerm", + "termId": "payment_obligation", + "termType": "PAYMENT", + "description": "EV driver must complete payment before session start.", + "condition": { + "expr": "contractState == 'COMMITTED'" + }, + "obligation": { + "expr": "payment.status == 'COMPLETED'" + }, + "appliesTo": ["ev_driver"] + }, + { + "@type": "DEGTerm", + "termId": "cancellation_terms", + "termType": "CANCELLATION", + "description": "Free cancellation up to 15 minutes before reserved slot. Late cancellation incurs 25% fee.", + "condition": { + "expr": "cancellation.requested == true" + }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { "expr": "cancellation.time < (fulfillment.startTime - PT15M)" }, + { "expr": "cancellation.fee == 0" } + ] + } + }, + "consequence": { + "expr": "cancellation.fee == orderValue.value * 0.25" + }, + "appliesTo": ["ev_driver"] + } + ], + "energySpec": { + "@type": "EnergySpecification", + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 30 + } + }, + "fulfillmentSpec": { + "@type": "FulfillmentSpecification", + "deliveryMode": "EV_CHARGING", + "deliveryWindow": { + "startTime": "2026-01-15T11:00:00Z", + "endTime": "2026-01-15T12:30:00Z" + }, + "location": { + "geo": { + "type": "Point", + "coordinates": [77.6104, 12.9352] + } + }, + "meterIds": ["IN*CZ*KOR*01*CCS2*A*METER"] + }, + "settlementSpec": { + "@type": "SettlementSpecification", + "paymentType": "PRE_ORDER", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "@type": "RevenueModel", + "definitions": [ + { + "term": "tariff_rate", + "description": "Per-unit energy tariff rate charged by CPO", + "source": "role:cpo.input:tariff_rate", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy metered and delivered to vehicle", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "base_charge", + "description": "Computed energy payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate", + "source": "constant", + "value": 0.025, + "unit": "fraction" + }, + { + "term": "platform_fee_amount", + "description": "Computed platform fee", + "source": "flow:platform_fee", + "unit": "INR" + }, + { + "term": "gst_rate", + "description": "Goods and Services Tax rate", + "source": "constant", + "value": 0.18, + "unit": "fraction" + } + ], + "flows": [ + { + "@type": "RevenueFlow", + "flowId": "charging_payment", + "from": "ev_driver", + "to": "cpo", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "tariff_rate * delivered_kwh" + }, + "currency": "INR", + "triggeredBy": "pricing_term", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "platform_fee", + "from": "ev_driver", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "base_charge * platform_fee_pct" + }, + "currency": "INR", + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + }, + { + "@type": "RevenueFlow", + "flowId": "gst_tax", + "from": "ev_driver", + "to": "tax_authority", + "flowType": "TAX", + "formula": { + "expr": "(base_charge + platform_fee_amount) * gst_rate" + }, + "currency": "INR", + "triggeredBy": "payment_obligation", + "schedule": "ON_DELIVERY" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/cascaded_init.json b/examples/deg_contract/p2p-trading-interdiscom/cascaded_init.json new file mode 100644 index 00000000..5a3fd15c --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/cascaded_init.json @@ -0,0 +1,206 @@ +{ + "context": { + "version": "2.0.0", + "action": "init", + "timestamp": "2025-06-15T06:40:10Z", + "message_id": "msg-cascaded-init-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bpp.solarfarm-delhi.in", + "bap_uri": "https://bpp.solarfarm-delhi.in/cascaded/bap", + "bpp_id": "bpp.tpddl.gov.in", + "bpp_uri": "https://bpp.tpddl.gov.in/pilot/bpp/energy/v2", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0", + "location": { + "city": { + "code": "DL", + "name": "Delhi" + }, + "country": { + "code": "IND", + "name": "India" + } + } + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-cascaded-utility-interdiscom-001", + "beckn:provider": { + "@type": "beckn:Provider", + "beckn:id": "provider-tpddl-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "TPDDL - Tata Power Delhi Distribution Limited" + }, + "beckn:providerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_provider_context.jsonld", + "@type": "EnergyProvider", + "operatorName": "TPDDL - Tata Power Delhi Distribution Limited", + "identifier": "TPDDL-DL", + "gridAccount": { + "utilityId": "TPDDL-DL", + "connectionType": "UTILITY_GRID" + }, + "registration": { + "licenseNumber": "DERC/2004/001" + } + } + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-utility-registration-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY", + "beckn:customer": { + "@type": "beckn:Customer", + "beckn:person": { + "@type": "schema:Person", + "schema:name": "Delhi Solar Prosumer" + }, + "beckn:contact": { + "@type": "schema:ContactPoint", + "schema:telephone": "+91-11-27654321" + } + }, + "beckn:stops": [ + { + "@type": "beckn:Stop", + "beckn:id": "stop-source-utility-001", + "beckn:type": "START", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/100200300", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Source Meter - TPDDL Delhi" + } + }, + "beckn:time": { + "@type": "beckn:Time", + "beckn:range": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + } + } + }, + { + "@type": "beckn:Stop", + "beckn:id": "stop-target-utility-001", + "beckn:type": "END", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/98765456", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Destination Meter - BESCOM Bangalore" + } + }, + "beckn:time": { + "@type": "beckn:Time", + "beckn:range": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + } + } + } + ] + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "COMMITTED", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + } + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": { + "partyId": "buyer-bangalore-001", + "partyType": "BUYER", + "platformId": "bap.energy-buyer.in" + } + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": { + "partyId": "BESCOM-KA", + "partyType": "PROVIDER", + "platformId": "bpp.bescom.gov.in" + }, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "unitQuantity": 20.0 + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "meterIds": [ + "der://meter/100200300", + "der://meter/98765456" + ], + "interDiscom": true, + "sourceDiscom": "TPDDL-DL", + "destinationDiscom": "BESCOM-KA" + } + }, + "beckn:billing": { + "@type": "beckn:Billing", + "beckn:name": "P2P Trading Platform (acting as BAP)", + "beckn:email": "utility-ops@solarfarm-delhi.in", + "beckn:phone": "+91-11-27654321" + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/cascaded_on_init.json b/examples/deg_contract/p2p-trading-interdiscom/cascaded_on_init.json new file mode 100644 index 00000000..ee8ef8cf --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/cascaded_on_init.json @@ -0,0 +1,222 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_init", + "timestamp": "2025-06-15T06:40:15Z", + "message_id": "msg-cascaded-on-init-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bpp.solarfarm-delhi.in", + "bap_uri": "https://bpp.solarfarm-delhi.in/cascaded/bap", + "bpp_id": "bpp.tpddl.gov.in", + "bpp_uri": "https://bpp.tpddl.gov.in/pilot/bpp/energy/v2", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-cascaded-utility-interdiscom-001", + "beckn:provider": { + "@type": "beckn:Provider", + "beckn:id": "provider-tpddl-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "TPDDL - Tata Power Delhi Distribution Limited" + }, + "beckn:providerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_provider_context.jsonld", + "@type": "EnergyProvider", + "operatorName": "TPDDL - Tata Power Delhi Distribution Limited", + "identifier": "TPDDL-DL", + "gridAccount": { + "utilityId": "TPDDL-DL", + "connectionType": "UTILITY_GRID" + }, + "registration": { + "licenseNumber": "DERC/2004/001" + } + } + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-utility-registration-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY", + "beckn:customer": { + "@type": "beckn:Customer", + "beckn:person": { + "@type": "schema:Person", + "schema:name": "Delhi Solar Prosumer" + }, + "beckn:contact": { + "@type": "schema:ContactPoint", + "schema:telephone": "+91-11-27654321" + } + }, + "beckn:stops": [ + { + "@type": "beckn:Stop", + "beckn:id": "stop-source-utility-001", + "beckn:type": "START", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/100200300" + } + }, + { + "@type": "beckn:Stop", + "beckn:id": "stop-target-utility-001", + "beckn:type": "END", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/98765456" + } + } + ] + } + ], + "beckn:quote": { + "@type": "beckn:Quotation", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 10.00, + "schema:priceCurrency": "INR", + "schema:description": "Total inter-DISCOM wheeling charges for 20 kWh" + }, + "beckn:breakup": [ + { + "@type": "beckn:Breakup", + "beckn:title": "TPDDL Source Wheeling Charge (20 kWh @ INR 0.25/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 5.00, + "schema:priceCurrency": "INR" + } + }, + { + "@type": "beckn:Breakup", + "beckn:title": "BESCOM Destination Wheeling Charge (20 kWh @ INR 0.25/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 5.00, + "schema:priceCurrency": "INR" + } + } + ] + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "COMMITTED", + "roles": [ + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": 0.25, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": { + "partyId": "BESCOM-KA", + "partyType": "PROVIDER", + "platformId": "bpp.bescom.gov.in" + }, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": 0.25, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ], + "wheelingDetails": { + "sourceDiscom": { + "discomId": "TPDDL-DL", + "wheelingRatePerKwh": 0.25, + "currency": "INR", + "openAccessApproved": true, + "approvalRef": "TPDDL/OA/2025/06/00789" + }, + "destinationDiscom": { + "discomId": "BESCOM-KA", + "wheelingRatePerKwh": 0.25, + "currency": "INR", + "openAccessApproved": true, + "approvalRef": "BESCOM/OA/2025/06/01234" + }, + "transmissionCharge": { + "chargePerKwh": 0.00, + "currency": "INR", + "description": "Inter-state transmission via ISTS waived for solar under Green Energy Open Access Rules 2022" + } + }, + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "unitQuantity": 20.0 + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "meterIds": [ + "der://meter/100200300", + "der://meter/98765456" + ], + "interDiscom": true, + "sourceDiscom": "TPDDL-DL", + "destinationDiscom": "BESCOM-KA" + } + }, + "beckn:billing": { + "@type": "beckn:Billing", + "beckn:name": "P2P Trading Platform (acting as BAP)", + "beckn:email": "utility-ops@solarfarm-delhi.in", + "beckn:phone": "+91-11-27654321" + }, + "beckn:cancellationTerms": [ + { + "@type": "beckn:CancellationTerm", + "beckn:externalRef": { + "@type": "schema:MediaObject", + "schema:encodingFormat": "text/html", + "schema:contentUrl": "https://tpddl.com/open-access/cancellation-terms.html" + }, + "beckn:cancellationFee": { + "@type": "schema:PriceSpecification", + "schema:price": 0.0, + "schema:priceCurrency": "INR", + "schema:description": "No cancellation fee if cancelled 1 hour before delivery window" + } + } + ] + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/confirm.json b/examples/deg_contract/p2p-trading-interdiscom/confirm.json new file mode 100644 index 00000000..d49da6b7 --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/confirm.json @@ -0,0 +1,87 @@ +{ + "context": { + "version": "2.0.0", + "action": "confirm", + "timestamp": "2025-06-15T06:45:00Z", + "message_id": "msg-confirm-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY" + } + ], + "beckn:payments": [ + { + "@type": "beckn:Payment", + "beckn:id": "payment-interdiscom-001", + "beckn:type": "ON-FULFILLMENT", + "beckn:status": "NOT-PAID", + "beckn:collected_by": "BPP", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/PaymentSettlement/v0.2/context.jsonld", + "@type": "PaymentSettlement", + "paymentAttributes": { + "settlementAccounts": [ + { + "accountType": "UPI", + "accountId": "priya.sharma@okicici", + "accountHolder": "Priya Sharma", + "role": "buyer" + } + ] + } + } + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "ACTIVE", + "confirmation": { + "buyerConfirmed": true, + "buyerConfirmedAt": "2025-06-15T06:45:00Z", + "sellerConfirmed": true, + "sellerConfirmedAt": "2025-06-15T06:40:05Z", + "utilityApproved": true, + "utilityApprovedAt": "2025-06-15T06:40:15Z" + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/init.json b/examples/deg_contract/p2p-trading-interdiscom/init.json new file mode 100644 index 00000000..e254a1a7 --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/init.json @@ -0,0 +1,493 @@ +{ + "context": { + "version": "2.0.0", + "action": "init", + "timestamp": "2025-06-15T06:40:00Z", + "message_id": "msg-init-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:buyer": { + "@type": "beckn:Buyer", + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "gridAccount": { + "meterId": "der://meter/98765456", + "utilityId": "BESCOM-KA", + "utilityCustomerId": "BESCOM-2024-CON-08832", + "sanctionedLoad": 5.0, + "connectionType": "LT-DOMESTIC" + } + } + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY", + "beckn:customer": { + "@type": "beckn:Customer", + "beckn:person": { + "@type": "schema:Person", + "schema:name": "Priya Sharma" + }, + "beckn:contact": { + "@type": "schema:ContactPoint", + "schema:telephone": "+91-9876543210", + "schema:email": "priya.sharma@example.com" + } + }, + "beckn:stops": [ + { + "@type": "beckn:Stop", + "beckn:id": "stop-source-001", + "beckn:type": "START", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/100200300", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Solar Farm - South Delhi (TPDDL)" + } + }, + "beckn:time": { + "@type": "beckn:Time", + "beckn:range": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + } + } + }, + { + "@type": "beckn:Stop", + "beckn:id": "stop-destination-001", + "beckn:type": "END", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/98765456", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Consumer - Koramangala, Bangalore (BESCOM)" + } + }, + "beckn:time": { + "@type": "beckn:Time", + "beckn:range": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + } + } + } + ] + } + ], + "beckn:payments": [ + { + "@type": "beckn:Payment", + "beckn:id": "payment-interdiscom-001", + "beckn:type": "ON-FULFILLMENT", + "beckn:status": "NOT-PAID", + "beckn:collected_by": "BPP", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/PaymentSettlement/v0.2/context.jsonld", + "@type": "PaymentSettlement", + "paymentAttributes": { + "settlementAccounts": [ + { + "accountType": "UPI", + "accountId": "priya.sharma@okicici", + "accountHolder": "Priya Sharma", + "role": "buyer" + } + ] + } + } + } + ], + "beckn:billing": { + "@type": "beckn:Billing", + "beckn:name": "Priya Sharma", + "beckn:email": "priya.sharma@example.com", + "beckn:phone": "+91-9876543210", + "beckn:address": "42, 3rd Cross, Koramangala, Bangalore 560034" + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "COMMITTED", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + }, + "inputs": [ + { + "inputId": "source_meter", + "inputType": "METER_ID", + "value": "der://meter/100200300", + "providedAt": "TEMPLATE" + }, + { + "inputId": "energy_source", + "inputType": "PARAMETER", + "value": "SOLAR", + "providedAt": "TEMPLATE" + }, + { + "inputId": "available_kwh", + "inputType": "PARAMETER", + "value": 25.0, + "providedAt": "TEMPLATE" + }, + { + "inputId": "source_utility", + "inputType": "PARAMETER", + "value": "TPDDL-DL", + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation"] + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": { + "partyId": "buyer-bangalore-001", + "partyType": "BUYER", + "platformId": "bap.energy-buyer.in" + }, + "inputs": [ + { + "inputId": "destination_meter", + "inputType": "METER_ID", + "required": true, + "value": "der://meter/98765456", + "providedAt": "NEGOTIATION" + }, + { + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 20.0, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "buyer_utility_id", + "inputType": "PARAMETER", + "required": true, + "value": "BESCOM-KA", + "providedAt": "COMMITMENT" + }, + { + "inputId": "buyer_utility_customer_id", + "inputType": "PARAMETER", + "required": true, + "value": "BESCOM-2024-CON-08832", + "providedAt": "COMMITMENT" + } + ], + "obligations": ["pricing", "payment_obligation"] + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": { + "partyId": "BESCOM-KA", + "partyType": "PROVIDER", + "platformId": "bpp.bescom.gov.in" + }, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ], + "terms": [ + { + "termId": "delivery_obligation", + "termType": "DELIVERY", + "condition": { + "expr": "currentTime WITHIN deliveryWindow" + }, + "obligation": { + "rule": { + "operator": "AND", + "operands": [ + { + "field": "deliveredQuantity", + "operator": "GTE", + "value": "requestedKwh * 0.9" + }, + { + "field": "deliveredQuantity", + "operator": "LTE", + "value": "requestedKwh * 1.1" + } + ] + } + }, + "consequence": { + "expr": "deviationPenalty = abs(deliveredQuantity - requestedKwh) * 1.50" + }, + "appliesTo": ["seller"] + }, + { + "termId": "pricing", + "termType": "PAYMENT", + "condition": { + "expr": "true" + }, + "obligation": { + "expr": "payment = deliveredQuantity * pricePerKwh + totalWheelingCharge + platformFee" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "utility_registration", + "termType": "QUALITY", + "condition": { + "expr": "contractState == COMMITTED" + }, + "obligation": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "open_access_check" + } + }, + "appliesTo": ["source_utility", "destination_utility"] + }, + { + "termId": "payment_obligation", + "termType": "PAYMENT", + "condition": { + "expr": "deliveryStatus == COMPLETED" + }, + "obligation": { + "expr": "settlePayment WITHIN 24 hours OF deliveryCompletion" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "cancellation", + "termType": "CANCELLATION", + "condition": { + "expr": "contractState IN [COMMITTED, ACTIVE] AND cancellationRequestTime < deliveryWindow.start - PT1H" + }, + "consequence": { + "expr": "cancellationFee = 0" + }, + "appliesTo": ["buyer", "seller"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "unitQuantity": 20.0 + }, + "qualityConstraints": { + "greenCertRequired": true + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "meterIds": [ + "der://meter/100200300", + "der://meter/98765456" + ], + "signals": [] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "DAILY" + }, + "revenueModel": { + "definitions": [ + { + "term": "price_per_kwh", + "description": "Per-kWh energy price set by seller", + "source": "role:seller.input:price_per_kwh", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy delivered via grid injection", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "source_wheeling_rate", + "description": "Wheeling charge rate from source DISCOM", + "source": "role:source_utility.input:source_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "dest_wheeling_rate", + "description": "Wheeling charge rate from destination DISCOM", + "source": "role:destination_utility.input:destination_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "energy_payment_amount", + "description": "Computed energy payment from buyer to seller", + "source": "flow:energy_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate (bilateral)", + "source": "constant", + "value": 0.01, + "unit": "fraction" + } + ], + "flows": [ + { + "flowId": "energy_payment", + "from": "buyer", + "to": "seller", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "delivered_kwh * price_per_kwh" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "pricing" + }, + { + "flowId": "wheeling_charge", + "from": "buyer", + "to": "source_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * source_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "dest_wheeling_charge", + "from": "buyer", + "to": "destination_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * dest_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "platform_fee_buyer", + "from": "buyer", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "platform_fee_seller", + "from": "seller", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "deviation_penalty", + "from": "seller", + "to": "buyer", + "flowType": "DEVIATION_PENALTY", + "formula": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "deviation_penalty" + } + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "delivery_obligation" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/on_confirm.json b/examples/deg_contract/p2p-trading-interdiscom/on_confirm.json new file mode 100644 index 00000000..4bc87c2a --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/on_confirm.json @@ -0,0 +1,452 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_confirm", + "timestamp": "2025-06-15T06:45:05Z", + "message_id": "msg-on-confirm-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY", + "beckn:state": { + "@type": "beckn:State", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "PENDING" + } + }, + "beckn:attributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "PENDING", + "deliveryMode": "GRID_INJECTION", + "energyFlow": { + "delivered": { "value": 0.0, "unitCode": "KWH" } + }, + "timing": { + "startTime": "2025-06-15T06:00:00+05:30" + }, + "meterReadings": [], + "lastUpdated": "2025-06-15T06:45:05+05:30" + } + } + ], + "beckn:payments": [ + { + "@type": "beckn:Payment", + "beckn:id": "payment-interdiscom-001", + "beckn:type": "ON-FULFILLMENT", + "beckn:status": "NOT-PAID", + "beckn:collected_by": "BPP", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/PaymentSettlement/v0.2/context.jsonld", + "@type": "PaymentSettlement", + "paymentAttributes": { + "settlementAccounts": [ + { + "accountType": "UPI", + "accountId": "priya.sharma@okicici", + "accountHolder": "Priya Sharma", + "role": "buyer" + }, + { + "accountType": "BANK_ACCOUNT", + "accountId": "HDFC-50100012345678", + "ifscCode": "HDFC0001234", + "accountHolder": "Delhi Solar Prosumer Pvt Ltd", + "role": "seller" + } + ] + } + } + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "ACTIVE", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + }, + "inputs": [ + { + "inputId": "source_meter", + "inputType": "METER_ID", + "value": "der://meter/100200300", + "providedAt": "TEMPLATE" + }, + { + "inputId": "energy_source", + "inputType": "PARAMETER", + "value": "SOLAR", + "providedAt": "TEMPLATE" + }, + { + "inputId": "available_kwh", + "inputType": "PARAMETER", + "value": 25.0, + "providedAt": "TEMPLATE" + }, + { + "inputId": "source_utility", + "inputType": "PARAMETER", + "value": "TPDDL-DL", + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation"] + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": { + "partyId": "buyer-bangalore-001", + "partyType": "BUYER", + "platformId": "bap.energy-buyer.in" + }, + "inputs": [ + { + "inputId": "destination_meter", + "inputType": "METER_ID", + "value": "der://meter/98765456", + "providedAt": "NEGOTIATION" + }, + { + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "value": 20.0, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "buyer_utility_id", + "inputType": "PARAMETER", + "value": "BESCOM-KA", + "providedAt": "COMMITMENT" + }, + { + "inputId": "buyer_utility_customer_id", + "inputType": "PARAMETER", + "value": "BESCOM-2024-CON-08832", + "providedAt": "COMMITMENT" + } + ], + "obligations": ["pricing", "payment_obligation"] + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": 0.25, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": { + "partyId": "BESCOM-KA", + "partyType": "PROVIDER", + "platformId": "bpp.bescom.gov.in" + }, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": 0.25, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ], + "terms": [ + { + "termId": "delivery_obligation", + "termType": "DELIVERY", + "condition": { + "expr": "currentTime WITHIN deliveryWindow" + }, + "obligation": { + "rule": { + "operator": "AND", + "operands": [ + { + "field": "deliveredQuantity", + "operator": "GTE", + "value": "requestedKwh * 0.9" + }, + { + "field": "deliveredQuantity", + "operator": "LTE", + "value": "requestedKwh * 1.1" + } + ] + } + }, + "consequence": { + "expr": "deviationPenalty = abs(deliveredQuantity - requestedKwh) * 1.50" + }, + "appliesTo": ["seller"] + }, + { + "termId": "pricing", + "termType": "PAYMENT", + "condition": { + "expr": "true" + }, + "obligation": { + "expr": "payment = deliveredQuantity * pricePerKwh + totalWheelingCharge + platformFee" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "utility_registration", + "termType": "QUALITY", + "condition": { + "expr": "contractState == COMMITTED" + }, + "obligation": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "open_access_check" + } + }, + "appliesTo": ["source_utility", "destination_utility"] + }, + { + "termId": "payment_obligation", + "termType": "PAYMENT", + "condition": { + "expr": "deliveryStatus == COMPLETED" + }, + "obligation": { + "expr": "settlePayment WITHIN 24 hours OF deliveryCompletion" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "cancellation", + "termType": "CANCELLATION", + "condition": { + "expr": "contractState IN [COMMITTED, ACTIVE] AND cancellationRequestTime < deliveryWindow.start - PT1H" + }, + "consequence": { + "expr": "cancellationFee = 0" + }, + "appliesTo": ["buyer", "seller"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "unitQuantity": 20.0 + }, + "qualityConstraints": { + "greenCertRequired": true + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "meterIds": [ + "der://meter/100200300", + "der://meter/98765456" + ], + "signals": [] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "DAILY" + }, + "revenueModel": { + "definitions": [ + { + "term": "price_per_kwh", + "description": "Per-kWh energy price set by seller", + "source": "role:seller.input:price_per_kwh", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy delivered via grid injection", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "source_wheeling_rate", + "description": "Wheeling charge rate from source DISCOM", + "source": "role:source_utility.input:source_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "dest_wheeling_rate", + "description": "Wheeling charge rate from destination DISCOM", + "source": "role:destination_utility.input:destination_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "energy_payment_amount", + "description": "Computed energy payment from buyer to seller", + "source": "flow:energy_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate (bilateral)", + "source": "constant", + "value": 0.01, + "unit": "fraction" + } + ], + "flows": [ + { + "flowId": "energy_payment", + "from": "buyer", + "to": "seller", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "delivered_kwh * price_per_kwh" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "pricing" + }, + { + "flowId": "wheeling_charge", + "from": "buyer", + "to": "source_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * source_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "dest_wheeling_charge", + "from": "buyer", + "to": "destination_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * dest_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "platform_fee_buyer", + "from": "buyer", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "platform_fee_seller", + "from": "seller", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "deviation_penalty", + "from": "seller", + "to": "buyer", + "flowType": "DEVIATION_PENALTY", + "formula": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "deviation_penalty" + } + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "delivery_obligation" + } + ] + } + }, + "beckn:cancellationTerms": [ + { + "@type": "beckn:CancellationTerm", + "beckn:externalRef": { + "@type": "schema:MediaObject", + "schema:encodingFormat": "text/html", + "schema:contentUrl": "https://tpddl.com/open-access/cancellation-terms.html" + } + } + ] + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/on_discover.json b/examples/deg_contract/p2p-trading-interdiscom/on_discover.json new file mode 100644 index 00000000..0c715b1b --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/on_discover.json @@ -0,0 +1,830 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_discover", + "timestamp": "2025-06-15T06:30:05Z", + "message_id": "msg-on-discover-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "catalogs": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Catalog", + "beckn:id": "catalog-solar-delhi-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Delhi Solar Farm - Inter-DISCOM Energy Trading Catalog" + }, + "beckn:items": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Item", + "beckn:id": "energy-resource-solar-delhi-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Rooftop Solar Energy - 50 kWh", + "beckn:shortDesc": "Green Certified Rooftop Solar Energy from Delhi", + "beckn:longDesc": "High-quality rooftop solar energy from a verified 10 kW installation in South Delhi, available for inter-DISCOM P2P trading via open access." + }, + "beckn:provider": { + "@type": "beckn:Provider", + "beckn:id": "provider-solar-delhi-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Delhi Solar Prosumer" + }, + "beckn:providerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_provider_context.jsonld", + "@type": "EnergyProvider", + "operatorName": "Delhi Solar Prosumer", + "identifier": "delhi-solar-prosumer-001", + "gridAccount": { + "meterId": "der://meter/100200300", + "utilityId": "TPDDL-DL", + "utilityCustomerId": "TPDDL-2024-SOL-00451", + "sanctionedLoad": 10.0, + "connectionType": "LT-DOMESTIC" + }, + "registration": { + "gstNumber": "07AABCS1234D1Z5" + } + } + }, + "beckn:itemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_resource_context.jsonld", + "@type": "EnergyResource", + "resourceType": "GENERATION_PLANT", + "sourceType": "SOLAR", + "deliveryMode": "GRID_INJECTION", + "capacity": { + "installed": { "value": 10, "unitCode": "KW" }, + "available": { "value": 50, "unitCode": "KWH" } + }, + "metering": { + "meterId": "der://meter/100200300", + "inverterId": "inv-delhi-sol-78901" + }, + "capabilities": { + "productionAsynchronous": true + }, + "location": { + "geo": { "type": "Point", "coordinates": [77.209, 28.6139] }, + "address": { + "streetAddress": "42, Safdarjung Enclave", + "addressLocality": "New Delhi", + "addressRegion": "Delhi", + "postalCode": "110001", + "addressCountry": "IN" + } + }, + "certification": { + "status": "Green Energy Certified", + "verification": { + "verified": true, + "verificationDate": "2025-01-15", + "certificates": [ + "https://mnre.gov.in/certs/rooftop-solar-cert-00451.pdf" + ] + } + }, + "productionWindow": { + "start": "06:00:00+05:30", + "end": "18:00:00+05:30" + }, + "rating": { "value": 4.4, "count": 89 } + } + } + ], + "beckn:offers": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-solar-morning-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Morning Slot Solar Energy (06:00-12:00)" + }, + "beckn:provider": "provider-solar-delhi-001", + "beckn:items": ["energy-resource-solar-delhi-001"], + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 4.50, + "schema:priceCurrency": "INR", + "schema:unitText": "kWh" + }, + "beckn:offerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "TEMPLATE", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + }, + "inputs": [ + { + "inputId": "source_meter", + "inputType": "METER_ID", + "value": "der://meter/100200300", + "providedAt": "TEMPLATE" + }, + { + "inputId": "energy_source", + "inputType": "PARAMETER", + "value": "SOLAR", + "providedAt": "TEMPLATE" + }, + { + "inputId": "available_kwh", + "inputType": "PARAMETER", + "value": 25.0, + "providedAt": "TEMPLATE" + }, + { + "inputId": "source_utility", + "inputType": "PARAMETER", + "value": "TPDDL-DL", + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation"] + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": null, + "inputs": [ + { + "inputId": "destination_meter", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "NEGOTIATION", + "description": "Buyer's meter ID for energy delivery" + }, + { + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": null, + "schema": { + "type": "number", + "minimum": 5.0, + "maximum": 25.0 + }, + "providedAt": "NEGOTIATION", + "description": "Quantity of energy requested in kWh" + }, + { + "inputId": "buyer_utility_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT", + "description": "Buyer's DISCOM utility identifier" + }, + { + "inputId": "buyer_utility_customer_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT", + "description": "Buyer's utility customer account number" + } + ], + "obligations": ["pricing", "payment_obligation"] + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "source_utility", + "description": "Wheeling charge from source DISCOM" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": null, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility", + "description": "Wheeling charge from destination DISCOM" + } + ], + "obligations": ["utility_registration"] + } + ], + "terms": [ + { + "termId": "delivery_obligation", + "termType": "DELIVERY", + "condition": { + "expr": "currentTime WITHIN deliveryWindow" + }, + "obligation": { + "rule": { + "operator": "AND", + "operands": [ + { + "field": "deliveredQuantity", + "operator": "GTE", + "value": "requestedKwh * 0.9" + }, + { + "field": "deliveredQuantity", + "operator": "LTE", + "value": "requestedKwh * 1.1" + } + ] + } + }, + "consequence": { + "expr": "deviationPenalty = abs(deliveredQuantity - requestedKwh) * deviationPenaltyPerKwh" + }, + "appliesTo": ["seller"] + }, + { + "termId": "pricing", + "termType": "PAYMENT", + "condition": { + "expr": "true" + }, + "obligation": { + "expr": "payment = deliveredQuantity * pricePerKwh + totalWheelingCharge" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "utility_registration", + "termType": "QUALITY", + "condition": { + "expr": "contractState == COMMITTED" + }, + "obligation": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "open_access_check" + } + }, + "appliesTo": ["source_utility", "destination_utility"] + }, + { + "termId": "payment_obligation", + "termType": "PAYMENT", + "condition": { + "expr": "deliveryStatus == COMPLETED" + }, + "obligation": { + "expr": "settlePayment WITHIN 24 hours OF deliveryCompletion" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "cancellation", + "termType": "CANCELLATION", + "condition": { + "expr": "contractState IN [COMMITTED, ACTIVE] AND cancellationRequestTime < deliveryWindow.start - PT1H" + }, + "consequence": { + "expr": "cancellationFee = 0" + }, + "appliesTo": ["buyer", "seller"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "maxQuantity": 25.0 + }, + "qualityConstraints": { + "greenCertRequired": true + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T12:00:00+05:30" + }, + "meterIds": ["der://meter/100200300"], + "signals": [] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "DAILY" + }, + "revenueModel": { + "definitions": [ + { + "term": "price_per_kwh", + "description": "Per-kWh energy price set by seller", + "source": "role:seller.input:price_per_kwh", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy delivered via grid injection", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "source_wheeling_rate", + "description": "Wheeling charge rate from source DISCOM", + "source": "role:source_utility.input:source_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "dest_wheeling_rate", + "description": "Wheeling charge rate from destination DISCOM", + "source": "role:destination_utility.input:destination_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "energy_payment_amount", + "description": "Computed energy payment from buyer to seller", + "source": "flow:energy_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate (bilateral)", + "source": "constant", + "value": 0.01, + "unit": "fraction" + } + ], + "flows": [ + { + "flowId": "energy_payment", + "from": "buyer", + "to": "seller", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "delivered_kwh * price_per_kwh" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "pricing" + }, + { + "flowId": "wheeling_charge", + "from": "buyer", + "to": "source_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * source_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "dest_wheeling_charge", + "from": "buyer", + "to": "destination_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * dest_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "platform_fee_buyer", + "from": "buyer", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "platform_fee_seller", + "from": "seller", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "deviation_penalty", + "from": "seller", + "to": "buyer", + "flowType": "DEVIATION_PENALTY", + "formula": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "deviation_penalty" + } + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "delivery_obligation" + } + ] + } + } + }, + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-solar-afternoon-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Afternoon Slot Solar Energy (12:00-18:00)" + }, + "beckn:provider": "provider-solar-delhi-001", + "beckn:items": ["energy-resource-solar-delhi-001"], + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 4.00, + "schema:priceCurrency": "INR", + "schema:unitText": "kWh" + }, + "beckn:offerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "TEMPLATE", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + }, + "inputs": [ + { + "inputId": "source_meter", + "inputType": "METER_ID", + "value": "der://meter/100200300", + "providedAt": "TEMPLATE" + }, + { + "inputId": "energy_source", + "inputType": "PARAMETER", + "value": "SOLAR", + "providedAt": "TEMPLATE" + }, + { + "inputId": "available_kwh", + "inputType": "PARAMETER", + "value": 25.0, + "providedAt": "TEMPLATE" + }, + { + "inputId": "source_utility", + "inputType": "PARAMETER", + "value": "TPDDL-DL", + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation"] + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": null, + "inputs": [ + { + "inputId": "destination_meter", + "inputType": "METER_ID", + "required": true, + "value": null, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": null, + "schema": { + "type": "number", + "minimum": 5.0, + "maximum": 25.0 + }, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "buyer_utility_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + }, + { + "inputId": "buyer_utility_customer_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + } + ], + "obligations": ["pricing", "payment_obligation"] + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": null, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ], + "terms": [ + { + "termId": "delivery_obligation", + "termType": "DELIVERY", + "condition": { + "expr": "currentTime WITHIN deliveryWindow" + }, + "obligation": { + "rule": { + "operator": "AND", + "operands": [ + { + "field": "deliveredQuantity", + "operator": "GTE", + "value": "requestedKwh * 0.9" + }, + { + "field": "deliveredQuantity", + "operator": "LTE", + "value": "requestedKwh * 1.1" + } + ] + } + }, + "consequence": { + "expr": "deviationPenalty = abs(deliveredQuantity - requestedKwh) * deviationPenaltyPerKwh" + }, + "appliesTo": ["seller"] + }, + { + "termId": "pricing", + "termType": "PAYMENT", + "condition": { + "expr": "true" + }, + "obligation": { + "expr": "payment = deliveredQuantity * pricePerKwh + totalWheelingCharge" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "utility_registration", + "termType": "QUALITY", + "condition": { + "expr": "contractState == COMMITTED" + }, + "obligation": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "open_access_check" + } + }, + "appliesTo": ["source_utility", "destination_utility"] + }, + { + "termId": "payment_obligation", + "termType": "PAYMENT", + "condition": { + "expr": "deliveryStatus == COMPLETED" + }, + "obligation": { + "expr": "settlePayment WITHIN 24 hours OF deliveryCompletion" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "cancellation", + "termType": "CANCELLATION", + "condition": { + "expr": "contractState IN [COMMITTED, ACTIVE] AND cancellationRequestTime < deliveryWindow.start - PT1H" + }, + "consequence": { + "expr": "cancellationFee = 0" + }, + "appliesTo": ["buyer", "seller"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "maxQuantity": 25.0 + }, + "qualityConstraints": { + "greenCertRequired": true + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T12:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "meterIds": ["der://meter/100200300"], + "signals": [] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "DAILY" + }, + "revenueModel": { + "definitions": [ + { + "term": "price_per_kwh", + "description": "Per-kWh energy price set by seller", + "source": "role:seller.input:price_per_kwh", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy delivered via grid injection", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "source_wheeling_rate", + "description": "Wheeling charge rate from source DISCOM", + "source": "role:source_utility.input:source_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "dest_wheeling_rate", + "description": "Wheeling charge rate from destination DISCOM", + "source": "role:destination_utility.input:destination_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "energy_payment_amount", + "description": "Computed energy payment from buyer to seller", + "source": "flow:energy_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate (bilateral)", + "source": "constant", + "value": 0.01, + "unit": "fraction" + } + ], + "flows": [ + { + "flowId": "energy_payment", + "from": "buyer", + "to": "seller", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "delivered_kwh * price_per_kwh" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "pricing" + }, + { + "flowId": "wheeling_charge", + "from": "buyer", + "to": "source_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * source_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "dest_wheeling_charge", + "from": "buyer", + "to": "destination_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * dest_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "platform_fee_buyer", + "from": "buyer", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "platform_fee_seller", + "from": "seller", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "deviation_penalty", + "from": "seller", + "to": "buyer", + "flowType": "DEVIATION_PENALTY", + "formula": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "deviation_penalty" + } + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "delivery_obligation" + } + ] + } + } + } + ] + } + ] + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/on_init.json b/examples/deg_contract/p2p-trading-interdiscom/on_init.json new file mode 100644 index 00000000..83d3ce03 --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/on_init.json @@ -0,0 +1,491 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_init", + "timestamp": "2025-06-15T06:40:05Z", + "message_id": "msg-on-init-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY", + "beckn:stops": [ + { + "@type": "beckn:Stop", + "beckn:id": "stop-source-001", + "beckn:type": "START", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/100200300" + } + }, + { + "@type": "beckn:Stop", + "beckn:id": "stop-destination-001", + "beckn:type": "END", + "beckn:location": { + "@type": "beckn:Location", + "beckn:address": "der://meter/98765456" + } + } + ] + } + ], + "beckn:payments": [ + { + "@type": "beckn:Payment", + "beckn:id": "payment-interdiscom-001", + "beckn:type": "ON-FULFILLMENT", + "beckn:status": "NOT-PAID", + "beckn:collected_by": "BPP", + "beckn:params": { + "amount": "95.70", + "currency": "INR" + }, + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/PaymentSettlement/v0.2/context.jsonld", + "@type": "PaymentSettlement", + "paymentAttributes": { + "settlementAccounts": [ + { + "accountType": "UPI", + "accountId": "priya.sharma@okicici", + "accountHolder": "Priya Sharma", + "role": "buyer" + }, + { + "accountType": "BANK_ACCOUNT", + "accountId": "HDFC-50100012345678", + "ifscCode": "HDFC0001234", + "accountHolder": "Delhi Solar Prosumer Pvt Ltd", + "role": "seller" + } + ] + } + } + } + ], + "beckn:quote": { + "@type": "beckn:Quotation", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 95.70, + "schema:priceCurrency": "INR" + }, + "beckn:breakup": [ + { + "@type": "beckn:Breakup", + "beckn:title": "Morning Slot Energy (10 kWh @ INR 4.50/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 45.00, + "schema:priceCurrency": "INR" + } + }, + { + "@type": "beckn:Breakup", + "beckn:title": "Afternoon Slot Energy (10 kWh @ INR 4.00/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 40.00, + "schema:priceCurrency": "INR" + } + }, + { + "@type": "beckn:Breakup", + "beckn:title": "Inter-DISCOM Wheeling Charges (20 kWh @ INR 0.50/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 10.00, + "schema:priceCurrency": "INR" + } + }, + { + "@type": "beckn:Breakup", + "beckn:title": "Platform Fee (1% of energy cost)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 0.85, + "schema:priceCurrency": "INR" + } + } + ] + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "COMMITTED", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + }, + "inputs": [ + { + "inputId": "source_meter", + "inputType": "METER_ID", + "value": "der://meter/100200300", + "providedAt": "TEMPLATE" + }, + { + "inputId": "energy_source", + "inputType": "PARAMETER", + "value": "SOLAR", + "providedAt": "TEMPLATE" + }, + { + "inputId": "available_kwh", + "inputType": "PARAMETER", + "value": 25.0, + "providedAt": "TEMPLATE" + }, + { + "inputId": "source_utility", + "inputType": "PARAMETER", + "value": "TPDDL-DL", + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation"] + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": { + "partyId": "buyer-bangalore-001", + "partyType": "BUYER", + "platformId": "bap.energy-buyer.in" + }, + "inputs": [ + { + "inputId": "destination_meter", + "inputType": "METER_ID", + "value": "der://meter/98765456", + "providedAt": "NEGOTIATION" + }, + { + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "value": 20.0, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "buyer_utility_id", + "inputType": "PARAMETER", + "value": "BESCOM-KA", + "providedAt": "COMMITMENT" + }, + { + "inputId": "buyer_utility_customer_id", + "inputType": "PARAMETER", + "value": "BESCOM-2024-CON-08832", + "providedAt": "COMMITMENT" + } + ], + "obligations": ["pricing", "payment_obligation"] + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": { + "partyId": "BESCOM-KA", + "partyType": "PROVIDER", + "platformId": "bpp.bescom.gov.in" + }, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ], + "terms": [ + { + "termId": "delivery_obligation", + "termType": "DELIVERY", + "condition": { + "expr": "currentTime WITHIN deliveryWindow" + }, + "obligation": { + "rule": { + "operator": "AND", + "operands": [ + { + "field": "deliveredQuantity", + "operator": "GTE", + "value": "requestedKwh * 0.9" + }, + { + "field": "deliveredQuantity", + "operator": "LTE", + "value": "requestedKwh * 1.1" + } + ] + } + }, + "consequence": { + "expr": "deviationPenalty = abs(deliveredQuantity - requestedKwh) * 1.50" + }, + "appliesTo": ["seller"] + }, + { + "termId": "pricing", + "termType": "PAYMENT", + "condition": { + "expr": "true" + }, + "obligation": { + "expr": "payment = deliveredQuantity * pricePerKwh + totalWheelingCharge + platformFee" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "utility_registration", + "termType": "QUALITY", + "condition": { + "expr": "contractState == COMMITTED" + }, + "obligation": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "open_access_check" + } + }, + "appliesTo": ["source_utility", "destination_utility"] + }, + { + "termId": "payment_obligation", + "termType": "PAYMENT", + "condition": { + "expr": "deliveryStatus == COMPLETED" + }, + "obligation": { + "expr": "settlePayment WITHIN 24 hours OF deliveryCompletion" + }, + "appliesTo": ["buyer"] + }, + { + "termId": "cancellation", + "termType": "CANCELLATION", + "condition": { + "expr": "contractState IN [COMMITTED, ACTIVE] AND cancellationRequestTime < deliveryWindow.start - PT1H" + }, + "consequence": { + "expr": "cancellationFee = 0" + }, + "appliesTo": ["buyer", "seller"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "unitQuantity": 20.0 + }, + "qualityConstraints": { + "greenCertRequired": true + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "meterIds": [ + "der://meter/100200300", + "der://meter/98765456" + ], + "signals": [] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "DAILY" + }, + "revenueModel": { + "definitions": [ + { + "term": "price_per_kwh", + "description": "Per-kWh energy price set by seller", + "source": "role:seller.input:price_per_kwh", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy delivered via grid injection", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "source_wheeling_rate", + "description": "Wheeling charge rate from source DISCOM", + "source": "role:source_utility.input:source_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "dest_wheeling_rate", + "description": "Wheeling charge rate from destination DISCOM", + "source": "role:destination_utility.input:destination_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "energy_payment_amount", + "description": "Computed energy payment from buyer to seller", + "source": "flow:energy_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate (bilateral)", + "source": "constant", + "value": 0.01, + "unit": "fraction" + } + ], + "flows": [ + { + "flowId": "energy_payment", + "from": "buyer", + "to": "seller", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "delivered_kwh * price_per_kwh" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "pricing" + }, + { + "flowId": "wheeling_charge", + "from": "buyer", + "to": "source_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * source_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "dest_wheeling_charge", + "from": "buyer", + "to": "destination_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * dest_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "platform_fee_buyer", + "from": "buyer", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "platform_fee_seller", + "from": "seller", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "deviation_penalty", + "from": "seller", + "to": "buyer", + "flowType": "DEVIATION_PENALTY", + "formula": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "deviation_penalty" + } + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "delivery_obligation" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/on_select.json b/examples/deg_contract/p2p-trading-interdiscom/on_select.json new file mode 100644 index 00000000..5bdf820b --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/on_select.json @@ -0,0 +1,409 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_select", + "timestamp": "2025-06-15T06:35:05Z", + "message_id": "msg-on-select-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:quote": { + "@type": "beckn:Quotation", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 95.70, + "schema:priceCurrency": "INR", + "schema:description": "Total estimated cost for 20 kWh (10 kWh morning + 10 kWh afternoon)" + }, + "beckn:breakup": [ + { + "@type": "beckn:Breakup", + "beckn:title": "Morning Slot Energy Cost (10 kWh @ INR 4.50/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 45.00, + "schema:priceCurrency": "INR" + } + }, + { + "@type": "beckn:Breakup", + "beckn:title": "Afternoon Slot Energy Cost (10 kWh @ INR 4.00/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 40.00, + "schema:priceCurrency": "INR" + } + }, + { + "@type": "beckn:Breakup", + "beckn:title": "Inter-DISCOM Wheeling Charges (20 kWh @ INR 0.50/kWh)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 10.00, + "schema:priceCurrency": "INR" + } + }, + { + "@type": "beckn:Breakup", + "beckn:title": "Platform Fee (1% of energy cost)", + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 0.85, + "schema:priceCurrency": "INR" + } + } + ] + }, + "beckn:orderItems": [ + { + "@type": "beckn:OrderItem", + "beckn:id": "order-item-morning-001", + "beckn:offer": { + "beckn:id": "offer-solar-morning-001" + }, + "beckn:item": { + "beckn:id": "energy-resource-solar-delhi-001" + }, + "beckn:quantity": { + "count": 10.0, + "unit": "kWh" + }, + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 45.00, + "schema:priceCurrency": "INR" + }, + "beckn:orderItemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_order_item_context.jsonld", + "@type": "EnergyOrderItem", + "deliveryWindow": { + "start": "06:00:00+05:30", + "end": "12:00:00+05:30" + } + } + }, + { + "@type": "beckn:OrderItem", + "beckn:id": "order-item-afternoon-001", + "beckn:offer": { + "beckn:id": "offer-solar-afternoon-001" + }, + "beckn:item": { + "beckn:id": "energy-resource-solar-delhi-001" + }, + "beckn:quantity": { + "count": 10.0, + "unit": "kWh" + }, + "beckn:price": { + "@type": "schema:PriceSpecification", + "schema:price": 40.00, + "schema:priceCurrency": "INR" + }, + "beckn:orderItemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_order_item_context.jsonld", + "@type": "EnergyOrderItem", + "deliveryWindow": { + "start": "12:00:00+05:30", + "end": "18:00:00+05:30" + } + } + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "NEGOTIATED", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + }, + "inputs": [ + { + "inputId": "source_meter", + "inputType": "METER_ID", + "value": "der://meter/100200300", + "providedAt": "TEMPLATE" + }, + { + "inputId": "energy_source", + "inputType": "PARAMETER", + "value": "SOLAR", + "providedAt": "TEMPLATE" + }, + { + "inputId": "available_kwh", + "inputType": "PARAMETER", + "value": 25.0, + "providedAt": "TEMPLATE" + }, + { + "inputId": "source_utility", + "inputType": "PARAMETER", + "value": "TPDDL-DL", + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation"] + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": { + "partyId": "buyer-bangalore-001", + "partyType": "BUYER", + "platformId": "bap.energy-buyer.in" + }, + "inputs": [ + { + "inputId": "destination_meter", + "inputType": "METER_ID", + "required": true, + "value": "der://meter/98765456", + "providedAt": "NEGOTIATION" + }, + { + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 20.0, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "buyer_utility_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + }, + { + "inputId": "buyer_utility_customer_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + } + ], + "obligations": ["pricing", "payment_obligation"] + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": null, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "SOLAR", + "quantity": { + "unitText": "kWh", + "unitQuantity": 20.0 + }, + "qualityConstraints": { + "greenCertRequired": true + } + }, + "fulfillmentSpec": { + "deliveryMode": "GRID_INJECTION", + "deliveryWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "meterIds": [ + "der://meter/100200300", + "der://meter/98765456" + ], + "signals": [] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "DAILY" + }, + "revenueModel": { + "definitions": [ + { + "term": "price_per_kwh", + "description": "Per-kWh energy price set by seller", + "source": "role:seller.input:price_per_kwh", + "unit": "INR/kWh" + }, + { + "term": "delivered_kwh", + "description": "Total energy delivered via grid injection", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "source_wheeling_rate", + "description": "Wheeling charge rate from source DISCOM", + "source": "role:source_utility.input:source_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "dest_wheeling_rate", + "description": "Wheeling charge rate from destination DISCOM", + "source": "role:destination_utility.input:destination_wheeling_charge", + "unit": "INR/kWh" + }, + { + "term": "energy_payment_amount", + "description": "Computed energy payment from buyer to seller", + "source": "flow:energy_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate (bilateral)", + "source": "constant", + "value": 0.01, + "unit": "fraction" + } + ], + "flows": [ + { + "flowId": "energy_payment", + "from": "buyer", + "to": "seller", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "delivered_kwh * price_per_kwh" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "pricing" + }, + { + "flowId": "wheeling_charge", + "from": "buyer", + "to": "source_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * source_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "dest_wheeling_charge", + "from": "buyer", + "to": "destination_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * dest_wheeling_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "utility_registration" + }, + { + "flowId": "platform_fee_buyer", + "from": "buyer", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "platform_fee_seller", + "from": "seller", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null + }, + { + "flowId": "deviation_penalty", + "from": "seller", + "to": "buyer", + "flowType": "DEVIATION_PENALTY", + "formula": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "deviation_penalty" + } + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "delivery_obligation" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/on_status.json b/examples/deg_contract/p2p-trading-interdiscom/on_status.json new file mode 100644 index 00000000..894c2a00 --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/on_status.json @@ -0,0 +1,211 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_status", + "timestamp": "2025-06-15T12:00:05Z", + "message_id": "msg-on-status-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY", + "beckn:state": { + "@type": "beckn:State", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "IN_PROGRESS" + } + }, + "beckn:attributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "IN_PROGRESS", + "deliveryMode": "GRID_INJECTION", + "energyFlow": { + "delivered": { "value": 9.2, "unitCode": "KWH" } + }, + "timing": { + "startTime": "2025-06-15T06:00:00+05:30" + }, + "meterReadings": [ + { + "timeWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T08:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 3.2, + "allocatedEnergy": 3.1, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T08:00:00+05:30", + "end": "2025-06-15T10:00:00+05:30" + }, + "consumedEnergy": 3.0, + "producedEnergy": 3.5, + "allocatedEnergy": 3.0, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T10:00:00+05:30", + "end": "2025-06-15T12:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 4.0, + "allocatedEnergy": 3.1, + "unit": "KWH" + } + ], + "lastUpdated": "2025-06-15T12:00:00+05:30" + } + } + ], + "beckn:orderItems": [ + { + "@type": "beckn:OrderItem", + "beckn:id": "order-item-morning-001", + "beckn:offer": { + "beckn:id": "offer-solar-morning-001" + }, + "beckn:item": { + "beckn:id": "energy-resource-solar-delhi-001" + }, + "beckn:quantity": { + "count": 10.0, + "unit": "kWh" + }, + "beckn:orderItemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_order_item_context.jsonld", + "@type": "EnergyOrderItem", + "deliveryWindow": { + "start": "06:00:00+05:30", + "end": "12:00:00+05:30" + }, + "delivery": { + "status": "COMPLETED", + "deliveredQuantity": { "value": 9.2, "unitCode": "KWH" }, + "meterReadings": [ + { + "timeWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T08:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 3.2, + "allocatedEnergy": 3.1, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T08:00:00+05:30", + "end": "2025-06-15T10:00:00+05:30" + }, + "consumedEnergy": 3.0, + "producedEnergy": 3.5, + "allocatedEnergy": 3.0, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T10:00:00+05:30", + "end": "2025-06-15T12:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 4.0, + "allocatedEnergy": 3.1, + "unit": "KWH" + } + ] + } + } + }, + { + "@type": "beckn:OrderItem", + "beckn:id": "order-item-afternoon-001", + "beckn:offer": { + "beckn:id": "offer-solar-afternoon-001" + }, + "beckn:item": { + "beckn:id": "energy-resource-solar-delhi-001" + }, + "beckn:quantity": { + "count": 10.0, + "unit": "kWh" + }, + "beckn:orderItemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_order_item_context.jsonld", + "@type": "EnergyOrderItem", + "deliveryWindow": { + "start": "12:00:00+05:30", + "end": "18:00:00+05:30" + }, + "delivery": { + "status": "PENDING", + "deliveredQuantity": { "value": 0.0, "unitCode": "KWH" }, + "meterReadings": [] + } + } + } + ], + "beckn:payments": [ + { + "@type": "beckn:Payment", + "beckn:id": "payment-interdiscom-001", + "beckn:type": "ON-FULFILLMENT", + "beckn:status": "NOT-PAID", + "beckn:collected_by": "BPP" + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "ACTIVE", + "deliveryProgress": { + "totalContracted": 20.0, + "totalDelivered": 9.2, + "morningSlotDelivered": 9.2, + "afternoonSlotDelivered": 0.0, + "complianceStatus": "ON_TRACK" + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/on_status_completed.json b/examples/deg_contract/p2p-trading-interdiscom/on_status_completed.json new file mode 100644 index 00000000..629febe8 --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/on_status_completed.json @@ -0,0 +1,517 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_status", + "timestamp": "2025-06-15T18:30:00Z", + "message_id": "msg-on-status-completed-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:fulfillments": [ + { + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-interdiscom-001", + "beckn:type": "ENERGY_DELIVERY", + "beckn:state": { + "@type": "beckn:State", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "COMPLETED" + } + }, + "beckn:attributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "COMPLETED", + "deliveryMode": "GRID_INJECTION", + "energyFlow": { + "delivered": { "value": 19.5, "unitCode": "KWH" } + }, + "timing": { + "startTime": "2025-06-15T06:00:00+05:30", + "endTime": "2025-06-15T18:00:00+05:30" + }, + "meterReadings": [ + { + "timeWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T08:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 3.2, + "allocatedEnergy": 3.1, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T08:00:00+05:30", + "end": "2025-06-15T10:00:00+05:30" + }, + "consumedEnergy": 3.0, + "producedEnergy": 3.5, + "allocatedEnergy": 3.0, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T10:00:00+05:30", + "end": "2025-06-15T12:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 4.0, + "allocatedEnergy": 3.1, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T12:00:00+05:30", + "end": "2025-06-15T14:00:00+05:30" + }, + "consumedEnergy": 3.5, + "producedEnergy": 4.5, + "allocatedEnergy": 3.5, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T14:00:00+05:30", + "end": "2025-06-15T16:00:00+05:30" + }, + "consumedEnergy": 3.8, + "producedEnergy": 3.8, + "allocatedEnergy": 3.8, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T16:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "consumedEnergy": 3.0, + "producedEnergy": 2.5, + "allocatedEnergy": 3.0, + "unit": "KWH" + } + ], + "lastUpdated": "2025-06-15T18:30:00+05:30" + } + } + ], + "beckn:orderItems": [ + { + "@type": "beckn:OrderItem", + "beckn:id": "order-item-morning-001", + "beckn:offer": { + "beckn:id": "offer-solar-morning-001" + }, + "beckn:item": { + "beckn:id": "energy-resource-solar-delhi-001" + }, + "beckn:quantity": { + "count": 10.0, + "unit": "kWh" + }, + "beckn:orderItemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_order_item_context.jsonld", + "@type": "EnergyOrderItem", + "deliveryWindow": { + "start": "06:00:00+05:30", + "end": "12:00:00+05:30" + }, + "delivery": { + "status": "COMPLETED", + "deliveredQuantity": { "value": 9.2, "unitCode": "KWH" }, + "meterReadings": [ + { + "timeWindow": { + "start": "2025-06-15T06:00:00+05:30", + "end": "2025-06-15T08:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 3.2, + "allocatedEnergy": 3.1, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T08:00:00+05:30", + "end": "2025-06-15T10:00:00+05:30" + }, + "consumedEnergy": 3.0, + "producedEnergy": 3.5, + "allocatedEnergy": 3.0, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T10:00:00+05:30", + "end": "2025-06-15T12:00:00+05:30" + }, + "consumedEnergy": 3.1, + "producedEnergy": 4.0, + "allocatedEnergy": 3.1, + "unit": "KWH" + } + ] + } + } + }, + { + "@type": "beckn:OrderItem", + "beckn:id": "order-item-afternoon-001", + "beckn:offer": { + "beckn:id": "offer-solar-afternoon-001" + }, + "beckn:item": { + "beckn:id": "energy-resource-solar-delhi-001" + }, + "beckn:quantity": { + "count": 10.0, + "unit": "kWh" + }, + "beckn:orderItemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_order_item_context.jsonld", + "@type": "EnergyOrderItem", + "deliveryWindow": { + "start": "12:00:00+05:30", + "end": "18:00:00+05:30" + }, + "delivery": { + "status": "COMPLETED", + "deliveredQuantity": { "value": 10.3, "unitCode": "KWH" }, + "meterReadings": [ + { + "timeWindow": { + "start": "2025-06-15T12:00:00+05:30", + "end": "2025-06-15T14:00:00+05:30" + }, + "consumedEnergy": 3.5, + "producedEnergy": 4.5, + "allocatedEnergy": 3.5, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T14:00:00+05:30", + "end": "2025-06-15T16:00:00+05:30" + }, + "consumedEnergy": 3.8, + "producedEnergy": 3.8, + "allocatedEnergy": 3.8, + "unit": "KWH" + }, + { + "timeWindow": { + "start": "2025-06-15T16:00:00+05:30", + "end": "2025-06-15T18:00:00+05:30" + }, + "consumedEnergy": 3.0, + "producedEnergy": 2.5, + "allocatedEnergy": 3.0, + "unit": "KWH" + } + ] + } + } + } + ], + "beckn:payments": [ + { + "@type": "beckn:Payment", + "beckn:id": "payment-interdiscom-001", + "beckn:type": "ON-FULFILLMENT", + "beckn:status": "PAID", + "beckn:collected_by": "BPP", + "beckn:params": { + "amount": "93.58", + "currency": "INR", + "transaction_id": "pay-upi-2025-06-15-00891" + }, + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/PaymentSettlement/v0.2/context.jsonld", + "@type": "PaymentSettlement", + "paymentAttributes": { + "settlementAccounts": [ + { + "accountType": "UPI", + "accountId": "priya.sharma@okicici", + "accountHolder": "Priya Sharma", + "role": "buyer" + }, + { + "accountType": "BANK_ACCOUNT", + "accountId": "HDFC-50100012345678", + "ifscCode": "HDFC0001234", + "accountHolder": "Delhi Solar Prosumer Pvt Ltd", + "role": "seller" + } + ] + } + } + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "SETTLED", + "deliveryProgress": { + "totalContracted": 20.0, + "totalDelivered": 19.5, + "morningSlotDelivered": 9.2, + "afternoonSlotDelivered": 10.3, + "complianceStatus": "COMPLIANT", + "deviationKwh": -0.5, + "deviationPercent": -2.5 + }, + "revenueModel": { + "definitions": [ + { + "term": "price_per_kwh", + "description": "Per-kWh energy price set by seller", + "source": "role:seller.input:price_per_kwh", + "unit": "INR/kWh", + "resolvedValue": { + "effective": 4.24, + "perSlot": { + "morningSlot": { "rate": 4.50, "deliveredKwh": 9.2, "amount": 41.40 }, + "afternoonSlot": { "rate": 4.00, "deliveredKwh": 10.3, "amount": 41.20 } + } + } + }, + { + "term": "delivered_kwh", + "description": "Total energy delivered via grid injection", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh", + "resolvedValue": 19.5 + }, + { + "term": "source_wheeling_rate", + "description": "Wheeling charge rate from source DISCOM", + "source": "role:source_utility.input:source_wheeling_charge", + "unit": "INR/kWh", + "resolvedValue": 0.25 + }, + { + "term": "dest_wheeling_rate", + "description": "Wheeling charge rate from destination DISCOM", + "source": "role:destination_utility.input:destination_wheeling_charge", + "unit": "INR/kWh", + "resolvedValue": 0.25 + }, + { + "term": "energy_payment_amount", + "description": "Computed energy payment from buyer to seller", + "source": "flow:energy_payment", + "unit": "INR", + "resolvedValue": 82.60 + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate (bilateral)", + "source": "constant", + "value": 0.01, + "unit": "fraction", + "resolvedValue": 0.01 + }, + { + "term": "requested_kwh", + "description": "Energy quantity requested by buyer", + "source": "role:buyer.input:requested_kwh", + "unit": "kWh", + "resolvedValue": 20.0 + }, + { + "term": "deviation_tolerance", + "description": "Acceptable deviation from contracted quantity", + "source": "constant", + "value": 0.10, + "unit": "fraction", + "resolvedValue": 0.10 + } + ], + "flows": [ + { + "flowId": "energy_payment", + "from": "buyer", + "to": "seller", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "delivered_kwh * price_per_kwh" + }, + "currency": "INR", + "computedAmount": 82.60, + "breakdown": { + "morningSlot": { + "deliveredKwh": 9.2, + "pricePerKwh": 4.50, + "amount": 41.40 + }, + "afternoonSlot": { + "deliveredKwh": 10.3, + "pricePerKwh": 4.00, + "amount": 41.20 + } + }, + "triggeredBy": "pricing" + }, + { + "flowId": "wheeling_charge", + "from": "buyer", + "to": "source_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * source_wheeling_rate" + }, + "currency": "INR", + "computedAmount": 4.88, + "triggeredBy": "utility_registration" + }, + { + "flowId": "dest_wheeling_charge", + "from": "buyer", + "to": "destination_utility", + "flowType": "WHEELING_CHARGE", + "formula": { + "expr": "delivered_kwh * dest_wheeling_rate" + }, + "currency": "INR", + "computedAmount": 4.88, + "triggeredBy": "utility_registration" + }, + { + "flowId": "platform_fee_buyer", + "from": "buyer", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": 0.83 + }, + { + "flowId": "platform_fee_seller", + "from": "seller", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "energy_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": 0.83 + }, + { + "flowId": "deviation_penalty", + "from": "seller", + "to": "buyer", + "flowType": "DEVIATION_PENALTY", + "formula": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.p2p_trade", + "entrypoint": "deviation_penalty" + } + }, + "currency": "INR", + "computedAmount": 0.00, + "note": "No penalty: deviation of -2.5% is within +/-10% tolerance", + "triggeredBy": "delivery_obligation" + } + ], + "netPositions": { + "buyer": { "net": -93.19, "direction": "PAYS" }, + "seller": { "net": 81.77, "direction": "RECEIVES" }, + "source_utility": { "net": 4.88, "direction": "RECEIVES" }, + "destination_utility": { "net": 4.88, "direction": "RECEIVES" }, + "platform": { "net": 1.66, "direction": "RECEIVES" } + }, + "netZeroCheck": { "sum": 0.00, "valid": true }, + "settlement": { + "settlementCycleId": "settle-2025-06-15-001", + "settlementTime": "2025-06-15T18:30:00+05:30", + "perRoleSummary": [ + { + "roleId": "buyer", + "inflows": 0.00, + "outflows": 93.19, + "netPosition": -93.19, + "direction": "PAYS", + "breakdown": { + "energyPayment": 82.60, + "sourceWheeling": 4.88, + "destWheeling": 4.88, + "platformFee": 0.83 + } + }, + { + "roleId": "seller", + "inflows": 82.60, + "outflows": 0.83, + "netPosition": 81.77, + "direction": "RECEIVES", + "breakdown": { + "energyPayment": 82.60, + "platformFee": -0.83 + } + }, + { + "roleId": "source_utility", + "inflows": 4.88, + "outflows": 0.00, + "netPosition": 4.88, + "direction": "RECEIVES" + }, + { + "roleId": "destination_utility", + "inflows": 4.88, + "outflows": 0.00, + "netPosition": 4.88, + "direction": "RECEIVES" + }, + { + "roleId": "platform", + "inflows": 1.66, + "outflows": 0.00, + "netPosition": 1.66, + "direction": "RECEIVES" + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/p2p-trading-interdiscom/select.json b/examples/deg_contract/p2p-trading-interdiscom/select.json new file mode 100644 index 00000000..437f7c27 --- /dev/null +++ b/examples/deg_contract/p2p-trading-interdiscom/select.json @@ -0,0 +1,185 @@ +{ + "context": { + "version": "2.0.0", + "action": "select", + "timestamp": "2025-06-15T06:35:00Z", + "message_id": "msg-select-interdiscom-001", + "transaction_id": "txn-p2p-interdiscom-001", + "bap_id": "bap.energy-buyer.in", + "bap_uri": "https://bap.energy-buyer.in", + "bpp_id": "bpp.solarfarm-delhi.in", + "bpp_uri": "https://bpp.solarfarm-delhi.in", + "ttl": "PT30S", + "domain": "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/draft/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-interdiscom-001", + "beckn:items": [ + { + "beckn:id": "energy-resource-solar-delhi-001", + "quantity": { + "count": 20.0, + "unit": "kWh" + } + } + ], + "beckn:offers": [ + { + "beckn:id": "offer-solar-morning-001" + }, + { + "beckn:id": "offer-solar-afternoon-001" + } + ], + "beckn:provider": { + "beckn:id": "provider-solar-delhi-001" + }, + "beckn:buyer": { + "@type": "beckn:Buyer", + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "gridAccount": { + "meterId": "der://meter/98765456", + "utilityId": "BESCOM-KA", + "utilityCustomerId": "BESCOM-2024-CON-08832", + "sanctionedLoad": 5.0, + "connectionType": "LT-DOMESTIC" + } + }, + "beckn:roleInputs": [ + { + "inputId": "destination_meter", + "value": "der://meter/98765456" + }, + { + "inputId": "requested_kwh", + "value": 20.0 + } + ] + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "P2P_ENERGY_TRADE", + "version": "0.4.0", + "contractState": "NEGOTIATED", + "roles": [ + { + "roleId": "seller", + "roleType": "PROSUMER", + "party": { + "partyId": "provider-solar-delhi-001", + "partyType": "PROVIDER", + "platformId": "bpp.solarfarm-delhi.in" + }, + "inputs": [ + { + "inputId": "source_meter", + "inputType": "METER_ID", + "value": "der://meter/100200300", + "providedAt": "TEMPLATE" + }, + { + "inputId": "energy_source", + "inputType": "PARAMETER", + "value": "SOLAR", + "providedAt": "TEMPLATE" + }, + { + "inputId": "available_kwh", + "inputType": "PARAMETER", + "value": 25.0, + "providedAt": "TEMPLATE" + }, + { + "inputId": "source_utility", + "inputType": "PARAMETER", + "value": "TPDDL-DL", + "providedAt": "TEMPLATE" + } + ], + "obligations": ["delivery_obligation"] + }, + { + "roleId": "buyer", + "roleType": "CONSUMER", + "party": { + "partyId": "buyer-bangalore-001", + "partyType": "BUYER", + "platformId": "bap.energy-buyer.in" + }, + "inputs": [ + { + "inputId": "destination_meter", + "inputType": "METER_ID", + "required": true, + "value": "der://meter/98765456", + "providedAt": "NEGOTIATION" + }, + { + "inputId": "requested_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 20.0, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "buyer_utility_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + }, + { + "inputId": "buyer_utility_customer_id", + "inputType": "PARAMETER", + "required": true, + "value": null, + "providedAt": "COMMITMENT" + } + ], + "obligations": ["pricing", "payment_obligation"] + }, + { + "roleId": "source_utility", + "roleType": "DISCOM", + "party": { + "partyId": "TPDDL-DL", + "partyType": "PROVIDER", + "platformId": "bpp.tpddl.gov.in" + }, + "inputs": [ + { + "inputId": "source_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "source_utility" + } + ], + "obligations": ["utility_registration"] + }, + { + "roleId": "destination_utility", + "roleType": "DISCOM", + "party": null, + "inputs": [ + { + "inputId": "destination_wheeling_charge", + "inputType": "PARAMETER", + "value": null, + "providedAt": "COMMITMENT", + "filledBy": "destination_utility" + } + ], + "obligations": ["utility_registration"] + } + ] + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/confirm.json b/examples/deg_contract/smart_ev_charging/confirm.json new file mode 100644 index 00000000..775a50c5 --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/confirm.json @@ -0,0 +1,118 @@ +{ + "context": { + "version": "2.0.0", + "action": "confirm", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:03:00Z", + "message_id": "msg-confirm-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-smart-ev-v2g-001", + "beckn:orderStatus": "PENDING", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma", + "beckn:telephone": "+91-9821234567", + "beckn:email": "ankit.sharma@example.com", + "beckn:taxID": "GSTIN27ABCDS5678K1Z9" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 20 + }, + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 163.20, + "components": [ + { + "type": "UNIT", + "value": 160.0, + "currency": "INR", + "description": "Estimated charging cost (20 kWh x INR 8/kWh peak rate)" + }, + { + "type": "FEE", + "value": 3.20, + "currency": "INR", + "description": "Platform fee (2%)" + } + ] + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "COMMITTED", + "version": "1.0.0", + "allPartiesConfirmed": { + "ev_owner": { + "confirmed": true, + "confirmedAt": "2026-02-08T09:03:00Z", + "v2gConsent": true + }, + "cpo_aggregator": { + "confirmed": true, + "confirmedAt": "2026-02-08T09:02:05Z" + }, + "grid_operator": { + "confirmed": true, + "confirmedAt": "2026-02-08T09:02:03Z", + "v2gDispatchReady": true + } + } + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-v2g-001", + "beckn:amount": { + "currency": "INR", + "value": 163.20 + }, + "beckn:paymentURL": "https://payments.tata-ezcharge.example.com/pay?txn=txn-smart-ev-v2g-001&amount=163.20¤cy=INR", + "beckn:txnRef": "TXN-V2G-20260208-001", + "beckn:paidAt": "2026-02-08T09:03:00Z", + "beckn:beneficiary": "BPP", + "beckn:paymentStatus": "COMPLETED" + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/init.json b/examples/deg_contract/smart_ev_charging/init.json new file mode 100644 index 00000000..1b98b32c --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/init.json @@ -0,0 +1,156 @@ +{ + "context": { + "version": "2.0.0", + "action": "init", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:02:00Z", + "message_id": "msg-init-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-smart-ev-v2g-001", + "beckn:orderStatus": "CREATED", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma", + "beckn:telephone": "+91-9821234567", + "beckn:email": "ankit.sharma@example.com", + "beckn:taxID": "GSTIN27ABCDS5678K1Z9" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 20 + }, + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 163.20, + "components": [ + { + "type": "UNIT", + "value": 160.0, + "currency": "INR", + "description": "Estimated charging cost (20 kWh x INR 8/kWh peak rate)" + }, + { + "type": "FEE", + "value": 3.20, + "currency": "INR", + "description": "Platform fee (2%)" + } + ] + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "COMMITTED", + "version": "1.0.0", + "roles": [ + { + "roleId": "ev_owner", + "roleType": "PROSUMER", + "party": { + "partyId": "ev-owner-ankit-001", + "partyType": "BUYER", + "platformId": "ev-app.example.com" + }, + "inputs": [ + { + "inputId": "vehicle_battery_kwh", + "inputType": "PARAMETER", + "value": 60, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "min_charge_level_pct", + "inputType": "PARAMETER", + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "v2g_consent", + "inputType": "PARAMETER", + "description": "EV owner consents to V2G discharge during grid stress", + "value": true, + "providedAt": "COMMITMENT" + }, + { + "inputId": "vehicle_registration", + "inputType": "CREDENTIAL", + "description": "Vehicle registration number", + "value": "MH-02-AB-1234", + "providedAt": "COMMITMENT" + }, + { + "inputId": "vehicle_make_model", + "inputType": "PARAMETER", + "description": "Vehicle make and model", + "value": "Tata Nexon EV Max", + "providedAt": "COMMITMENT" + } + ] + } + ] + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-v2g-001", + "beckn:amount": { + "currency": "INR", + "value": 163.20 + }, + "beckn:beneficiary": "BAP", + "beckn:paymentStatus": "INITIATED", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/payment_settlement_context.jsonld", + "@type": "PaymentSettlement", + "settlementAccounts": [ + { + "beneficiaryId": "ev-app.example.com", + "accountHolderName": "Ankit Sharma", + "vpa": "ankit.sharma@upi" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/on_confirm.json b/examples/deg_contract/smart_ev_charging/on_confirm.json new file mode 100644 index 00000000..11c16407 --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/on_confirm.json @@ -0,0 +1,147 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_confirm", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:03:05Z", + "message_id": "msg-on-confirm-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-smart-ev-v2g-001", + "beckn:orderStatus": "CONFIRMED", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma", + "beckn:telephone": "+91-9821234567", + "beckn:email": "ankit.sharma@example.com", + "beckn:taxID": "GSTIN27ABCDS5678K1Z9" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 20 + }, + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 163.20, + "components": [ + { + "type": "UNIT", + "value": 160.0, + "currency": "INR", + "description": "Estimated charging cost (20 kWh x INR 8/kWh peak rate)" + }, + { + "type": "FEE", + "value": 3.20, + "currency": "INR", + "description": "Platform fee (2%)" + } + ] + }, + "beckn:fulfillment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-v2g-001", + "beckn:mode": "RESERVATION", + "beckn:deliveryAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "PENDING", + "deliveryMode": "V2G", + "energyFlow": { + "direction": "TO_VEHICLE", + "delivered": { "value": 0, "unitCode": "KWH" } + }, + "timing": { + "reservationWindow": { + "startTime": "2026-02-08T09:15:00Z", + "endTime": "2026-02-08T11:15:00Z" + } + }, + "connector": { + "type": "CCS2", + "maxPowerKW": 60 + }, + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "minChargeLevelPct": 30 + }, + "v2g": { + "enabled": true, + "status": "STANDBY" + }, + "lastUpdated": "2026-02-08T09:03:05Z" + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "ACTIVE", + "version": "1.0.0", + "composedOf": [ + "EV_CHARGING", + "DEMAND_FLEXIBILITY" + ], + "sessionId": "SESSION-V2G-20260208-001", + "reservationConfirmed": true, + "chargerReservedUntil": "2026-02-08T09:30:00Z", + "instructions": "Please plug in your Tata Nexon EV Max (MH-02-AB-1234) at Charger Bay A1, Tata Power EZ Charge Hub, BKC Mumbai by 09:30. V2G is enabled — your vehicle may discharge to grid during peak demand events. You will earn INR 12/kWh for any V2G discharge." + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-v2g-001", + "beckn:amount": { + "currency": "INR", + "value": 163.20 + }, + "beckn:paymentURL": "https://payments.tata-ezcharge.example.com/pay?txn=txn-smart-ev-v2g-001&amount=163.20¤cy=INR", + "beckn:txnRef": "TXN-V2G-20260208-001", + "beckn:paidAt": "2026-02-08T09:03:00Z", + "beckn:beneficiary": "BPP", + "beckn:paymentStatus": "COMPLETED" + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/on_discover.json b/examples/deg_contract/smart_ev_charging/on_discover.json new file mode 100644 index 00000000..fe0f94df --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/on_discover.json @@ -0,0 +1,685 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_discover", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:00:05Z", + "transaction_id": "txn-smart-ev-v2g-001", + "message_id": "msg-on-discover-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "smart-ev-cds.example.com", + "bpp_uri": "https://smart-ev-cds.example.com/pilot/cds/energy/v2", + "ttl": "PT30S" + }, + "message": { + "catalogs": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Catalog", + "beckn:id": "catalog-smart-ev-v2g-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Smart EV Charging with V2G Services", + "beckn:shortDesc": "V2G-capable EV charging network enabling bidirectional energy flow, demand flexibility, and grid services across Mumbai" + }, + "beckn:bppId": "bpp.tata-ezcharge.example.com", + "beckn:bppUri": "https://bpp.tata-ezcharge.example.com/bpp", + "beckn:providerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_provider_context.jsonld", + "@type": "EnergyProvider", + "operatorName": "Tata Power EZ Charge", + "operatorCode": "TATA-EZCHARGE", + "identifier": "tata-ezcharge-mumbai", + "contact": { + "email": "support@tata-ezcharge.com", + "phone": "+91-1800-209-4567" + }, + "registration": { + "gstNumber": "27AABCT1234F1ZR", + "cpoLicense": "MERC/CPO/2025/0042" + } + }, + "beckn:items": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Item", + "beckn:id": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "DC Fast Charger - CCS2 (60kW) V2G-Capable", + "beckn:shortDesc": "Bidirectional DC fast charger with Vehicle-to-Grid capability", + "beckn:longDesc": "60kW bidirectional DC fast charging station supporting CCS2 connector with V2G (Vehicle-to-Grid) capability. Enables charging your EV during normal grid conditions and discharging back to grid during peak demand, earning you money while supporting grid stability." + }, + "beckn:category": { + "@type": "schema:CategoryCode", + "schema:codeValue": "smart-ev-charging", + "schema:name": "Smart EV Charging with V2G" + }, + "beckn:availabilityWindow": [ + { + "@type": "beckn:TimePeriod", + "schema:startTime": "00:00:00", + "schema:endTime": "23:59:59" + } + ], + "beckn:rateable": true, + "beckn:rating": { + "@type": "beckn:Rating", + "beckn:ratingValue": 4.6, + "beckn:ratingCount": 215 + }, + "beckn:isActive": true, + "beckn:provider": { + "beckn:id": "tata-ezcharge-mumbai", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Tata Power EZ Charge" + } + }, + "beckn:itemAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_resource_context.jsonld", + "@type": "EnergyResource", + "resourceType": "EV_CHARGER", + "sourceType": "GRID", + "deliveryMode": "V2G", + "capacity": { + "installed": { "value": 60, "unitCode": "KW" }, + "power": { + "min": { "value": 5, "unitCode": "KW" }, + "max": { "value": 60, "unitCode": "KW" } + } + }, + "connector": { + "type": "CCS2", + "format": "CABLE", + "powerType": "DC", + "chargingSpeed": "FAST" + }, + "metering": { + "evseId": "IN*TATA*BKC*01*CCS2*A", + "meterId": "METER-TATA-BKC-V2G-001" + }, + "capabilities": { + "v2gSupported": true, + "reservationSupported": true, + "vehicleType": "4-WHEELER" + }, + "location": { + "stationId": "IN-TATA-BKC-STATION-01", + "geo": { + "type": "Point", + "coordinates": [72.8614, 19.0596] + }, + "address": { + "streetAddress": "Tata Power EZ Charge Hub, Bandra Kurla Complex", + "addressLocality": "Mumbai", + "addressRegion": "Maharashtra", + "postalCode": "400051", + "addressCountry": "IN" + }, + "parkingType": "OffStreet" + }, + "amenities": ["RESTROOM", "WI-FI", "PARKING", "CAFE"], + "rating": { "value": 4.6, "count": 215 } + } + } + ], + "beckn:offers": [ + { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-smart-ev-v2g-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Smart V2G Charging Plan - CCS2 60kW", + "beckn:shortDesc": "Charge at time-of-day rates, earn money by discharging to grid during peak demand" + }, + "beckn:items": [ + "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G" + ], + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + }, + "beckn:validity": { + "@type": "beckn:TimePeriod", + "schema:startDate": "2026-01-01T00:00:00Z", + "schema:endDate": "2026-06-30T23:59:59Z" + }, + "beckn:acceptedPaymentMethod": [ + "UPI", + "CREDIT_CARD", + "WALLET" + ], + "beckn:offerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "TEMPLATE", + "version": "1.0.0", + "composedOf": [ + "EV_CHARGING", + "DEMAND_FLEXIBILITY" + ], + "roles": [ + { + "roleId": "ev_owner", + "roleType": "PROSUMER", + "party": null, + "inputs": [ + { + "inputId": "vehicle_battery_kwh", + "inputType": "PARAMETER", + "required": true, + "description": "Total battery capacity of the EV in kWh", + "schema": { + "type": "number", + "minimum": 10, + "maximum": 200 + }, + "value": null, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "min_charge_level_pct", + "inputType": "PARAMETER", + "required": true, + "description": "Minimum battery percentage the EV owner wants to retain (V2G will not discharge below this level)", + "schema": { + "type": "number", + "minimum": 20, + "maximum": 80, + "default": 30 + }, + "value": null, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "v2g_consent", + "inputType": "PARAMETER", + "required": true, + "description": "EV owner consents to V2G discharge when grid stress is detected", + "schema": { + "type": "boolean" + }, + "value": null, + "providedAt": "COMMITMENT" + }, + { + "inputId": "vehicle_registration", + "inputType": "CREDENTIAL", + "required": true, + "description": "Vehicle registration number for identification", + "value": null, + "providedAt": "COMMITMENT" + } + ], + "obligations": [ + "charging_payment", + "cancellation" + ] + }, + { + "roleId": "cpo_aggregator", + "roleType": "AGGREGATOR", + "party": { + "partyId": "tata-ezcharge-mumbai", + "partyType": "PROVIDER", + "platformId": "bpp.tata-ezcharge.example.com" + }, + "inputs": [ + { + "inputId": "charger_id", + "inputType": "ITEM_REF", + "required": true, + "value": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "providedAt": "TEMPLATE" + }, + { + "inputId": "connector_type", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "TEMPLATE" + }, + { + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + }, + { + "inputId": "v2g_supported", + "inputType": "PARAMETER", + "required": true, + "value": true, + "providedAt": "TEMPLATE" + } + ], + "obligations": [ + "charging_delivery", + "v2g_payment_to_owner" + ] + }, + { + "roleId": "grid_operator", + "roleType": "GRID_OPERATOR", + "party": { + "partyId": "msedcl-mumbai", + "partyType": "PROVIDER", + "platformId": "bpp.msedcl-grid.example.com" + }, + "inputs": [ + { + "inputId": "grid_frequency_feed", + "inputType": "SIGNAL", + "required": true, + "description": "Real-time grid frequency signal used to detect grid stress", + "value": { + "source": "mqtt://grid-signals.msedcl.example.com:8883/frequency/mumbai", + "protocol": "MQTT", + "sampleRate": "PT5S" + }, + "providedAt": "TEMPLATE" + }, + { + "inputId": "curtailment_signal", + "inputType": "SIGNAL", + "required": true, + "description": "Explicit curtailment/V2G dispatch signal from grid operator", + "value": { + "source": "https://api.msedcl-grid.example.com/v2/curtailment-signals", + "protocol": "HTTP_WEBHOOK", + "sampleRate": "PT1M" + }, + "providedAt": "TEMPLATE" + } + ], + "obligations": [ + "grid_v2g_payment" + ] + } + ], + "terms": [ + { + "termId": "charging_delivery", + "termType": "DELIVERY", + "description": "CPO delivers charging energy to the EV when grid conditions are normal", + "condition": { + "expr": "sessionStatus == 'ACTIVE' AND energyFlowDirection == 'TO_VEHICLE'" + }, + "obligation": { + "expr": "deliverPower(connectorType, maxPowerKW) WITHIN sessionWindow" + }, + "appliesTo": [ + "cpo_aggregator" + ] + }, + { + "termId": "v2g_discharge", + "termType": "SIGNAL_RESPONSE", + "description": "When grid stress is detected AND the EV battery level is above the owner's minimum threshold, discharge energy from EV to grid", + "condition": { + "rule": { + "operator": "AND", + "operands": [ + { + "field": "gridStress", + "value": true + }, + { + "rule": { + "operator": "GT", + "operands": [ + { + "field": "vehicleBatteryPct" + }, + { + "field": "contract.inputs.min_charge_level_pct" + } + ] + } + } + ] + }, + "policyRef": { + "type": "REGO", + "uri": "https://policies.deg-network.io/v2g/v1.rego", + "package": "deg.contracts.v2g", + "entrypoint": "should_discharge" + } + }, + "obligation": { + "expr": "dischargeToGrid(maxDischargeKwh) WHERE maxDischargeKwh = eval('deg.contracts.v2g.max_discharge_kwh')" + }, + "appliesTo": [ + "cpo_aggregator", + "ev_owner" + ] + }, + { + "termId": "v2g_eligibility", + "termType": "QUALITY", + "description": "V2G operations are only permitted when the vehicle and charger support V2G and battery is above minimum level", + "condition": { + "policyRef": { + "type": "REGO", + "uri": "https://policies.deg-network.io/v2g/v1.rego", + "package": "deg.contracts.v2g", + "entrypoint": "v2g_eligible" + } + }, + "obligation": { + "expr": "v2gCapable == true AND v2gSupported == true AND vehicleBatteryPct > minChargeLevelPct" + }, + "appliesTo": [ + "ev_owner", + "cpo_aggregator" + ] + }, + { + "termId": "charging_payment", + "termType": "PAYMENT", + "description": "EV owner pays for charging at time-of-day rates: INR 8/kWh peak (10AM-6PM), INR 5/kWh off-peak (10PM-6AM), INR 6.50/kWh normal hours", + "condition": { + "expr": "chargedKwh > 0" + }, + "obligation": { + "policyRef": { + "type": "REGO", + "uri": "https://policies.deg-network.io/ev_charging/v1.rego", + "package": "deg.contracts.ev_charging", + "entrypoint": "total_charge" + } + }, + "appliesTo": [ + "ev_owner" + ] + }, + { + "termId": "v2g_payment_to_owner", + "termType": "PAYMENT", + "description": "EV owner earns INR 12/kWh for energy discharged to grid during V2G events", + "condition": { + "expr": "dischargedKwh > 0" + }, + "obligation": { + "expr": "pay(ev_owner, dischargedKwh * v2gRate)" + }, + "appliesTo": [ + "cpo_aggregator" + ] + }, + { + "termId": "grid_v2g_payment", + "termType": "PAYMENT", + "description": "Grid operator pays CPO/aggregator INR 18/kWh for V2G grid services provided", + "condition": { + "expr": "dischargedKwh > 0" + }, + "obligation": { + "expr": "pay(cpo_aggregator, dischargedKwh * gridV2gRate)" + }, + "appliesTo": [ + "grid_operator" + ] + }, + { + "termId": "cancellation", + "termType": "CANCELLATION", + "description": "If the EV owner cancels mid-charge, a 30% cancellation fee of the estimated total applies", + "condition": { + "expr": "cancelRequested == true AND sessionStatus == 'ACTIVE'" + }, + "obligation": { + "expr": "cancellationFee = estimatedTotal * 0.30" + }, + "consequence": { + "policyRef": { + "type": "REGO", + "uri": "https://policies.deg-network.io/ev_charging/v1.rego", + "package": "deg.contracts.ev_charging", + "entrypoint": "cancellation_fee" + } + }, + "appliesTo": [ + "ev_owner" + ] + }, + { + "termId": "pricing", + "termType": "PAYMENT", + "description": "Time-of-day pricing schedule for charging", + "condition": { + "expr": "true" + }, + "obligation": { + "rule": { + "operator": "IF_THEN", + "operands": [ + { + "expr": "hour >= 10 AND hour < 18", + "result": { + "chargingRatePerKwh": 8.0, + "period": "PEAK" + } + }, + { + "expr": "hour >= 22 OR hour < 6", + "result": { + "chargingRatePerKwh": 5.0, + "period": "OFF_PEAK" + } + }, + { + "expr": "(hour >= 6 AND hour < 10) OR (hour >= 18 AND hour < 22)", + "result": { + "chargingRatePerKwh": 6.5, + "period": "NORMAL" + } + } + ] + } + }, + "appliesTo": [ + "ev_owner", + "cpo_aggregator" + ] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 60 + }, + "qualityConstraints": { + "voltageRange": { + "min": 300, + "max": 500 + } + } + }, + "fulfillmentSpec": { + "deliveryMode": "V2G", + "deliveryWindow": { + "@type": "beckn:TimePeriod", + "schema:startDate": "2026-02-08T09:00:00Z", + "schema:endDate": "2026-02-08T23:59:59Z" + }, + "location": { + "@type": "beckn:Location", + "geo": { + "type": "Point", + "coordinates": [72.8614, 19.0596] + }, + "address": { + "streetAddress": "Tata Power EZ Charge Hub, Bandra Kurla Complex", + "addressLocality": "Mumbai", + "addressRegion": "Maharashtra", + "postalCode": "400051", + "addressCountry": "IN" + } + }, + "meterIds": [ + "METER-TATA-BKC-V2G-001" + ], + "signals": [ + { + "signalType": "GRID_FREQUENCY", + "source": "mqtt://grid-signals.msedcl.example.com:8883/frequency/mumbai", + "protocol": "MQTT", + "sampleRate": "PT5S" + }, + { + "signalType": "CURTAILMENT_COMMAND", + "source": "https://api.msedcl-grid.example.com/v2/curtailment-signals", + "protocol": "HTTP_WEBHOOK", + "sampleRate": "PT1M" + }, + { + "signalType": "PRICE_SIGNAL", + "source": "mqtt://grid-signals.msedcl.example.com:8883/tod-pricing/mumbai", + "protocol": "MQTT", + "sampleRate": "PT15M" + } + ] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "escrowRequired": false, + "acceptedPaymentMethods": [ + "UPI", + "CREDIT_CARD", + "WALLET" + ] + }, + "revenueModel": { + "definitions": [ + { + "term": "charging_rate", + "description": "Per-kWh charging rate (time-of-day dependent)", + "source": "role:cpo_aggregator.input:charging_rate", + "unit": "INR/kWh" + }, + { + "term": "v2g_rate", + "description": "Per-kWh rate paid to EV owner for V2G discharge", + "source": "constant", + "value": 12.0, + "unit": "INR/kWh" + }, + { + "term": "grid_v2g_rate", + "description": "Per-kWh rate paid by grid operator for V2G grid services", + "source": "constant", + "value": 18.0, + "unit": "INR/kWh" + }, + { + "term": "charged_kwh", + "description": "Total energy delivered to vehicle (charging)", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh" + }, + { + "term": "discharged_kwh", + "description": "Total energy discharged from vehicle to grid (V2G)", + "source": "delivery:energyFlow.discharged.value", + "unit": "kWh" + }, + { + "term": "charging_payment_amount", + "description": "Computed charging payment amount", + "source": "flow:charging_payment", + "unit": "INR" + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate on charging", + "source": "constant", + "value": 0.02, + "unit": "fraction" + } + ], + "flows": [ + { + "flowId": "charging_payment", + "from": "ev_owner", + "to": "cpo_aggregator", + "flowType": "ENERGY_PAYMENT", + "formula": { + "expr": "charged_kwh * charging_rate", + "policyRef": { + "type": "REGO", + "package": "deg.contracts.ev_charging", + "entrypoint": "total_charge" + } + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "charging_payment", + "schedule": "ON_DELIVERY" + }, + { + "flowId": "v2g_payment", + "from": "cpo_aggregator", + "to": "ev_owner", + "flowType": "INCENTIVE", + "formula": { + "expr": "discharged_kwh * v2g_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "v2g_payment_to_owner", + "schedule": "ON_DELIVERY" + }, + { + "flowId": "grid_v2g_payment", + "from": "grid_operator", + "to": "cpo_aggregator", + "flowType": "GRID_SERVICE_FEE", + "formula": { + "expr": "discharged_kwh * grid_v2g_rate" + }, + "currency": "INR", + "computedAmount": null, + "triggeredBy": "grid_v2g_payment", + "schedule": "ON_SETTLEMENT" + }, + { + "flowId": "platform_fee", + "from": "cpo_aggregator", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { + "expr": "charging_payment_amount * platform_fee_pct" + }, + "currency": "INR", + "computedAmount": null, + "schedule": "ON_SETTLEMENT" + } + ], + "netZeroCheck": { + "expr": "sum(flows[*].computedAmount) == 0" + } + } + }, + "beckn:provider": "tata-ezcharge-mumbai" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/on_init.json b/examples/deg_contract/smart_ev_charging/on_init.json new file mode 100644 index 00000000..6efed38c --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/on_init.json @@ -0,0 +1,161 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_init", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:02:05Z", + "message_id": "msg-on-init-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-smart-ev-v2g-001", + "beckn:orderStatus": "CREATED", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma", + "beckn:telephone": "+91-9821234567", + "beckn:email": "ankit.sharma@example.com", + "beckn:taxID": "GSTIN27ABCDS5678K1Z9" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 20 + }, + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 163.20, + "components": [ + { + "type": "UNIT", + "value": 160.0, + "currency": "INR", + "description": "Estimated charging cost (20 kWh x INR 8/kWh peak rate)" + }, + { + "type": "FEE", + "value": 3.20, + "currency": "INR", + "description": "Platform fee (2%)" + } + ] + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "COMMITTED", + "version": "1.0.0", + "v2gTermsSummary": { + "v2gConsent": true, + "minChargeLevelPct": 30, + "vehicleBatteryKwh": 60, + "v2gRatePerKwh": 12.0, + "v2gRateCurrency": "INR", + "maxDischargeKwh": 9.0, + "gridStressTrigger": "Automatic — based on grid frequency and curtailment signals from MSEDCL", + "disclaimer": "V2G discharge is conditional on grid stress events. Earnings are not guaranteed and depend on grid conditions during the charging session." + }, + "cancellationPolicy": { + "midChargeFeePercent": 30, + "description": "30% of estimated total if cancelled during active charging" + }, + "pricingSchedule": { + "chargingRates": [ + { + "period": "PEAK", + "hours": "10:00-18:00", + "ratePerKwh": 8.0, + "currency": "INR" + }, + { + "period": "NORMAL", + "hours": "06:00-10:00, 18:00-22:00", + "ratePerKwh": 6.5, + "currency": "INR" + }, + { + "period": "OFF_PEAK", + "hours": "22:00-06:00", + "ratePerKwh": 5.0, + "currency": "INR" + } + ], + "v2gRate": { + "ratePerKwh": 12.0, + "currency": "INR" + } + } + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-v2g-001", + "beckn:amount": { + "currency": "INR", + "value": 163.20 + }, + "beckn:paymentURL": "https://payments.tata-ezcharge.example.com/pay?txn=txn-smart-ev-v2g-001&amount=163.20¤cy=INR", + "beckn:beneficiary": "BPP", + "beckn:paymentStatus": "AWAITING", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/payment_settlement_context.jsonld", + "@type": "PaymentSettlement", + "paymentNote": "Payment will be adjusted at session end based on actual charging consumed and any V2G earnings. Estimated hold: INR 163.20. Final amount may be lower if V2G events occur.", + "settlementAccounts": [ + { + "beneficiaryId": "bpp.tata-ezcharge.example.com", + "accountHolderName": "Tata Power EZ Charge Pvt Ltd", + "accountNumber": "5432109876543", + "ifscCode": "HDFC0001234", + "bankName": "HDFC Bank", + "vpa": "tataezcharge@hdfcbank" + } + ], + "v2gPayoutAccount": { + "beneficiaryId": "ev-owner-ankit-001", + "accountHolderName": "Ankit Sharma", + "vpa": "ankit.sharma@upi", + "description": "V2G earnings will be credited to this account upon session settlement" + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/on_select.json b/examples/deg_contract/smart_ev_charging/on_select.json new file mode 100644 index 00000000..3f9df001 --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/on_select.json @@ -0,0 +1,193 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_select", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:01:05Z", + "message_id": "msg-on-select-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:orderStatus": "CREATED", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma", + "beckn:telephone": "+91-9821234567", + "beckn:email": "ankit.sharma@example.com" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 20 + }, + "beckn:acceptedOffer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-smart-ev-v2g-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Smart V2G Charging Plan - CCS2 60kW" + }, + "beckn:items": [ + "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G" + ], + "beckn:provider": "tata-ezcharge-mumbai", + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + }, + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 160.0, + "components": [ + { + "type": "UNIT", + "value": 160.0, + "currency": "INR", + "description": "Estimated charging cost (20 kWh x INR 8/kWh peak rate)" + }, + { + "type": "DISCOUNT", + "value": 0.0, + "currency": "INR", + "description": "No discount applied — time-of-day pricing active" + }, + { + "type": "FEE", + "value": 3.20, + "currency": "INR", + "description": "Platform fee (2%)" + } + ], + "estimatedCharging": { + "estimatedKwh": 20, + "estimatedDuration": "PT1H30M", + "currentPricingPeriod": "PEAK", + "currentRate": { + "currency": "INR", + "value": 8.0, + "unitCode": "KWH" + }, + "estimatedCost": { + "currency": "INR", + "value": 160.0 + } + }, + "estimatedV2gEarnings": { + "potentialV2gKwh": 9.0, + "v2gRate": { + "currency": "INR", + "value": 12.0, + "unitCode": "KWH" + }, + "potentialEarnings": { + "currency": "INR", + "value": 108.0 + }, + "note": "V2G earnings depend on grid stress events. The EV has 60 kWh battery at 45% SOC (27 kWh). With 30% minimum (18 kWh), up to 9 kWh is available for V2G discharge. Actual discharge depends on grid conditions." + }, + "estimatedNetCost": { + "bestCase": { + "currency": "INR", + "value": 52.0, + "description": "If full V2G potential realized: INR 160 charging - INR 108 V2G earnings = INR 52" + }, + "worstCase": { + "currency": "INR", + "value": 163.20, + "description": "No V2G events: INR 160 charging + INR 3.20 platform fee = INR 163.20" + } + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "NEGOTIATED", + "version": "1.0.0", + "vehicleProfile": { + "batteryCapacityKwh": 60, + "currentSocPct": 45, + "targetSocPct": 80, + "minChargeLevelPct": 30, + "availableForV2gKwh": 9.0, + "estimatedChargeKwh": 21.0, + "estimatedChargeDuration": "PT1H30M" + }, + "pricingSchedule": { + "chargingRates": [ + { + "period": "PEAK", + "hours": "10:00-18:00", + "ratePerKwh": 8.0, + "currency": "INR" + }, + { + "period": "NORMAL", + "hours": "06:00-10:00, 18:00-22:00", + "ratePerKwh": 6.5, + "currency": "INR" + }, + { + "period": "OFF_PEAK", + "hours": "22:00-06:00", + "ratePerKwh": 5.0, + "currency": "INR" + } + ], + "v2gRate": { + "ratePerKwh": 12.0, + "currency": "INR", + "description": "Paid to EV owner for V2G discharge" + } + }, + "cancellationPolicy": { + "midChargeFeePercent": 30, + "description": "If cancelled while charging is active, 30% of estimated total applies as cancellation fee" + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/on_status_charging.json b/examples/deg_contract/smart_ev_charging/on_status_charging.json new file mode 100644 index 00000000..7db3b7c0 --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/on_status_charging.json @@ -0,0 +1,133 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_status", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:45:00Z", + "message_id": "msg-on-status-charging-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-smart-ev-v2g-001", + "beckn:orderStatus": "INPROGRESS", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G" + } + ], + "beckn:fulfillment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-v2g-001", + "beckn:mode": "RESERVATION", + "trackingAction": { + "@type": "beckn:TrackAction", + "target": { + "@type": "schema:EntryPoint", + "url": "https://track.tata-ezcharge.example.com/session/SESSION-V2G-20260208-001" + } + }, + "beckn:deliveryAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "ACTIVE", + "deliveryMode": "EV_CHARGING", + "energyFlow": { + "direction": "TO_VEHICLE", + "delivered": { "value": 10.1, "unitCode": "KWH" } + }, + "timing": { + "startTime": "2026-02-08T09:20:00Z", + "elapsed": "PT25M" + }, + "connector": { + "type": "CCS2", + "maxPowerKW": 60 + }, + "v2g": { + "enabled": true, + "status": "STANDBY" + }, + "grid": { + "status": "NORMAL" + }, + "telemetry": [ + { + "eventTime": "2026-02-08T09:35:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 52.0, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 55.2, "unitCode": "KW" }, + { "name": "ENERGY_DELIVERED", "value": 4.6, "unitCode": "KWH" }, + { "name": "VOLTAGE", "value": 410, "unitCode": "VLT" }, + { "name": "CURRENT", "value": 134.6, "unitCode": "AMP" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" } + ] + }, + { + "eventTime": "2026-02-08T09:40:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 56.5, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 54.8, "unitCode": "KW" }, + { "name": "ENERGY_DELIVERED", "value": 7.2, "unitCode": "KWH" }, + { "name": "VOLTAGE", "value": 412, "unitCode": "VLT" }, + { "name": "CURRENT", "value": 133.0, "unitCode": "AMP" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" } + ] + }, + { + "eventTime": "2026-02-08T09:45:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 60.8, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 53.5, "unitCode": "KW" }, + { "name": "ENERGY_DELIVERED", "value": 10.1, "unitCode": "KWH" }, + { "name": "VOLTAGE", "value": 408, "unitCode": "VLT" }, + { "name": "CURRENT", "value": 131.1, "unitCode": "AMP" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" } + ] + } + ], + "summary": { + "totalDeliveredKwh": 10.1, + "totalDischargedKwh": 0.0, + "netKwh": 10.1, + "currentPricingPeriod": "PEAK", + "currentRatePerKwh": 8.0, + "runningChargeCost": { + "currency": "INR", + "value": 80.80 + }, + "v2gEventsCount": 0, + "v2gEarnings": { + "currency": "INR", + "value": 0.0 + } + }, + "lastUpdated": "2026-02-08T09:45:00Z" + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "ACTIVE", + "version": "1.0.0" + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/on_status_complete.json b/examples/deg_contract/smart_ev_charging/on_status_complete.json new file mode 100644 index 00000000..f8a3c9ec --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/on_status_complete.json @@ -0,0 +1,630 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_status", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T11:02:00Z", + "message_id": "msg-on-status-complete-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-smart-ev-v2g-001", + "beckn:orderStatus": "COMPLETED", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma", + "beckn:telephone": "+91-9821234567", + "beckn:email": "ankit.sharma@example.com", + "beckn:taxID": "GSTIN27ABCDS5678K1Z9" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 28.0 + }, + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + ], + "beckn:orderValue": { + "currency": "INR", + "value": 170.0, + "components": [ + { + "type": "UNIT", + "value": 224.0, + "currency": "INR", + "description": "Charging cost (28.0 kWh x INR 8/kWh peak rate)" + }, + { + "type": "CREDIT", + "value": -54.0, + "currency": "INR", + "description": "V2G earnings credit (4.5 kWh discharged x INR 12/kWh)" + } + ] + }, + "beckn:fulfillment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-v2g-001", + "beckn:mode": "RESERVATION", + "beckn:deliveryAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "COMPLETED", + "deliveryMode": "V2G", + "energyFlow": { + "delivered": { "value": 28.0, "unitCode": "KWH" }, + "discharged": { "value": 4.5, "unitCode": "KWH" }, + "net": { "value": 23.5, "unitCode": "KWH" } + }, + "timing": { + "startTime": "2026-02-08T09:20:00Z", + "endTime": "2026-02-08T11:00:00Z", + "duration": "PT1H40M" + }, + "connector": { + "type": "CCS2", + "maxPowerKW": 60 + }, + "v2g": { + "enabled": true, + "status": "COMPLETED", + "events": [ + { + "eventId": "V2G-EVENT-20260208-001", + "eventType": "GRID_STRESS_V2G_DISPATCH", + "startTime": "2026-02-08T10:02:00Z", + "endTime": "2026-02-08T10:15:00Z", + "duration": "PT13M", + "triggerSignal": { + "signalType": "GRID_FREQUENCY", + "observedValue": 49.35, + "thresholdValue": 49.5, + "unit": "Hz" + }, + "energyDischargedKwh": 4.5, + "dischargePowerKw": 30.0, + "v2gEarnings": { + "currency": "INR", + "value": 54.0 + }, + "policyEvaluation": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.v2g", + "uri": "https://policies.deg-network.io/v2g/v1.rego" + }, + "results": { + "v2g_eligible": true, + "should_discharge": true, + "max_discharge_kwh": 22.8, + "net_payment": -130.0, + "net_payment_note": "Negative = EV owner still pays net (charged > discharged)" + } + } + } + ] + }, + "grid": { + "status": "NORMAL" + }, + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "minChargeLevelPct": 30 + }, + "telemetry": [ + { + "eventTime": "2026-02-08T09:20:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 25.0, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 55.0, "unitCode": "KW" }, + { "name": "ENERGY_DELIVERED", "value": 0.0, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" } + ] + }, + { + "eventTime": "2026-02-08T09:45:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 40.5, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 54.2, "unitCode": "KW" }, + { "name": "ENERGY_DELIVERED", "value": 7.4, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" } + ] + }, + { + "eventTime": "2026-02-08T10:02:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 48.0, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 0.0, "unitCode": "KW", "note": "V2G event triggered — grid stress detected" }, + { "name": "ENERGY_DELIVERED", "value": 13.8, "unitCode": "KWH" }, + { "name": "GRID_FREQUENCY", "value": 49.35, "unitCode": "HZ" } + ] + }, + { + "eventTime": "2026-02-08T10:05:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 47.0, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": -30.0, "unitCode": "KW", "note": "Negative = discharging to grid" }, + { "name": "ENERGY_DISCHARGED", "value": 1.5, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "FROM_VEHICLE", "unitCode": "DIRECTION" }, + { "name": "GRID_FREQUENCY", "value": 49.42, "unitCode": "HZ" } + ] + }, + { + "eventTime": "2026-02-08T10:15:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 43.5, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": -30.0, "unitCode": "KW" }, + { "name": "ENERGY_DISCHARGED", "value": 4.5, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "FROM_VEHICLE", "unitCode": "DIRECTION" }, + { "name": "GRID_FREQUENCY", "value": 49.48, "unitCode": "HZ", "note": "Grid recovering" } + ] + }, + { + "eventTime": "2026-02-08T10:16:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 43.5, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 54.0, "unitCode": "KW", "note": "V2G event ended, normal charging resumed" }, + { "name": "ENERGY_DELIVERED", "value": 13.8, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" }, + { "name": "GRID_FREQUENCY", "value": 49.52, "unitCode": "HZ" } + ] + }, + { + "eventTime": "2026-02-08T10:45:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 63.2, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 45.8, "unitCode": "KW" }, + { "name": "ENERGY_DELIVERED", "value": 24.6, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" } + ] + }, + { + "eventTime": "2026-02-08T11:00:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 80.0, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 0.0, "unitCode": "KW", "note": "Target SOC reached, session complete" }, + { "name": "ENERGY_DELIVERED", "value": 28.0, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "COMPLETED", "unitCode": "DIRECTION" } + ] + } + ], + "summary": { + "totalDeliveredKwh": 28.0, + "totalDischargedKwh": 4.5, + "netKwh": 23.5, + "duration": "PT1H40M", + "startSOC": 25.0, + "endSOC": 80.0, + "v2gEventsCount": 1, + "v2gTotalDuration": "PT13M", + "pricingPeriodBreakdown": { + "peak": { + "chargedKwh": 28.0, + "ratePerKwh": 8.0, + "subtotal": 224.0 + } + }, + "totalChargingCost": { + "currency": "INR", + "value": 224.0 + }, + "totalV2gEarnings": { + "currency": "INR", + "value": 54.0, + "description": "4.5 kWh x INR 12/kWh V2G rate" + }, + "netPayment": { + "currency": "INR", + "value": 170.0, + "description": "INR 224.00 charging - INR 54.00 V2G earnings = INR 170.00" + } + }, + "lastUpdated": "2026-02-08T11:02:00Z" + } + }, + "beckn:payment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Payment", + "beckn:id": "payment-v2g-001", + "beckn:amount": { + "currency": "INR", + "value": 170.0 + }, + "beckn:paymentURL": "https://payments.tata-ezcharge.example.com/pay?txn=txn-smart-ev-v2g-001&amount=170.00¤cy=INR", + "beckn:txnRef": "TXN-V2G-20260208-001", + "beckn:paidAt": "2026-02-08T11:02:00Z", + "beckn:beneficiary": "BPP", + "beckn:paymentStatus": "COMPLETED", + "beckn:paymentAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/payment_settlement_context.jsonld", + "@type": "PaymentSettlement", + "settlementAccounts": [ + { + "beneficiaryId": "ev-app.example.com", + "accountHolderName": "EV App Solutions Pvt Ltd", + "accountNumber": "5012345678901", + "ifscCode": "ICIC0001234", + "bankName": "ICICI Bank", + "vpa": "evapp@icici" + }, + { + "beneficiaryId": "bpp.tata-ezcharge.example.com", + "accountHolderName": "Tata Power EZ Charge Pvt Ltd", + "accountNumber": "1098765432101", + "ifscCode": "HDFC0005678", + "bankName": "HDFC Bank", + "vpa": "tataezcharge@hdfc" + } + ], + "reconciliation": { + "prepaidAmount": 163.20, + "actualAmount": 170.0, + "additionalDue": 6.80, + "additionalDueStatus": "COLLECTED", + "additionalDueTxnRef": "ADD-TXN-V2G-20260208-001", + "note": "Actual usage (28 kWh) exceeded estimate (20 kWh), but V2G credit (INR 54) reduced net. Additional INR 6.80 collected." + } + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "SETTLED", + "version": "1.0.0", + "composedOf": [ + "EV_CHARGING", + "DEMAND_FLEXIBILITY" + ], + "roles": [ + { + "roleId": "ev_owner", + "roleType": "PROSUMER", + "party": { + "partyId": "ev-owner-ankit-001", + "partyType": "BUYER", + "platformId": "ev-app.example.com" + }, + "inputs": [ + { + "inputId": "vehicle_battery_kwh", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "min_charge_level_pct", + "inputType": "PARAMETER", + "required": true, + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "v2g_consent", + "inputType": "PARAMETER", + "required": true, + "value": true, + "providedAt": "COMMITMENT" + }, + { + "inputId": "vehicle_registration", + "inputType": "CREDENTIAL", + "required": true, + "value": "MH-02-AB-1234", + "providedAt": "COMMITMENT" + } + ], + "obligations": ["charging_payment", "cancellation"] + }, + { + "roleId": "cpo_aggregator", + "roleType": "AGGREGATOR", + "party": { + "partyId": "tata-ezcharge-mumbai", + "partyType": "PROVIDER", + "platformId": "bpp.tata-ezcharge.example.com" + }, + "inputs": [ + { + "inputId": "charger_id", + "inputType": "ITEM_REF", + "required": true, + "value": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "providedAt": "TEMPLATE" + }, + { + "inputId": "connector_type", + "inputType": "PARAMETER", + "required": true, + "value": "CCS2", + "providedAt": "TEMPLATE" + }, + { + "inputId": "max_power_kw", + "inputType": "PARAMETER", + "required": true, + "value": 60, + "providedAt": "TEMPLATE" + }, + { + "inputId": "v2g_supported", + "inputType": "PARAMETER", + "required": true, + "value": true, + "providedAt": "TEMPLATE" + } + ], + "obligations": ["charging_delivery", "v2g_payment_to_owner"] + }, + { + "roleId": "grid_operator", + "roleType": "GRID_OPERATOR", + "party": { + "partyId": "msedcl-mumbai", + "partyType": "PROVIDER", + "platformId": "bpp.msedcl-grid.example.com" + }, + "inputs": [ + { + "inputId": "grid_frequency_feed", + "inputType": "SIGNAL", + "required": true, + "value": { + "source": "mqtt://grid-signals.msedcl.example.com:8883/frequency/mumbai", + "protocol": "MQTT", + "sampleRate": "PT5S" + }, + "providedAt": "TEMPLATE" + }, + { + "inputId": "curtailment_signal", + "inputType": "SIGNAL", + "required": true, + "value": { + "source": "https://api.msedcl-grid.example.com/v2/curtailment-signals", + "protocol": "HTTP_WEBHOOK", + "sampleRate": "PT1M" + }, + "providedAt": "TEMPLATE" + } + ], + "obligations": ["grid_v2g_payment"] + } + ], + "terms": [ + { + "termId": "charging_delivery", + "termType": "DELIVERY", + "description": "CPO delivered 28.0 kWh of charging energy over 1h40m session. Target SOC of 80% reached.", + "condition": { "expr": "sessionStatus == 'COMPLETED'" }, + "obligation": { "expr": "deliveredKwh >= requestedKwh OR targetSOC reached" }, + "appliesTo": ["cpo_aggregator"] + }, + { + "termId": "v2g_discharge", + "termType": "SIGNAL_RESPONSE", + "description": "V2G event dispatched at 10:02. Grid frequency dropped to 49.35 Hz. Vehicle discharged 4.5 kWh over 13 minutes at 30 kW. Grid recovered to 49.52 Hz.", + "condition": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.v2g", + "entrypoint": "should_discharge" + } + }, + "obligation": { "expr": "dischargeToGrid(4.5 kWh) — completed successfully" }, + "appliesTo": ["cpo_aggregator", "ev_owner"] + }, + { + "termId": "charging_payment", + "termType": "PAYMENT", + "description": "EV owner charged 28.0 kWh at peak rate INR 8/kWh = INR 224.00", + "condition": { "expr": "chargedKwh > 0" }, + "obligation": { "expr": "payment.chargingAmount == 224.00" }, + "appliesTo": ["ev_owner"] + }, + { + "termId": "v2g_payment_to_owner", + "termType": "PAYMENT", + "description": "EV owner earned INR 54.00 for 4.5 kWh V2G discharge at INR 12/kWh", + "condition": { "expr": "dischargedKwh > 0" }, + "obligation": { "expr": "v2gCredit == 4.5 * 12.0 == 54.00" }, + "appliesTo": ["cpo_aggregator"] + }, + { + "termId": "grid_v2g_payment", + "termType": "PAYMENT", + "description": "Grid operator pays CPO INR 81.00 for V2G grid services (4.5 kWh x INR 18/kWh)", + "condition": { "expr": "dischargedKwh > 0" }, + "obligation": { "expr": "gridPayment == 4.5 * 18.0 == 81.00" }, + "appliesTo": ["grid_operator"] + } + ], + "energySpec": { + "energyType": "ELECTRICAL", + "sourceType": "GRID", + "quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 28.0, + "note": "Total energy charged to vehicle" + }, + "v2gDischarge": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 4.5, + "note": "Total energy discharged from vehicle to grid" + }, + "netEnergy": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 23.5, + "note": "Net energy to vehicle (charged - discharged)" + } + }, + "fulfillmentSpec": { + "deliveryMode": "V2G", + "deliveryWindow": { + "startTime": "2026-02-08T09:20:00Z", + "endTime": "2026-02-08T11:00:00Z" + }, + "location": { + "geo": { "type": "Point", "coordinates": [72.8614, 19.0596] } + }, + "meterIds": ["METER-TATA-BKC-V2G-001"] + }, + "settlementSpec": { + "paymentType": "POST_DELIVERY", + "currency": "INR", + "settlementCycle": "IMMEDIATE", + "acceptedPaymentMethods": ["UPI", "CREDIT_CARD", "WALLET"] + }, + "revenueModel": { + "definitions": [ + { + "term": "charging_rate", + "description": "Per-kWh charging rate (time-of-day dependent)", + "source": "role:cpo_aggregator.input:charging_rate", + "unit": "INR/kWh", + "resolvedValue": 8.0 + }, + { + "term": "v2g_rate", + "description": "Per-kWh rate paid to EV owner for V2G discharge", + "source": "constant", + "value": 12.0, + "unit": "INR/kWh", + "resolvedValue": 12.0 + }, + { + "term": "grid_v2g_rate", + "description": "Per-kWh rate paid by grid operator for V2G grid services", + "source": "constant", + "value": 18.0, + "unit": "INR/kWh", + "resolvedValue": 18.0 + }, + { + "term": "charged_kwh", + "description": "Total energy delivered to vehicle (charging)", + "source": "delivery:energyFlow.delivered.value", + "unit": "kWh", + "resolvedValue": 28.0 + }, + { + "term": "discharged_kwh", + "description": "Total energy discharged from vehicle to grid (V2G)", + "source": "delivery:energyFlow.discharged.value", + "unit": "kWh", + "resolvedValue": 4.5 + }, + { + "term": "charging_payment_amount", + "description": "Computed charging payment amount", + "source": "flow:charging_payment", + "unit": "INR", + "resolvedValue": 224.0 + }, + { + "term": "platform_fee_pct", + "description": "Platform commission rate on charging", + "source": "constant", + "value": 0.02, + "unit": "fraction", + "resolvedValue": 0.02 + } + ], + "flows": [ + { + "flowId": "charging_payment", + "from": "ev_owner", + "to": "cpo_aggregator", + "flowType": "ENERGY_PAYMENT", + "formula": { "expr": "charged_kwh * charging_rate" }, + "currency": "INR", + "computedAmount": 224.0, + "triggeredBy": "charging_payment", + "schedule": "ON_DELIVERY" + }, + { + "flowId": "v2g_payment", + "from": "cpo_aggregator", + "to": "ev_owner", + "flowType": "INCENTIVE", + "formula": { "expr": "discharged_kwh * v2g_rate" }, + "currency": "INR", + "computedAmount": 54.0, + "triggeredBy": "v2g_payment_to_owner", + "schedule": "ON_DELIVERY" + }, + { + "flowId": "grid_v2g_payment", + "from": "grid_operator", + "to": "cpo_aggregator", + "flowType": "GRID_SERVICE_FEE", + "formula": { "expr": "discharged_kwh * grid_v2g_rate" }, + "currency": "INR", + "computedAmount": 81.0, + "triggeredBy": "grid_v2g_payment", + "schedule": "ON_SETTLEMENT" + }, + { + "flowId": "platform_fee", + "from": "cpo_aggregator", + "to": "platform", + "flowType": "PLATFORM_FEE", + "formula": { "expr": "charging_payment_amount * platform_fee_pct" }, + "currency": "INR", + "computedAmount": 4.48, + "schedule": "ON_SETTLEMENT" + } + ], + "netPositions": { + "ev_owner": { "net": -170.00, "direction": "PAYS" }, + "cpo_aggregator": { "net": 246.52, "direction": "RECEIVES" }, + "grid_operator": { "net": -81.00, "direction": "PAYS" }, + "platform": { "net": 4.48, "direction": "RECEIVES" } + }, + "netZeroCheck": { "sum": 0.00, "valid": true } + } + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/on_update_v2g_event.json b/examples/deg_contract/smart_ev_charging/on_update_v2g_event.json new file mode 100644 index 00000000..2d0f2719 --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/on_update_v2g_event.json @@ -0,0 +1,202 @@ +{ + "context": { + "version": "2.0.0", + "action": "on_update", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T10:05:00Z", + "message_id": "msg-on-update-v2g-event-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:id": "order-smart-ev-v2g-001", + "beckn:orderStatus": "INPROGRESS", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma" + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G" + } + ], + "beckn:fulfillment": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Fulfillment", + "beckn:id": "fulfillment-v2g-001", + "beckn:mode": "RESERVATION", + "beckn:deliveryAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_delivery_context.jsonld", + "@type": "EnergyDelivery", + "status": "ACTIVE", + "deliveryMode": "V2G", + "energyFlow": { + "direction": "FROM_VEHICLE", + "delivered": { "value": 13.8, "unitCode": "KWH" }, + "discharged": { "value": 1.5, "unitCode": "KWH" }, + "net": { "value": 12.3, "unitCode": "KWH" } + }, + "timing": { + "startTime": "2026-02-08T09:20:00Z", + "elapsed": "PT45M" + }, + "connector": { + "type": "CCS2", + "maxPowerKW": 60 + }, + "v2g": { + "enabled": true, + "status": "DISCHARGING", + "events": [ + { + "eventId": "V2G-EVENT-20260208-001", + "eventType": "GRID_STRESS_V2G_DISPATCH", + "triggeredAt": "2026-02-08T10:02:00Z", + "triggerSource": "MSEDCL Grid Frequency Monitor", + "triggerSignal": { + "signalType": "GRID_FREQUENCY", + "observedValue": 49.35, + "thresholdValue": 49.5, + "unit": "Hz", + "source": "mqtt://grid-signals.msedcl.example.com:8883/frequency/mumbai" + }, + "curtailmentSignal": { + "signalType": "CURTAILMENT_COMMAND", + "status": "ACTIVE", + "dispatchedAt": "2026-02-08T10:02:15Z", + "requestedCapacityKw": 30, + "source": "https://api.msedcl-grid.example.com/v2/curtailment-signals" + }, + "policyEvaluation": { + "policyRef": { + "type": "REGO", + "package": "deg.contracts.v2g", + "uri": "https://policies.deg-network.io/v2g/v1.rego" + }, + "evaluatedAt": "2026-02-08T10:02:20Z", + "results": { + "v2g_eligible": { + "entrypoint": "v2g_eligible", + "result": true, + "inputs": { + "vehicleBatteryPct": 68.0, + "v2gCapable": true, + "v2gSupported": true, + "minChargeLevelPct": 30 + } + }, + "should_discharge": { + "entrypoint": "should_discharge", + "result": true, + "inputs": { + "gridStress": true, + "vehicleBatteryPct": 68.0, + "minChargeLevelPct": 30 + } + }, + "max_discharge_kwh": { + "entrypoint": "max_discharge_kwh", + "result": 22.8, + "inputs": { + "vehicleBatteryKwh": 60, + "vehicleBatteryPct": 68.0, + "minChargeLevelPct": 30 + }, + "calculation": "currentKwh (40.8) - minKwh (18.0) = 22.8 kWh available for discharge" + } + } + } + } + ] + }, + "grid": { + "status": "STRESSED" + }, + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "minChargeLevelPct": 30 + }, + "telemetry": [ + { + "eventTime": "2026-02-08T10:00:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 68.0, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": 48.2, "unitCode": "KW" }, + { "name": "ENERGY_DELIVERED", "value": 13.8, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "TO_VEHICLE", "unitCode": "DIRECTION" } + ] + }, + { + "eventTime": "2026-02-08T10:02:30Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 67.8, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": -30.0, "unitCode": "KW", "note": "Negative power = discharging from vehicle to grid" }, + { "name": "ENERGY_DISCHARGED", "value": 0.25, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "FROM_VEHICLE", "unitCode": "DIRECTION" }, + { "name": "GRID_FREQUENCY", "value": 49.38, "unitCode": "HZ" } + ] + }, + { + "eventTime": "2026-02-08T10:05:00Z", + "metrics": [ + { "name": "STATE_OF_CHARGE", "value": 67.0, "unitCode": "PERCENTAGE" }, + { "name": "POWER", "value": -30.0, "unitCode": "KW" }, + { "name": "ENERGY_DISCHARGED", "value": 1.5, "unitCode": "KWH" }, + { "name": "ENERGY_FLOW_DIRECTION", "value": "FROM_VEHICLE", "unitCode": "DIRECTION" }, + { "name": "GRID_FREQUENCY", "value": 49.42, "unitCode": "HZ" } + ] + } + ], + "summary": { + "totalDeliveredKwh": 13.8, + "totalDischargedKwh": 1.5, + "netKwh": 12.3, + "duration": "PT45M", + "v2gEventsCount": 1, + "v2gEventActive": true, + "v2gEventDuration": "PT3M", + "currentPricingPeriod": "PEAK", + "currentRatePerKwh": 8.0, + "runningChargeCost": { + "currency": "INR", + "value": 110.40 + }, + "v2gEarnings": { + "currency": "INR", + "value": 18.0, + "description": "1.5 kWh x INR 12/kWh (V2G event in progress)" + }, + "runningNetCost": { + "currency": "INR", + "value": 92.40, + "description": "INR 110.40 charging - INR 18.00 V2G earnings" + } + }, + "lastUpdated": "2026-02-08T10:05:00Z" + } + }, + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "ACTIVE", + "version": "1.0.0", + "updateType": "V2G_EVENT_ACTIVATED", + "updateMessage": "Grid stress detected (frequency: 49.35 Hz < 49.5 Hz threshold). V2G discharge activated. Your Tata Nexon EV Max is discharging at 30 kW to support grid stability. Current SOC: 67%. You are earning INR 12/kWh. Discharge will stop when grid stabilizes or battery reaches 30% minimum." + } + } + } +} \ No newline at end of file diff --git a/examples/deg_contract/smart_ev_charging/select.json b/examples/deg_contract/smart_ev_charging/select.json new file mode 100644 index 00000000..65c7bba6 --- /dev/null +++ b/examples/deg_contract/smart_ev_charging/select.json @@ -0,0 +1,130 @@ +{ + "context": { + "version": "2.0.0", + "action": "select", + "domain": "beckn.one:deg:smart-ev-charging:2.0.0", + "timestamp": "2026-02-08T09:01:00Z", + "message_id": "msg-select-v2g-001", + "transaction_id": "txn-smart-ev-v2g-001", + "bap_id": "ev-app.example.com", + "bap_uri": "https://ev-app.example.com/bap", + "bpp_id": "bpp.tata-ezcharge.example.com", + "bpp_uri": "https://bpp.tata-ezcharge.example.com/bpp", + "ttl": "PT30S" + }, + "message": { + "order": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Order", + "beckn:orderStatus": "CREATED", + "beckn:seller": "tata-ezcharge-mumbai", + "beckn:buyer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Buyer", + "beckn:id": "ev-owner-ankit-001", + "beckn:role": "BUYER", + "beckn:displayName": "Ankit Sharma", + "beckn:telephone": "+91-9821234567", + "beckn:email": "ankit.sharma@example.com" + }, + "beckn:buyerAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/energy_buyer_context.jsonld", + "@type": "EnergyBuyer", + "vehicle": { + "registration": "MH-02-AB-1234", + "makeModel": "Tata Nexon EV Max", + "batteryKwh": 60, + "connectorType": "CCS2" + } + }, + "beckn:orderItems": [ + { + "beckn:orderedItem": "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G", + "beckn:quantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 20 + }, + "beckn:acceptedOffer": { + "@context": "https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/context.jsonld", + "@type": "beckn:Offer", + "beckn:id": "offer-smart-ev-v2g-001", + "beckn:descriptor": { + "@type": "beckn:Descriptor", + "schema:name": "Smart V2G Charging Plan - CCS2 60kW" + }, + "beckn:items": [ + "IND*tata-ezcharge*cs-mum-01*IN*TATA*BKC*01*CCS2*A*V2G" + ], + "beckn:provider": "tata-ezcharge-mumbai", + "beckn:price": { + "currency": "INR", + "value": 8.0, + "applicableQuantity": { + "unitText": "Kilowatt Hour", + "unitCode": "KWH", + "unitQuantity": 1 + } + } + } + } + ], + "beckn:orderAttributes": { + "@context": "https://raw.githubusercontent.com/beckn/DEG/refs/heads/main/specification/schema/deg_contract_context.jsonld", + "@type": "DEGContract", + "contractType": "COMPOSITE", + "contractState": "NEGOTIATED", + "version": "1.0.0", + "roles": [ + { + "roleId": "ev_owner", + "roleType": "PROSUMER", + "party": { + "partyId": "ev-owner-ankit-001", + "partyType": "BUYER", + "platformId": "ev-app.example.com" + }, + "inputs": [ + { + "inputId": "vehicle_battery_kwh", + "inputType": "PARAMETER", + "required": true, + "description": "Total battery capacity of the EV in kWh", + "value": 60, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "min_charge_level_pct", + "inputType": "PARAMETER", + "required": true, + "description": "Minimum battery percentage to retain — V2G will not discharge below this", + "value": 30, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "current_soc_pct", + "inputType": "PARAMETER", + "required": false, + "description": "Current state of charge of the vehicle battery", + "value": 45, + "providedAt": "NEGOTIATION" + }, + { + "inputId": "target_soc_pct", + "inputType": "PARAMETER", + "required": false, + "description": "Desired state of charge when session ends", + "value": 80, + "providedAt": "NEGOTIATION" + } + ] + } + ], + "preferences": { + "startTime": "2026-02-08T09:15:00Z", + "endTime": "2026-02-08T11:15:00Z" + } + } + } + } +} \ No newline at end of file diff --git a/specification/deg_contract_beckn_flow.arazzo.yaml b/specification/deg_contract_beckn_flow.arazzo.yaml new file mode 100644 index 00000000..bc4d3cd9 --- /dev/null +++ b/specification/deg_contract_beckn_flow.arazzo.yaml @@ -0,0 +1,1067 @@ +arazzo: 1.0.1 +info: + title: DEG Contract — Full Beckn Transaction Flow + version: 0.1.0 + description: > + Arazzo workflow specification for testing a complete Beckn Protocol v2 + transaction flow using the DEG Contract-as-Code schema. Covers three + use cases: EV Charging, P2P Inter-Discom Trading, and Smart EV Charging + (composite EV + Demand Flexibility / V2G). + + Each workflow describes the sequence of Beckn actions (search → confirm → status) + with the DEGContract flowing through as offerAttributes (template) → + orderAttributes (committed) → settlement (with computed revenue flows). + + summary: End-to-end Beckn transaction workflows for DEG energy contracts + +# ─────────────────────────────────────────────────────────────────────── +# Source Descriptions — API endpoints used in workflows +# ─────────────────────────────────────────────────────────────────────── +sourceDescriptions: + - name: beckn-bap + url: https://ev-bap.mobility-app.in/beckn/v2 + type: openapi + x-description: > + Beckn Application Platform (BAP) — consumer-facing app. + Sends search, select, init, confirm, status, update, cancel. + + - name: beckn-bpp + url: https://api.chargezone-bpp.in/beckn/v2 + type: openapi + x-description: > + Beckn Provider Platform (BPP) — provider-side. + Responds with on_search, on_select, on_init, on_confirm, on_status, on_update. + + - name: beckn-bpp-utility + url: https://api.bescom-utility-bpp.in/beckn/v2 + type: openapi + x-description: > + Utility BPP — for cascaded init in P2P inter-discom trading. + The P2P BPP acts as BAP and forwards init to the utility. + + - name: opa-policy-engine + url: https://opa.deg-network.io/v1 + type: openapi + x-description: > + OPA (Open Policy Agent) endpoint for evaluating Rego policies. + Used for contract term evaluation, compliance checks, and settlement. + +# ─────────────────────────────────────────────────────────────────────── +# WORKFLOW 1: EV Charging with DEG Contract +# ─────────────────────────────────────────────────────────────────────── +workflows: + - workflowId: ev-charging-flow + summary: Complete EV charging transaction with DEG contract + description: > + BAP discovers charging stations, selects an offer with embedded + DEGContract template, fills consumer role inputs, commits, and tracks + the charging session through to settlement with computed revenue flows. + inputs: + type: object + properties: + searchLocation: + type: object + description: Geographic search area + properties: + lat: { type: number, example: 19.076 } + lon: { type: number, example: 72.8777 } + radius: { type: string, example: "5km" } + connectorType: + type: string + example: "CCS2" + requestedKwh: + type: number + example: 15.0 + vehicleConnectorType: + type: string + example: "CCS2" + + steps: + # ── Step 1: Discover charging stations ── + - stepId: discover + operationId: beckn-bap.search + description: > + BAP sends search with location filter and connector type. + Filter uses JSONPath over itemAttributes. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: search + domain: "beckn.one:deg:ev-charging:2.0.0" + bap_id: "ev-bap.mobility-app.in" + bap_uri: "https://ev-bap.mobility-app.in/beckn/v2" + transaction_id: "{$generate.uuid}" + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + filters: + type: jsonpath + expression: > + $[?(@.itemAttributes.connectorType == '{$inputs.connectorType}' + && @.itemAttributes.maxPowerKW >= 22)] + successCriteria: + - condition: $statusCode == 200 + outputs: + transactionId: $response.body.context.transaction_id + + # ── Step 2: Receive catalog with DEGContract templates ── + - stepId: on-discover + operationId: beckn-bpp.on_search + description: > + BPP returns catalog. Each Offer contains a DEGContract in TEMPLATE + state with CPO role pre-filled and ev_driver role as open slot. + The revenueModel shows estimated flows. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.catalogs[0].beckn:offers[0].beckn:offerAttributes.degContract.contractState == 'TEMPLATE' + - condition: $response.body.message.catalogs[0].beckn:offers[0].beckn:offerAttributes.degContract.roles[?(@.roleId=='ev_driver')].party == null + outputs: + catalogId: $response.body.message.catalogs[0].beckn:id + selectedOfferId: $response.body.message.catalogs[0].beckn:offers[0].beckn:id + selectedItemId: $response.body.message.catalogs[0].beckn:items[0].beckn:id + contractTemplate: $response.body.message.catalogs[0].beckn:offers[0].beckn:offerAttributes.degContract + + # ── Step 3: Select offer and fill buyer inputs ── + - stepId: select + operationId: beckn-bap.select + description: > + BAP selects offer and fills ev_driver role inputs: + requested_kwh, vehicle connector type, preferred time window. + DEGContract transitions to NEGOTIATED. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: select + domain: "beckn.one:deg:ev-charging:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + "@type": "beckn:Order" + beckn:orderStatus: CREATED + beckn:buyer: + beckn:id: "buyer-ev-001" + beckn:orderItems: + - beckn:orderedItem: $steps.on-discover.outputs.selectedItemId + beckn:quantity: + unitQuantity: $inputs.requestedKwh + unitText: "kWh" + beckn:acceptedOffer: + beckn:id: $steps.on-discover.outputs.selectedOfferId + beckn:orderAttributes: + degContract: + contractState: NEGOTIATED + roles: + - roleId: ev_driver + inputs: + - inputId: requested_kwh + value: $inputs.requestedKwh + - inputId: vehicle_connector_type + value: $inputs.vehicleConnectorType + successCriteria: + - condition: $statusCode == 200 + + # ── Step 4: Receive quote ── + - stepId: on-select + operationId: beckn-bpp.on_select + description: > + BPP returns price quote with breakup. + Evaluates pricing terms and returns orderValue components. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:orderValue.value > 0 + outputs: + quotedAmount: $response.body.message.order.beckn:orderValue.value + quoteCurrency: $response.body.message.order.beckn:orderValue.currency + + # ── Step 5: Initialize order with payment and credentials ── + - stepId: init + operationId: beckn-bap.init + description: > + BAP provides billing info, payment settlement accounts. + DEGContract transitions to COMMITTED. + All required COMMITMENT-phase inputs must be filled. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: init + domain: "beckn.one:deg:ev-charging:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:payment: + beckn:amount: + currency: $steps.on-select.outputs.quoteCurrency + value: $steps.on-select.outputs.quotedAmount + beckn:beneficiary: BPP + beckn:paymentStatus: INITIATED + beckn:paymentAttributes: + "@type": PaymentSettlement + settlementAccounts: + - beneficiaryId: "ev-bap.mobility-app.in" + accountHolderName: "EV Mobility App Pvt Ltd" + bankName: "HDFC Bank" + ifscCode: "HDFC0001234" + vpa: "evmobility@upi" + beckn:orderAttributes: + degContract: + contractState: COMMITTED + roles: + - roleId: ev_driver + inputs: + - inputId: billing_info + value: + name: "Raj Kumar" + email: "raj@example.com" + phone: "+91-9876543210" + successCriteria: + - condition: $statusCode == 200 + + # ── Step 6: Receive payment link ── + - stepId: on-init + operationId: beckn-bpp.on_init + description: > + BPP assigns order ID, returns payment URL and final contract terms. + DEGContract is fully committed with all roles and terms finalized. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:payment.beckn:paymentURL != null + - condition: $response.body.message.order.beckn:orderAttributes.degContract.contractState == 'COMMITTED' + outputs: + orderId: $response.body.message.order.beckn:id + paymentUrl: $response.body.message.order.beckn:payment.beckn:paymentURL + + # ── Step 7: Confirm (after payment) ── + - stepId: confirm + operationId: beckn-bap.confirm + description: > + BAP confirms order after payment is completed. + DEGContract transitions to ACTIVE. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: confirm + domain: "beckn.one:deg:ev-charging:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:id: $steps.on-init.outputs.orderId + beckn:payment: + beckn:paymentStatus: COMPLETED + beckn:paidAt: "{$generate.timestamp}" + successCriteria: + - condition: $statusCode == 200 + + # ── Step 8: Receive confirmation ── + - stepId: on-confirm + operationId: beckn-bpp.on_confirm + description: > + BPP confirms the order. Fulfillment is created with RESERVATION mode. + ChargingSession deliveryAttributes initialized with sessionStatus PENDING. + DEGContract is now ACTIVE. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:orderStatus == 'CONFIRMED' + - condition: $response.body.message.order.beckn:orderAttributes.degContract.contractState == 'ACTIVE' + - condition: $response.body.message.order.beckn:fulfillment.beckn:deliveryAttributes.sessionStatus == 'PENDING' + outputs: + fulfillmentId: $response.body.message.order.beckn:fulfillment.beckn:id + + # ── Step 9: Track charging session ── + - stepId: status-tracking + operationId: beckn-bap.status + description: > + BAP polls for charging session status. + BPP returns ChargingSession telemetry (power, energy, SOC). + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: status + domain: "beckn.one:deg:ev-charging:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:id: $steps.on-init.outputs.orderId + successCriteria: + - condition: $statusCode == 200 + + # ── Step 10: Receive session status ── + - stepId: on-status + operationId: beckn-bpp.on_status + description: > + BPP returns current session state with telemetry metrics. + ChargingSession shows ACTIVE with real-time power, energy, SOC. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:fulfillment.beckn:deliveryAttributes.sessionStatus == 'ACTIVE' + outputs: + deliveredKwh: $response.body.message.order.beckn:fulfillment.beckn:deliveryAttributes.energyDeliveredKwh + sessionStatus: $response.body.message.order.beckn:fulfillment.beckn:deliveryAttributes.sessionStatus + + # ── Step 11: Evaluate contract compliance (OPA) ── + - stepId: evaluate-compliance + operationId: opa-policy-engine.evaluate + description: > + Evaluate delivery compliance using Rego policy. + Checks delivery_quantity_compliant and deviation_penalty. + requestBody: + contentType: application/json + payload: + input: + deliveredQuantity: $steps.on-status.outputs.deliveredKwh + fulfillmentState: "charging-complete" + data: + contract: $steps.on-confirm.outputs + successCriteria: + - condition: $statusCode == 200 + outputs: + deliveryCompliant: $response.body.result.delivery_compliant + deviationPenalty: $response.body.result.deviation_penalty + + # ── Step 12: Session complete with settlement ── + - stepId: on-update-complete + operationId: beckn-bpp.on_update + description: > + BPP sends final update: session COMPLETED. + revenueModel.flows have computedAmount filled. + Settlement summary shows per-role net positions. + DEGContract transitions to SETTLED. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:fulfillment.beckn:deliveryAttributes.sessionStatus == 'COMPLETED' + - condition: $response.body.message.order.beckn:orderAttributes.degContract.contractState == 'SETTLED' + - condition: $response.body.message.order.beckn:orderAttributes.degContract.revenueModel.netZeroValid == true + outputs: + finalAmount: $response.body.message.order.beckn:payment.beckn:amount.value + settlementSummary: $response.body.message.order.beckn:orderAttributes.degContract.revenueModel + + # ─────────────────────────────────────────────────────────────────────── + # WORKFLOW 2: P2P Inter-Discom Energy Trading with DEG Contract + # ─────────────────────────────────────────────────────────────────────── + - workflowId: p2p-interdiscom-trading-flow + summary: Inter-discom P2P energy trade with cascaded utility init + description: > + A prosumer (solar farm under TPDDL) sells energy to a consumer + (under BESCOM). The P2P BPP cascades init to both utilities for + wheeling approval and sanctioned load checks. DEGContract tracks + all three roles (seller, buyer, utility) and their revenue positions. + inputs: + type: object + properties: + sourceType: + type: string + example: "SOLAR" + requestedKwh: + type: number + example: 25.0 + buyerMeterId: + type: string + example: "der://meter/98765456" + buyerUtilityId: + type: string + example: "BESCOM-KA" + + steps: + # ── Step 1: Search for energy offers ── + - stepId: discover + operationId: beckn-bap.search + description: > + BAP searches for solar energy offers. + Filter: sourceType SOLAR, deliveryMode GRID_INJECTION, minimum quantity. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: search + domain: "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + bap_id: "bap.energy-consumer.com" + bap_uri: "https://bap.energy-consumer.com" + transaction_id: "{$generate.uuid}" + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + location: + city: + code: "DL" + name: "Delhi" + country: + code: "IND" + message: + filters: + type: jsonpath + expression: > + $[?(@.itemAttributes.sourceType == 'SOLAR' + && @.itemAttributes.deliveryMode == 'GRID_INJECTION')] + successCriteria: + - condition: $statusCode == 200 + outputs: + transactionId: $response.body.context.transaction_id + + # ── Step 2: Catalog with DEGContract template ── + - stepId: on-discover + operationId: beckn-bpp.on_search + description: > + BPP returns energy catalog. Offers contain DEGContract templates + with seller role filled, buyer and utility roles open. + EnergyResource itemAttributes and EnergyTradeOffer preserved. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.catalogs[0].beckn:offers[0].beckn:offerAttributes.degContract.contractType == 'P2P_ENERGY_TRADE' + outputs: + selectedOfferId: $response.body.message.catalogs[0].beckn:offers[0].beckn:id + selectedItemId: $response.body.message.catalogs[0].beckn:items[0].beckn:id + + # ── Step 3: Select offer with buyer inputs ── + - stepId: select + operationId: beckn-bap.select + description: > + Buyer selects offer, provides destination meter and requested quantity. + DEGContract: buyer role inputs filled, state → NEGOTIATED. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: select + domain: "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:orderItems: + - beckn:orderedItem: $steps.on-discover.outputs.selectedItemId + beckn:quantity: + unitQuantity: $inputs.requestedKwh + unitText: "kWh" + beckn:acceptedOffer: + beckn:id: $steps.on-discover.outputs.selectedOfferId + beckn:orderAttributes: + degContract: + contractState: NEGOTIATED + roles: + - roleId: buyer + inputs: + - inputId: destination_meter + value: $inputs.buyerMeterId + - inputId: requested_kwh + value: $inputs.requestedKwh + successCriteria: + - condition: $statusCode == 200 + + # ── Step 4: Quote ── + - stepId: on-select + operationId: beckn-bpp.on_select + description: > + BPP returns quote: energy cost + estimated wheeling charges. + successCriteria: + - condition: $statusCode == 200 + outputs: + quotedTotal: $response.body.message.order.beckn:quote.beckn:price.schema:price + + # ── Step 5: Init with buyer credentials ── + - stepId: init + operationId: beckn-bap.init + description: > + Buyer provides EnergyCustomer attributes (meterId, utilityId), + billing info, and payment settlement accounts. + DEGContract state → COMMITTED. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: init + domain: "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:buyer: + beckn:buyerAttributes: + "@type": EnergyCustomer + meterId: $inputs.buyerMeterId + utilityId: $inputs.buyerUtilityId + utilityCustomerId: "BESCOM-CUST-001" + beckn:orderAttributes: + degContract: + contractState: COMMITTED + beckn:payment: + beckn:paymentStatus: INITIATED + beckn:billing: + beckn:name: "Energy Consumer" + beckn:email: "consumer@example.com" + beckn:phone: "+91-9876543210" + successCriteria: + - condition: $statusCode == 200 + + # ── Step 6: BPP returns init response ── + - stepId: on-init + operationId: beckn-bpp.on_init + description: > + BPP acknowledges init, assigns order ID. + EnergyTradeOrder in orderAttributes alongside DEGContract. + successCriteria: + - condition: $statusCode == 200 + outputs: + orderId: $response.body.message.order.beckn:id + + # ── Step 7: Cascaded init to source utility ── + - stepId: cascaded-init-source + operationId: beckn-bpp.init + description: > + P2P BPP (acting as BAP) forwards init to source utility (TPDDL). + Checks sanctioned load for the source meter. + Uses Rego policy: deg.contracts.p2p_trade.sanctioned_load_check + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: init + domain: "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + bap_id: "bpp.energy-provider.com" + bap_uri: "https://bpp.energy-provider.com/bap" + bpp_id: "bpp.tpddl-utility.in" + bpp_uri: "https://api.tpddl-utility.in/beckn/v2" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:orderAttributes: + "@type": EnergyTradeOrder + sourceMeterId: "der://meter/100200300" + targetMeterId: $inputs.buyerMeterId + contractedQuantity: $inputs.requestedKwh + beckn:fulfillment: + beckn:type: ENERGY_DELIVERY + beckn:stops: + - beckn:type: START + beckn:location: + beckn:address: "der://meter/100200300" + - beckn:type: END + beckn:location: + beckn:address: $inputs.buyerMeterId + successCriteria: + - condition: $statusCode == 200 + + # ── Step 8: Source utility response ── + - stepId: cascaded-on-init-source + operationId: beckn-bpp-utility.on_init + description: > + Source utility (TPDDL) approves, returns wheeling charges. + DEGContract utility role gets filled. + successCriteria: + - condition: $statusCode == 200 + outputs: + sourceWheelingCharge: $response.body.message.order.beckn:quote.beckn:breakup[0].beckn:price.schema:price + + # ── Step 9: Cascaded init to destination utility ── + - stepId: cascaded-init-dest + operationId: beckn-bpp.init + description: > + P2P BPP forwards init to destination utility (BESCOM). + Checks sanctioned load for the destination meter. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: init + domain: "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + bap_id: "bpp.energy-provider.com" + bap_uri: "https://bpp.energy-provider.com/bap" + bpp_id: "bpp.bescom-utility.in" + bpp_uri: "https://api.bescom-utility.in/beckn/v2" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:orderAttributes: + "@type": EnergyTradeOrder + sourceMeterId: "der://meter/100200300" + targetMeterId: $inputs.buyerMeterId + contractedQuantity: $inputs.requestedKwh + successCriteria: + - condition: $statusCode == 200 + + # ── Step 10: Destination utility response ── + - stepId: cascaded-on-init-dest + operationId: beckn-bpp-utility.on_init + description: > + Destination utility (BESCOM) approves, returns wheeling charges. + successCriteria: + - condition: $statusCode == 200 + outputs: + destWheelingCharge: $response.body.message.order.beckn:quote.beckn:breakup[0].beckn:price.schema:price + + # ── Step 11: Confirm trade ── + - stepId: confirm + operationId: beckn-bap.confirm + dependsOn: + - cascaded-on-init-source + - cascaded-on-init-dest + description: > + After both utilities approve, BAP confirms the trade. + DEGContract transitions to ACTIVE with all three roles filled. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: confirm + domain: "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:id: $steps.on-init.outputs.orderId + beckn:payment: + beckn:paymentStatus: AUTHORIZED + successCriteria: + - condition: $statusCode == 200 + + # ── Step 12: Trade confirmed ── + - stepId: on-confirm + operationId: beckn-bpp.on_confirm + description: > + BPP confirms trade. DEGContract ACTIVE. + EnergyTradeDelivery initialized with PENDING status. + settlementCycles created for the delivery window. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:orderAttributes.degContract.contractState == 'ACTIVE' + + # ── Step 13: Delivery status check ── + - stepId: status + operationId: beckn-bap.status + description: > + BAP checks delivery progress during the delivery window. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: status + domain: "beckn.one:deg:p2p-trading-interdiscom:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:id: $steps.on-init.outputs.orderId + successCriteria: + - condition: $statusCode == 200 + + # ── Step 14: Delivery in progress ── + - stepId: on-status + operationId: beckn-bpp.on_status + description: > + BPP returns delivery status with EnergyTradeDelivery fulfillmentAttributes: + deliveryStatus, deliveredQuantity, meterReadings. + successCriteria: + - condition: $statusCode == 200 + outputs: + deliveredQuantity: $response.body.message.order.beckn:orderItems[0].beckn:orderItemAttributes.fulfillmentAttributes.deliveredQuantity + + # ── Step 15: Evaluate delivery compliance (OPA) ── + - stepId: evaluate-delivery + operationId: opa-policy-engine.evaluate + description: > + Evaluate delivery_compliant and deviation_penalty using Rego. + Package: deg.contracts.p2p_trade + requestBody: + contentType: application/json + payload: + input: + deliveredQuantity: $steps.on-status.outputs.deliveredQuantity + data: + contract: + energySpec: + quantity: + unitQuantity: $inputs.requestedKwh + terms: + deviationPenaltyPerKwh: 1.0 + successCriteria: + - condition: $statusCode == 200 + outputs: + isCompliant: $response.body.result.delivery_compliant + penalty: $response.body.result.deviation_penalty + + # ── Step 16: Compute settlement (OPA) ── + - stepId: compute-settlement + operationId: opa-policy-engine.evaluate + description: > + Compute multi-party settlement using deg.revenue package. + Returns per-role net positions and validates net-zero invariant. + requestBody: + contentType: application/json + payload: + input: + deliveredKwh: $steps.on-status.outputs.deliveredQuantity + pricePerKwh: 4.50 + wheelingRatePerKwh: 0.50 + deviationPenaltyPerKwh: 1.0 + data: + contract: + roles: + - { roleId: seller } + - { roleId: buyer } + - { roleId: utility } + - { roleId: platform } + revenueModel: + flows: + - { flowId: energy_payment, from: buyer, to: seller, flowType: ENERGY_PAYMENT } + - { flowId: wheeling_charge, from: buyer, to: utility, flowType: WHEELING_CHARGE } + - { flowId: platform_fee_buyer, from: buyer, to: platform, flowType: PLATFORM_FEE } + - { flowId: platform_fee_seller, from: seller, to: platform, flowType: PLATFORM_FEE } + - { flowId: deviation_penalty, from: seller, to: buyer, flowType: DEVIATION_PENALTY } + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.result.net_zero_valid == true + outputs: + settlementSummary: $response.body.result.settlement_summary + + # ── Step 17: Final status (completed) ── + - stepId: on-status-completed + operationId: beckn-bpp.on_status + description: > + Final on_status with delivery COMPLETED. + DEGContract transitions to SETTLED. + revenueModel.flows have all computedAmounts filled. + settlementCycles show final breakdown. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:orderAttributes.degContract.contractState == 'SETTLED' + - condition: $response.body.message.order.beckn:orderAttributes.degContract.revenueModel.netZeroValid == true + + # ─────────────────────────────────────────────────────────────────────── + # WORKFLOW 3: Smart EV Charging (Composite: EV Charging + V2G) + # ─────────────────────────────────────────────────────────────────────── + - workflowId: smart-ev-charging-v2g-flow + summary: Composite EV charging with V2G demand flexibility + description: > + A V2G-capable EV plugs in at a smart charger. Normal charging proceeds + until a grid stress event occurs, at which point the vehicle discharges + to grid. The DEGContract is COMPOSITE (EV_CHARGING + DEMAND_FLEXIBILITY) + with three roles: ev_owner, cpo_aggregator, grid_operator. + Revenue flows bidirectionally: EV owner pays for charging but earns + from V2G discharge. + inputs: + type: object + properties: + vehicleBatteryKwh: + type: number + example: 60.0 + minChargeLevelPct: + type: number + example: 30 + requestedKwh: + type: number + example: 20.0 + connectorType: + type: string + example: "CCS2" + + steps: + # ── Step 1: Discover V2G-capable chargers ── + - stepId: discover + operationId: beckn-bap.search + description: > + Search for chargers with v2gSupported: true. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: search + domain: "beckn.one:deg:smart-ev-charging:2.0.0" + bap_id: "ev-bap.mobility-app.in" + bap_uri: "https://ev-bap.mobility-app.in/beckn/v2" + transaction_id: "{$generate.uuid}" + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + filters: + type: jsonpath + expression: > + $[?(@.itemAttributes.connectorType == '{$inputs.connectorType}' + && @.itemAttributes.v2gSupported == true)] + successCriteria: + - condition: $statusCode == 200 + outputs: + transactionId: $response.body.context.transaction_id + + # ── Step 2: Catalog with COMPOSITE DEGContract ── + - stepId: on-discover + operationId: beckn-bpp.on_search + description: > + BPP returns V2G-capable charger catalog. + Offer contains COMPOSITE DEGContract with: + - ev_owner role (open), cpo_aggregator (filled), grid_operator (filled) + - Terms: charging, v2g_discharge, pricing (both directions) + - Revenue model: charging_payment, v2g_payment, grid_v2g_payment, platform_fee + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.catalogs[0].beckn:offers[0].beckn:offerAttributes.degContract.contractType == 'COMPOSITE' + outputs: + selectedOfferId: $response.body.message.catalogs[0].beckn:offers[0].beckn:id + selectedItemId: $response.body.message.catalogs[0].beckn:items[0].beckn:id + + # ── Step 3: Select with vehicle details ── + - stepId: select + operationId: beckn-bap.select + description: > + EV owner fills vehicle details: battery capacity, min charge level, + V2G consent. DEGContract → NEGOTIATED. + requestBody: + contentType: application/json + payload: + context: + version: "2.0.0" + action: select + domain: "beckn.one:deg:smart-ev-charging:2.0.0" + transaction_id: $steps.discover.outputs.transactionId + message_id: "{$generate.uuid}" + timestamp: "{$generate.timestamp}" + ttl: "PT30S" + message: + order: + beckn:orderItems: + - beckn:orderedItem: $steps.on-discover.outputs.selectedItemId + beckn:quantity: + unitQuantity: $inputs.requestedKwh + unitText: "kWh" + beckn:acceptedOffer: + beckn:id: $steps.on-discover.outputs.selectedOfferId + beckn:orderAttributes: + degContract: + contractState: NEGOTIATED + roles: + - roleId: ev_owner + inputs: + - inputId: vehicle_battery_kwh + value: $inputs.vehicleBatteryKwh + - inputId: min_charge_level_pct + value: $inputs.minChargeLevelPct + - inputId: v2g_consent + value: true + successCriteria: + - condition: $statusCode == 200 + + # ── Step 4: Quote with V2G earning potential ── + - stepId: on-select + operationId: beckn-bpp.on_select + description: > + BPP returns quote showing: + - Estimated charging cost (e.g., INR 160 for 20 kWh @ INR 8/kWh) + - Estimated V2G earning potential (e.g., "up to INR 72 if 6 kWh discharged") + - Net estimated cost + successCriteria: + - condition: $statusCode == 200 + outputs: + estimatedChargingCost: $response.body.message.order.beckn:orderValue.value + + # ── Step 5-8: Init → Confirm (same pattern as EV charging) ── + - stepId: init + operationId: beckn-bap.init + description: Commit with billing and payment. + successCriteria: + - condition: $statusCode == 200 + + - stepId: on-init + operationId: beckn-bpp.on_init + description: Payment URL returned, contract COMMITTED. + successCriteria: + - condition: $statusCode == 200 + outputs: + orderId: $response.body.message.order.beckn:id + + - stepId: confirm + operationId: beckn-bap.confirm + description: Payment completed, confirm order. + successCriteria: + - condition: $statusCode == 200 + + - stepId: on-confirm + operationId: beckn-bpp.on_confirm + description: > + Contract ACTIVE. Session begins with normal charging. + energyFlowDirection: TO_VEHICLE + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:orderAttributes.degContract.contractState == 'ACTIVE' + + # ── Step 9: Normal charging status ── + - stepId: on-status-charging + operationId: beckn-bpp.on_status + description: > + Normal charging in progress. + ChargingSession: ACTIVE, energyFlowDirection: TO_VEHICLE. + Telemetry: SOC rising, power at requested level. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:fulfillment.beckn:deliveryAttributes.sessionStatus == 'ACTIVE' + + # ── Step 10: Grid stress → V2G activation ── + - stepId: evaluate-v2g-eligibility + operationId: opa-policy-engine.evaluate + description: > + Grid stress signal received. Evaluate V2G eligibility using Rego: + deg.contracts.v2g.v2g_eligible and should_discharge. + Checks: batteryPct > minChargeLevelPct, v2gCapable, charger supports V2G. + requestBody: + contentType: application/json + payload: + input: + vehicleBatteryPct: 65 + vehicleBatteryKwh: $inputs.vehicleBatteryKwh + v2gCapable: true + gridStress: true + data: + contract: + inputs: + minChargeLevelPct: $inputs.minChargeLevelPct + charger_state: + v2gSupported: true + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.result.v2g_eligible == true + - condition: $response.body.result.should_discharge == true + outputs: + maxDischargeKwh: $response.body.result.max_discharge_kwh + + # ── Step 11: V2G discharge event ── + - stepId: on-update-v2g + operationId: beckn-bpp.on_update + description: > + BPP notifies: V2G discharge activated. + energyFlowDirection: FROM_VEHICLE. + Telemetry shows discharge power and grid feed-in. + successCriteria: + - condition: $statusCode == 200 + outputs: + dischargedKwh: $response.body.message.order.beckn:fulfillment.beckn:deliveryAttributes.v2gDischargedKwh + + # ── Step 12: Compute net payment ── + - stepId: compute-net-payment + operationId: opa-policy-engine.evaluate + description: > + Compute bidirectional revenue using deg.contracts.v2g.net_payment. + Positive = EV owner pays, Negative = EV owner receives. + requestBody: + contentType: application/json + payload: + input: + totalChargedKwh: 15.0 + totalDischargedKwh: $steps.on-update-v2g.outputs.dischargedKwh + data: + contract: + terms: + chargingRate: 8.0 + v2gRate: 12.0 + successCriteria: + - condition: $statusCode == 200 + outputs: + netPayment: $response.body.result.net_payment + + # ── Step 13: Session complete with full settlement ── + - stepId: on-status-complete + operationId: beckn-bpp.on_status + description: > + Final status: session COMPLETED. DEGContract → SETTLED. + Revenue model fully computed: + - charging_payment: ev_owner → cpo_aggregator (INR 120 for 15 kWh) + - v2g_payment: cpo_aggregator → ev_owner (INR 36 for 3 kWh) + - grid_v2g_payment: grid_operator → cpo_aggregator (INR 54 for 3 kWh) + - platform_fee: cpo_aggregator → platform (INR 2.61) + Net-zero check passes. + EV owner effective rate: INR 5.60/kWh instead of INR 8/kWh. + successCriteria: + - condition: $statusCode == 200 + - condition: $response.body.message.order.beckn:orderAttributes.degContract.contractState == 'SETTLED' + - condition: $response.body.message.order.beckn:orderAttributes.degContract.revenueModel.netZeroValid == true + outputs: + finalSettlement: $response.body.message.order.beckn:orderAttributes.degContract.revenueModel + effectiveRate: $response.body.message.order.beckn:orderAttributes.degContract.x-settlement.effectiveRatePerKwh + +# ─────────────────────────────────────────────────────────────────────── +# Reusable Components +# ─────────────────────────────────────────────────────────────────────── +components: + inputs: + BecknContext: + type: object + properties: + version: { type: string, default: "2.0.0" } + domain: { type: string } + bap_id: { type: string } + bap_uri: { type: string } + bpp_id: { type: string } + bpp_uri: { type: string } + transaction_id: { type: string } + message_id: { type: string } + timestamp: { type: string, format: date-time } + ttl: { type: string, default: "PT30S" } + + DEGContractTemplate: + type: object + description: > + A partially-filled DEGContract used as input to workflows. + The contract template is retrieved from catalog (on_discover) + and progressively filled through the transaction lifecycle. + properties: + contractType: { type: string } + version: { type: string } + contractState: { type: string } + roles: { type: array } + terms: { type: array } + energySpec: { type: object } + fulfillmentSpec: { type: object } + settlementSpec: { type: object } + revenueModel: { type: object } + + parameters: + transactionId: + name: transaction_id + in: header + description: Beckn transaction ID that links all messages in a flow. + + orderId: + name: order_id + in: path + description: Order ID assigned by BPP during on_init. diff --git a/specification/policies/demand_flex.rego b/specification/policies/demand_flex.rego new file mode 100644 index 00000000..84efbbcc --- /dev/null +++ b/specification/policies/demand_flex.rego @@ -0,0 +1,71 @@ +# DEG Contract Policy — Demand Flexibility +# +# Evaluates trigger conditions, event limits, curtailment measurement, +# and incentive calculations for demand flexibility contracts. +# +# input: current grid/event state (frequency, price, load measurements) +# data.contract: the committed DEGContract terms +# data.events_this_month: array of events dispatched this billing month + +package deg.contracts.demand_flex + +import rego.v1 + +# --- Trigger Conditions --- + +# Grid frequency drops below safe threshold +demand_response_triggered if { + input.gridFrequency < 49.5 +} + +# Spot price exceeds contract threshold +demand_response_triggered if { + input.spotPrice > data.contract.inputs.priceThreshold +} + +# Explicit curtailment signal from grid operator +demand_response_triggered if { + input.curtailmentSignal == "ACTIVE" +} + +# --- Event Limit --- + +# Check if monthly event limit is reached +events_exhausted if { + count(data.events_this_month) >= data.contract.inputs.maxEventsPerMonth +} + +# Event can be dispatched +event_allowed if { + demand_response_triggered + not events_exhausted +} + +# --- Curtailment Measurement --- + +# Calculate actual curtailment +actual_curtailment := reduction if { + reduction := input.baselineLoad - input.actualLoad + reduction > 0 +} + +actual_curtailment := 0 if { + input.actualLoad >= input.baselineLoad +} + +# Was curtailment sufficient? (80% of committed capacity) +curtailment_compliant if { + actual_curtailment >= data.contract.inputs.curtailmentCapacity * 0.8 +} + +# --- Incentive Calculation --- + +incentive_amount := amount if { + curtailment_compliant + hours := (input.eventEndNs - input.eventStartNs) / (3600 * 1000000000) + amount := actual_curtailment * data.contract.inputs.incentiveRate * hours +} + +incentive_amount := 0 if { + not curtailment_compliant +} diff --git a/specification/policies/ev_charging.rego b/specification/policies/ev_charging.rego new file mode 100644 index 00000000..9a08b6a2 --- /dev/null +++ b/specification/policies/ev_charging.rego @@ -0,0 +1,90 @@ +# DEG Contract Policy — EV Charging +# +# Evaluates connector compatibility, power feasibility, reservation checks, +# time-of-day pricing, and cancellation fees for EV charging contracts. +# +# input: current charging session state (vehicle info, session status, energy delivered) +# data.contract: the committed DEGContract terms +# data.charger_state: real-time charger/EVSE state + +package deg.contracts.ev_charging + +import rego.v1 + +# --- Connector Compatibility --- + +connector_compatible if { + input.vehicleConnectorType == data.contract.inputs.connectorType +} + +connector_compatible if { + # CCS2 is backward compatible with Type2 + input.vehicleConnectorType == "Type2" + data.contract.inputs.connectorType == "CCS2" +} + +# --- Power Feasibility --- + +power_feasible if { + input.requestedPowerKW <= data.contract.inputs.maxPowerKW + input.requestedPowerKW >= data.contract.inputs.minPowerKW +} + +# --- Reservation Check --- + +reservation_available if { + data.contract.inputs.reservationSupported == true + not data.charger_state.occupied +} + +# --- Charging Price Calculation --- + +# Time-of-day pricing +charging_price_per_kwh := price if { + hour := time.clock(time.now_ns())[0] + hour >= 22 # Off-peak: 10 PM - 6 AM + price := data.contract.terms.offPeakRate +} + +charging_price_per_kwh := price if { + hour := time.clock(time.now_ns())[0] + hour < 6 # Off-peak: 10 PM - 6 AM + price := data.contract.terms.offPeakRate +} + +charging_price_per_kwh := price if { + hour := time.clock(time.now_ns())[0] + hour >= 6 + hour < 10 # Normal: 6 AM - 10 AM + price := data.contract.terms.normalRate +} + +charging_price_per_kwh := price if { + hour := time.clock(time.now_ns())[0] + hour >= 10 + hour < 18 # Peak: 10 AM - 6 PM + price := data.contract.terms.peakRate +} + +charging_price_per_kwh := price if { + hour := time.clock(time.now_ns())[0] + hour >= 18 + hour < 22 # Normal: 6 PM - 10 PM + price := data.contract.terms.normalRate +} + +# Total charging cost +total_charge := cost if { + cost := input.deliveredKwh * charging_price_per_kwh +} + +# --- Cancellation Fee --- + +cancellation_fee := fee if { + input.fulfillmentState == "charging-started" + fee := input.estimatedTotal * 0.30 +} + +cancellation_fee := 0 if { + input.fulfillmentState == "order-initiated" +} diff --git a/specification/policies/p2p_trade.rego b/specification/policies/p2p_trade.rego new file mode 100644 index 00000000..ef1b39e3 --- /dev/null +++ b/specification/policies/p2p_trade.rego @@ -0,0 +1,73 @@ +# DEG Contract Policy — P2P Energy Trading +# +# Evaluates delivery compliance, deviation penalties, sanctioned load checks, +# and settlement calculations for peer-to-peer energy trading contracts. +# +# input: current transaction/event state (meter readings, timestamps) +# data.contract: the committed DEGContract terms +# data.meters: meter registry data (for sanctioned load checks) + +package deg.contracts.p2p_trade + +import rego.v1 + +# --- Delivery Compliance --- + +# Check if delivery is within the contracted window +delivery_in_window if { + now := time.now_ns() + start := time.parse_rfc3339_ns(data.contract.fulfillmentSpec.deliveryWindow.startTime) + end := time.parse_rfc3339_ns(data.contract.fulfillmentSpec.deliveryWindow.endTime) + now >= start + now <= end +} + +# Check if delivered quantity meets the obligation (within 10% tolerance) +delivery_quantity_compliant if { + contracted := data.contract.energySpec.quantity.unitQuantity + delivered := input.deliveredQuantity + delivered >= contracted * 0.9 + delivered <= contracted * 1.1 +} + +# Overall delivery compliance +delivery_compliant if { + delivery_in_window + delivery_quantity_compliant +} + +# --- Deviation Penalty --- + +# Calculate deviation penalty when delivery falls short +deviation_penalty := penalty if { + not delivery_quantity_compliant + contracted := data.contract.energySpec.quantity.unitQuantity + delivered := input.deliveredQuantity + shortfall := contracted - delivered + shortfall > 0 + rate := data.contract.terms.deviationPenaltyPerKwh + penalty := shortfall * rate +} + +# No penalty if compliant +deviation_penalty := 0 if { + delivery_quantity_compliant +} + +# --- Sanctioned Load Check (for cascaded init to utility) --- + +sanctioned_load_check if { + meter_id := input.meterId + requested := input.requestedQuantity + sanctioned := data.meters[meter_id].sanctionedLoad + requested <= sanctioned +} + +# --- Settlement Calculation --- + +settlement_amount := amount if { + delivered := input.deliveredQuantity + price := data.contract.terms.pricePerKwh + wheeling := data.contract.terms.wheelingCharge + amount := (delivered * price) + wheeling +} diff --git a/specification/policies/revenue.rego b/specification/policies/revenue.rego new file mode 100644 index 00000000..d656699d --- /dev/null +++ b/specification/policies/revenue.rego @@ -0,0 +1,121 @@ +# DEG Contract Policy — Multi-Party Revenue Model (Net-Zero Flows) +# +# Generic revenue computation package. Evaluates the revenueModel declared +# in any DEGContract, computes each flow's amount, validates the net-zero +# invariant, and produces per-role revenue summaries that agents can query. +# +# input: { deliveredKwh, pricePerKwh, wheelingRate, ... } +# data.contract.revenueModel: { flows: [...] } +# data.contract.roles: [...] + +package deg.revenue + +import rego.v1 + +# --- Compute individual flow amounts --- + +# P2P energy payment: buyer -> seller +flow_amount["energy_payment"] := amount if { + some flow in data.contract.revenueModel.flows + flow.flowId == "energy_payment" + amount := input.deliveredKwh * input.pricePerKwh +} + +# Wheeling charge: buyer -> utility +flow_amount["wheeling_charge"] := amount if { + some flow in data.contract.revenueModel.flows + flow.flowId == "wheeling_charge" + amount := input.deliveredKwh * input.wheelingRatePerKwh +} + +# Platform fee (buyer side): buyer -> platform +flow_amount["platform_fee_buyer"] := amount if { + some flow in data.contract.revenueModel.flows + flow.flowId == "platform_fee_buyer" + amount := flow_amount["energy_payment"] * 0.01 +} + +# Platform fee (seller side): seller -> platform +flow_amount["platform_fee_seller"] := amount if { + some flow in data.contract.revenueModel.flows + flow.flowId == "platform_fee_seller" + amount := flow_amount["energy_payment"] * 0.01 +} + +# Deviation penalty: seller -> buyer (only if delivery fell short) +flow_amount["deviation_penalty"] := amount if { + some flow in data.contract.revenueModel.flows + flow.flowId == "deviation_penalty" + contracted := data.contract.energySpec.quantity.unitQuantity + shortfall := contracted - input.deliveredKwh + shortfall > 0 + amount := shortfall * input.deviationPenaltyPerKwh +} + +flow_amount["deviation_penalty"] := 0 if { + contracted := data.contract.energySpec.quantity.unitQuantity + input.deliveredKwh >= contracted +} + +# --- Per-role net position --- +# An agent queries: net_position["my_role_id"] to know if it earns or pays. +# Positive = earns money, Negative = pays money. + +# Inflows for a role: sum of all flows where role is the recipient +inflows[role_id] := total if { + some role in data.contract.roles + role_id := role.roleId + total := sum([amount | + some flow in data.contract.revenueModel.flows + flow.to == role_id + amount := flow_amount[flow.flowId] + ]) +} + +# Outflows for a role: sum of all flows where role is the payer +outflows[role_id] := total if { + some role in data.contract.roles + role_id := role.roleId + total := sum([amount | + some flow in data.contract.revenueModel.flows + flow.from == role_id + amount := flow_amount[flow.flowId] + ]) +} + +# Net position per role +net_position[role_id] := position if { + some role in data.contract.roles + role_id := role.roleId + position := inflows[role_id] - outflows[role_id] +} + +# --- Net-zero invariant check --- + +# Sum of all net positions must be zero +total_net := sum([pos | some role_id; pos := net_position[role_id]]) + +net_zero_valid if { + abs(total_net) < 0.01 # Tolerance for floating point +} + +# Helper +abs(x) := x if { x >= 0 } +abs(x) := y if { x < 0; y := 0 - x } + +# --- Settlement summary --- +# Returns a structured object that agents can consume + +# --- Settlement summary (individual fields) --- + +settlement_flows := [flow_detail | + some flow in data.contract.revenueModel.flows + flow_detail := { + "flowId": flow.flowId, + "from": flow.from, + "to": flow.to, + "flowType": flow.flowType, + "amount": flow_amount[flow.flowId], + "currency": flow.currency, + } +] diff --git a/specification/policies/tests/demand_flex/run_tests.sh b/specification/policies/tests/demand_flex/run_tests.sh new file mode 100755 index 00000000..f0f8376b --- /dev/null +++ b/specification/policies/tests/demand_flex/run_tests.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# Tests for deg.contracts.demand_flex policy +set -euo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" +POLICY_DIR="$(cd "$DIR/../.." && pwd)" +POLICY="$POLICY_DIR/demand_flex.rego" +DATA="$DIR/test_data.json" +INPUT="$DIR/test_input.json" + +PASS=0 +FAIL=0 + +run_test() { + local name="$1" query="$2" expected="$3" + result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT" "$query" --format raw 2>&1) || true + result=$(echo "$result" | tr -d '[:space:]') + expected_trimmed=$(echo "$expected" | tr -d '[:space:]') + if [ "$result" = "$expected_trimmed" ]; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected: $expected" + echo " got: $result" + FAIL=$((FAIL + 1)) + fi +} + +echo "=== Demand Flexibility Policy Tests ===" +echo "--- Input: freq=49.2, spotPrice=12, signal=ACTIVE, baseline=200, actual=100 ---" +echo "--- Data: threshold=10, maxEvents=5, capacity=100, incentiveRate=3.50, 2 prior events ---" + +# demand_response_triggered: freq 49.2 < 49.5 → true +run_test "demand_response_triggered (low frequency)" \ + "data.deg.contracts.demand_flex.demand_response_triggered" \ + "true" + +# events_exhausted: 2 events < 5 max → should be undefined (not exhausted) +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT" \ + "data.deg.contracts.demand_flex.events_exhausted" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ -z "$result" ] || [ "$result" = "undefined" ]; then + echo " PASS: events_exhausted (2 of 5 — not exhausted)" + PASS=$((PASS + 1)) +else + echo " FAIL: events_exhausted (should be undefined)" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# event_allowed: triggered + not exhausted → true +run_test "event_allowed" \ + "data.deg.contracts.demand_flex.event_allowed" \ + "true" + +# actual_curtailment: 200 - 100 = 100 +run_test "actual_curtailment" \ + "data.deg.contracts.demand_flex.actual_curtailment" \ + "100" + +# curtailment_compliant: 100 >= 100 * 0.8 (80) → true +run_test "curtailment_compliant" \ + "data.deg.contracts.demand_flex.curtailment_compliant" \ + "true" + +# incentive_amount: 100 kW * 3.50 INR/kW * 1 hour = 350 +# hours = (4600000000000 - 1000000000000) / 3600000000000 = 1.0 +run_test "incentive_amount" \ + "data.deg.contracts.demand_flex.incentive_amount" \ + "350" + +echo "" +echo "--- Testing exhausted events (5 of 5) ---" + +DATA_EXHAUSTED=$(mktemp) +cat > "$DATA_EXHAUSTED" <<'EOF' +{ + "contract": { + "inputs": { + "priceThreshold": 10.0, + "maxEventsPerMonth": 5, + "curtailmentCapacity": 100.0, + "incentiveRate": 3.50 + } + }, + "events_this_month": [ + {"eventId":"1"},{"eventId":"2"},{"eventId":"3"},{"eventId":"4"},{"eventId":"5"} + ] +} +EOF + +result=$(opa eval -d "$POLICY" -d "$DATA_EXHAUSTED" -i "$INPUT" \ + "data.deg.contracts.demand_flex.events_exhausted" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "true" ]; then + echo " PASS: events_exhausted (5 of 5)" + PASS=$((PASS + 1)) +else + echo " FAIL: events_exhausted" + echo " expected: true" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# event_allowed should be undefined when exhausted +result=$(opa eval -d "$POLICY" -d "$DATA_EXHAUSTED" -i "$INPUT" \ + "data.deg.contracts.demand_flex.event_allowed" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ -z "$result" ] || [ "$result" = "undefined" ]; then + echo " PASS: event_allowed (blocked — events exhausted)" + PASS=$((PASS + 1)) +else + echo " FAIL: event_allowed (should be undefined when exhausted)" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +rm -f "$DATA_EXHAUSTED" + +echo "" +echo "--- Testing non-compliant curtailment ---" + +INPUT_LOW_CURTAIL=$(mktemp) +cat > "$INPUT_LOW_CURTAIL" <<'EOF' +{ + "gridFrequency": 49.2, + "spotPrice": 12.0, + "curtailmentSignal": "ACTIVE", + "baselineLoad": 200.0, + "actualLoad": 170.0, + "eventStartNs": 1000000000000, + "eventEndNs": 4600000000000 +} +EOF + +# actual_curtailment: 200 - 170 = 30 (below 80% of 100 = 80) +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_LOW_CURTAIL" \ + "data.deg.contracts.demand_flex.actual_curtailment" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "30" ]; then + echo " PASS: actual_curtailment (low: 30 kW)" + PASS=$((PASS + 1)) +else + echo " FAIL: actual_curtailment (low)" + echo " expected: 30" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# incentive_amount: 0 when not compliant +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_LOW_CURTAIL" \ + "data.deg.contracts.demand_flex.incentive_amount" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "0" ]; then + echo " PASS: incentive_amount (non-compliant = 0)" + PASS=$((PASS + 1)) +else + echo " FAIL: incentive_amount (non-compliant)" + echo " expected: 0" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +rm -f "$INPUT_LOW_CURTAIL" + +echo "" +echo "Results: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/specification/policies/tests/demand_flex/test_data.json b/specification/policies/tests/demand_flex/test_data.json new file mode 100644 index 00000000..7685e08c --- /dev/null +++ b/specification/policies/tests/demand_flex/test_data.json @@ -0,0 +1,15 @@ +{ + "contract": { + "contractType": "DEMAND_FLEXIBILITY", + "inputs": { + "priceThreshold": 10.0, + "maxEventsPerMonth": 5, + "curtailmentCapacity": 100.0, + "incentiveRate": 3.50 + } + }, + "events_this_month": [ + { "eventId": "evt-001" }, + { "eventId": "evt-002" } + ] +} diff --git a/specification/policies/tests/demand_flex/test_input.json b/specification/policies/tests/demand_flex/test_input.json new file mode 100644 index 00000000..c2ccca9e --- /dev/null +++ b/specification/policies/tests/demand_flex/test_input.json @@ -0,0 +1,9 @@ +{ + "gridFrequency": 49.2, + "spotPrice": 12.0, + "curtailmentSignal": "ACTIVE", + "baselineLoad": 200.0, + "actualLoad": 100.0, + "eventStartNs": 1000000000000, + "eventEndNs": 4600000000000 +} diff --git a/specification/policies/tests/ev_charging/run_tests.sh b/specification/policies/tests/ev_charging/run_tests.sh new file mode 100755 index 00000000..c87f9795 --- /dev/null +++ b/specification/policies/tests/ev_charging/run_tests.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# Tests for deg.contracts.ev_charging policy +set -euo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" +POLICY_DIR="$(cd "$DIR/../.." && pwd)" +POLICY="$POLICY_DIR/ev_charging.rego" +DATA="$DIR/test_data.json" +INPUT="$DIR/test_input.json" + +PASS=0 +FAIL=0 + +run_test() { + local name="$1" query="$2" expected="$3" + result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT" "$query" --format raw 2>&1) || true + result=$(echo "$result" | tr -d '[:space:]') + expected_trimmed=$(echo "$expected" | tr -d '[:space:]') + if [ "$result" = "$expected_trimmed" ]; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected: $expected" + echo " got: $result" + FAIL=$((FAIL + 1)) + fi +} + +# Check if result is defined (not empty/undefined) +run_test_defined() { + local name="$1" query="$2" + result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT" "$query" --format raw 2>&1) || true + result=$(echo "$result" | tr -d '[:space:]') + if [ -n "$result" ] && [ "$result" != "undefined" ]; then + echo " PASS: $name = $result" + PASS=$((PASS + 1)) + else + echo " FAIL: $name (undefined)" + FAIL=$((FAIL + 1)) + fi +} + +echo "=== EV Charging Policy Tests ===" +echo "--- Input: CCS2 vehicle, 50kW requested, 25 kWh delivered, charging-started ---" + +# connector_compatible: CCS2 == CCS2 +run_test "connector_compatible (CCS2 match)" \ + "data.deg.contracts.ev_charging.connector_compatible" \ + "true" + +# power_feasible: 50 kW within [7, 60] +run_test "power_feasible" \ + "data.deg.contracts.ev_charging.power_feasible" \ + "true" + +# reservation_available: supported=true, occupied=false +run_test "reservation_available" \ + "data.deg.contracts.ev_charging.reservation_available" \ + "true" + +# charging_price_per_kwh: depends on current hour, just check it's defined +run_test_defined "charging_price_per_kwh (time-of-day)" \ + "data.deg.contracts.ev_charging.charging_price_per_kwh" + +# total_charge: depends on time-of-day rate, check it's defined +run_test_defined "total_charge" \ + "data.deg.contracts.ev_charging.total_charge" + +# cancellation_fee: fulfillmentState=charging-started → 300 * 0.30 = 90 +run_test "cancellation_fee (charging started)" \ + "data.deg.contracts.ev_charging.cancellation_fee" \ + "90" + +echo "" +echo "--- Testing Type2→CCS2 backward compatibility ---" + +INPUT_TYPE2=$(mktemp) +cat > "$INPUT_TYPE2" <<'EOF' +{ + "vehicleConnectorType": "Type2", + "requestedPowerKW": 22, + "deliveredKwh": 10.0, + "fulfillmentState": "order-initiated", + "estimatedTotal": 100.0 +} +EOF + +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_TYPE2" \ + "data.deg.contracts.ev_charging.connector_compatible" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "true" ]; then + echo " PASS: connector_compatible (Type2 on CCS2 — backward compat)" + PASS=$((PASS + 1)) +else + echo " FAIL: connector_compatible (Type2 on CCS2)" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# cancellation_fee: order-initiated → 0 +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_TYPE2" \ + "data.deg.contracts.ev_charging.cancellation_fee" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "0" ]; then + echo " PASS: cancellation_fee (order-initiated = 0)" + PASS=$((PASS + 1)) +else + echo " FAIL: cancellation_fee (order-initiated)" + echo " expected: 0" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +rm -f "$INPUT_TYPE2" + +echo "" +echo "Results: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/specification/policies/tests/ev_charging/test_data.json b/specification/policies/tests/ev_charging/test_data.json new file mode 100644 index 00000000..b9ffb55f --- /dev/null +++ b/specification/policies/tests/ev_charging/test_data.json @@ -0,0 +1,20 @@ +{ + "contract": { + "contractType": "EV_CHARGING", + "inputs": { + "connectorType": "CCS2", + "maxPowerKW": 60, + "minPowerKW": 7, + "reservationSupported": true + }, + "terms": { + "peakRate": 12.0, + "normalRate": 8.0, + "offPeakRate": 5.0 + } + }, + "charger_state": { + "occupied": false, + "v2gSupported": true + } +} diff --git a/specification/policies/tests/ev_charging/test_input.json b/specification/policies/tests/ev_charging/test_input.json new file mode 100644 index 00000000..bfad06dc --- /dev/null +++ b/specification/policies/tests/ev_charging/test_input.json @@ -0,0 +1,7 @@ +{ + "vehicleConnectorType": "CCS2", + "requestedPowerKW": 50, + "deliveredKwh": 25.0, + "fulfillmentState": "charging-started", + "estimatedTotal": 300.0 +} diff --git a/specification/policies/tests/p2p_trade/run_tests.sh b/specification/policies/tests/p2p_trade/run_tests.sh new file mode 100755 index 00000000..9a1158e0 --- /dev/null +++ b/specification/policies/tests/p2p_trade/run_tests.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Tests for deg.contracts.p2p_trade policy +set -euo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" +POLICY_DIR="$(cd "$DIR/../.." && pwd)" +POLICY="$POLICY_DIR/p2p_trade.rego" +DATA="$DIR/test_data.json" +INPUT="$DIR/test_input.json" + +PASS=0 +FAIL=0 + +run_test() { + local name="$1" query="$2" expected="$3" + result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT" "$query" --format raw 2>&1) || true + # Trim whitespace + result=$(echo "$result" | tr -d '[:space:]') + expected_trimmed=$(echo "$expected" | tr -d '[:space:]') + if [ "$result" = "$expected_trimmed" ]; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected: $expected" + echo " got: $result" + FAIL=$((FAIL + 1)) + fi +} + +echo "=== P2P Trade Policy Tests ===" +echo "--- Input: 19.5 kWh delivered (contracted 20.0), within window ---" + +# delivery_in_window: window is 2020-2030, so current time is inside +run_test "delivery_in_window" \ + "data.deg.contracts.p2p_trade.delivery_in_window" \ + "true" + +# delivery_quantity_compliant: 19.5 is within 10% of 20 (18-22 range) +run_test "delivery_quantity_compliant" \ + "data.deg.contracts.p2p_trade.delivery_quantity_compliant" \ + "true" + +# delivery_compliant: both window and quantity pass +run_test "delivery_compliant" \ + "data.deg.contracts.p2p_trade.delivery_compliant" \ + "true" + +# deviation_penalty: 0 because quantity is compliant +run_test "deviation_penalty (compliant)" \ + "data.deg.contracts.p2p_trade.deviation_penalty" \ + "0" + +# sanctioned_load_check: requesting 20 from METER-001 (sanctioned 50) — OK +run_test "sanctioned_load_check (within limit)" \ + "data.deg.contracts.p2p_trade.sanctioned_load_check" \ + "true" + +# settlement_amount: (19.5 * 4.50) + 5.00 = 92.75 +run_test "settlement_amount" \ + "data.deg.contracts.p2p_trade.settlement_amount" \ + "92.75" + +echo "" +echo "--- Testing with non-compliant delivery (15.0 kWh, below 10% tolerance) ---" + +# Override input for non-compliant scenario +INPUT_NONCOMPLIANT=$(mktemp) +cat > "$INPUT_NONCOMPLIANT" <<'EOF' +{ + "deliveredQuantity": 15.0, + "meterId": "METER-002", + "requestedQuantity": 20.0 +} +EOF + +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_NONCOMPLIANT" \ + "data.deg.contracts.p2p_trade.deviation_penalty" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +expected="10" +if [ "$result" = "$expected" ]; then + echo " PASS: deviation_penalty (non-compliant: 5 kWh short * 2.00/kWh = 10)" + PASS=$((PASS + 1)) +else + echo " FAIL: deviation_penalty (non-compliant)" + echo " expected: $expected" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# sanctioned_load_check should fail: requesting 20 from METER-002 (sanctioned 10) +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_NONCOMPLIANT" \ + "data.deg.contracts.p2p_trade.sanctioned_load_check" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "" ] || [ "$result" = "undefined" ]; then + echo " PASS: sanctioned_load_check (exceeds limit — undefined/blocked)" + PASS=$((PASS + 1)) +else + echo " FAIL: sanctioned_load_check (should be undefined when exceeding limit)" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +rm -f "$INPUT_NONCOMPLIANT" + +echo "" +echo "Results: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/specification/policies/tests/p2p_trade/test_data.json b/specification/policies/tests/p2p_trade/test_data.json new file mode 100644 index 00000000..fd30c9a6 --- /dev/null +++ b/specification/policies/tests/p2p_trade/test_data.json @@ -0,0 +1,23 @@ +{ + "contract": { + "contractType": "P2P_ENERGY_TRADE", + "fulfillmentSpec": { + "deliveryWindow": { + "startTime": "2020-01-01T00:00:00Z", + "endTime": "2030-12-31T23:59:59Z" + } + }, + "energySpec": { + "quantity": { "unitQuantity": 20.0 } + }, + "terms": { + "pricePerKwh": 4.50, + "wheelingCharge": 5.00, + "deviationPenaltyPerKwh": 2.00 + } + }, + "meters": { + "METER-001": { "sanctionedLoad": 50.0 }, + "METER-002": { "sanctionedLoad": 10.0 } + } +} diff --git a/specification/policies/tests/p2p_trade/test_input.json b/specification/policies/tests/p2p_trade/test_input.json new file mode 100644 index 00000000..ae2873d9 --- /dev/null +++ b/specification/policies/tests/p2p_trade/test_input.json @@ -0,0 +1,5 @@ +{ + "deliveredQuantity": 19.5, + "meterId": "METER-001", + "requestedQuantity": 20.0 +} diff --git a/specification/policies/tests/revenue/run_tests.sh b/specification/policies/tests/revenue/run_tests.sh new file mode 100755 index 00000000..c5525a15 --- /dev/null +++ b/specification/policies/tests/revenue/run_tests.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# Tests for deg.revenue policy (multi-party net-zero revenue model) +set -euo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" +POLICY_DIR="$(cd "$DIR/../.." && pwd)" +POLICY="$POLICY_DIR/revenue.rego" +DATA="$DIR/test_data.json" +INPUT="$DIR/test_input.json" + +PASS=0 +FAIL=0 + +run_test() { + local name="$1" query="$2" expected="$3" + result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT" "$query" --format raw 2>&1) || true + result=$(echo "$result" | tr -d '[:space:]') + expected_trimmed=$(echo "$expected" | tr -d '[:space:]') + if [ "$result" = "$expected_trimmed" ]; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected: $expected" + echo " got: $result" + FAIL=$((FAIL + 1)) + fi +} + +echo "=== Revenue Model Policy Tests ===" +echo "--- Input: 19.5 kWh delivered (contracted 20), price=4.50, wheeling=0.25 ---" +echo "--- 4 roles: buyer, seller, source_utility, platform ---" + +# flow_amount: energy_payment = 19.5 * 4.50 = 87.75 +run_test "flow_amount[energy_payment]" \ + "data.deg.revenue.flow_amount.energy_payment" \ + "87.75" + +# flow_amount: wheeling_charge = 19.5 * 0.25 = 4.875 +run_test "flow_amount[wheeling_charge]" \ + "data.deg.revenue.flow_amount.wheeling_charge" \ + "4.875" + +# flow_amount: platform_fee_buyer = 87.75 * 0.01 = 0.8775 +run_test "flow_amount[platform_fee_buyer]" \ + "data.deg.revenue.flow_amount.platform_fee_buyer" \ + "0.8775" + +# flow_amount: platform_fee_seller = 87.75 * 0.01 = 0.8775 +run_test "flow_amount[platform_fee_seller]" \ + "data.deg.revenue.flow_amount.platform_fee_seller" \ + "0.8775" + +# flow_amount: deviation_penalty = (20 - 19.5) * 2.00 = 1.0 +run_test "flow_amount[deviation_penalty]" \ + "data.deg.revenue.flow_amount.deviation_penalty" \ + "1" + +# net_zero_valid: all flows must sum to zero +run_test "net_zero_valid" \ + "data.deg.revenue.net_zero_valid" \ + "true" + +# net_position: buyer pays (negative) +# outflows: energy(87.75) + wheeling(4.875) + platform_fee_buyer(0.8775) = 93.5025 +# inflows: deviation_penalty(1.0) = 1.0 +# net = 1.0 - 93.5025 = -92.5025 +run_test "net_position[buyer]" \ + "data.deg.revenue.net_position.buyer" \ + "-92.5025" + +# net_position: seller receives +# inflows: energy(87.75) = 87.75 +# outflows: platform_fee_seller(0.8775) + deviation_penalty(1.0) = 1.8775 +# net = 87.75 - 1.8775 = 85.8725 +run_test "net_position[seller]" \ + "data.deg.revenue.net_position.seller" \ + "85.8725" + +# net_position: source_utility receives +# inflows: wheeling(4.875) +# outflows: 0 +# net = 4.875 +run_test "net_position[source_utility]" \ + "data.deg.revenue.net_position.source_utility" \ + "4.875" + +# net_position: platform receives +# inflows: platform_fee_buyer(0.8775) + platform_fee_seller(0.8775) = 1.755 +# outflows: 0 +# net = 1.755 +run_test "net_position[platform]" \ + "data.deg.revenue.net_position.platform" \ + "1.755" + +echo "" +echo "--- Testing full delivery (no penalty) ---" + +INPUT_FULL=$(mktemp) +cat > "$INPUT_FULL" <<'EOF' +{ + "deliveredKwh": 20.0, + "pricePerKwh": 4.50, + "wheelingRatePerKwh": 0.25, + "deviationPenaltyPerKwh": 2.00 +} +EOF + +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_FULL" \ + "data.deg.revenue.flow_amount.deviation_penalty" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "0" ]; then + echo " PASS: deviation_penalty (full delivery = 0)" + PASS=$((PASS + 1)) +else + echo " FAIL: deviation_penalty (full delivery)" + echo " expected: 0" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_FULL" \ + "data.deg.revenue.net_zero_valid" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "true" ]; then + echo " PASS: net_zero_valid (full delivery)" + PASS=$((PASS + 1)) +else + echo " FAIL: net_zero_valid (full delivery)" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +rm -f "$INPUT_FULL" + +echo "" +echo "Results: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/specification/policies/tests/revenue/test_data.json b/specification/policies/tests/revenue/test_data.json new file mode 100644 index 00000000..694179b3 --- /dev/null +++ b/specification/policies/tests/revenue/test_data.json @@ -0,0 +1,23 @@ +{ + "contract": { + "contractType": "P2P_ENERGY_TRADE", + "roles": [ + { "roleId": "buyer", "roleType": "CONSUMER" }, + { "roleId": "seller", "roleType": "PRODUCER" }, + { "roleId": "source_utility", "roleType": "UTILITY" }, + { "roleId": "platform", "roleType": "AGGREGATOR" } + ], + "energySpec": { + "quantity": { "unitQuantity": 20.0 } + }, + "revenueModel": { + "flows": [ + { "flowId": "energy_payment", "from": "buyer", "to": "seller", "flowType": "ENERGY_PAYMENT", "currency": "INR" }, + { "flowId": "wheeling_charge", "from": "buyer", "to": "source_utility", "flowType": "WHEELING_CHARGE", "currency": "INR" }, + { "flowId": "platform_fee_buyer", "from": "buyer", "to": "platform", "flowType": "PLATFORM_FEE", "currency": "INR" }, + { "flowId": "platform_fee_seller","from": "seller", "to": "platform", "flowType": "PLATFORM_FEE", "currency": "INR" }, + { "flowId": "deviation_penalty", "from": "seller", "to": "buyer", "flowType": "DEVIATION_PENALTY", "currency": "INR" } + ] + } + } +} diff --git a/specification/policies/tests/revenue/test_input.json b/specification/policies/tests/revenue/test_input.json new file mode 100644 index 00000000..e91a68c9 --- /dev/null +++ b/specification/policies/tests/revenue/test_input.json @@ -0,0 +1,6 @@ +{ + "deliveredKwh": 19.5, + "pricePerKwh": 4.50, + "wheelingRatePerKwh": 0.25, + "deviationPenaltyPerKwh": 2.00 +} diff --git a/specification/policies/tests/run_all_tests.sh b/specification/policies/tests/run_all_tests.sh new file mode 100755 index 00000000..1e5912a9 --- /dev/null +++ b/specification/policies/tests/run_all_tests.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Run all DEG policy tests +set -uo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" + +TOTAL_PASS=0 +TOTAL_FAIL=0 +SUITES=0 +SUITE_FAILURES=0 + +for suite_dir in "$DIR"/*/; do + script="$suite_dir/run_tests.sh" + [ -f "$script" ] || continue + SUITES=$((SUITES + 1)) + echo "" + echo "================================================================" + if bash "$script"; then + TOTAL_PASS=$((TOTAL_PASS + 1)) + else + TOTAL_FAIL=$((TOTAL_FAIL + 1)) + SUITE_FAILURES=$((SUITE_FAILURES + 1)) + fi + echo "================================================================" +done + +echo "" +echo "===============================" +echo " ALL SUITES: $SUITES run, $SUITE_FAILURES failed" +echo "===============================" + +[ "$SUITE_FAILURES" -eq 0 ] diff --git a/specification/policies/tests/v2g/run_tests.sh b/specification/policies/tests/v2g/run_tests.sh new file mode 100755 index 00000000..611174cb --- /dev/null +++ b/specification/policies/tests/v2g/run_tests.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Tests for deg.contracts.v2g policy +set -euo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" +POLICY_DIR="$(cd "$DIR/../.." && pwd)" +POLICY="$POLICY_DIR/v2g.rego" +DATA="$DIR/test_data.json" +INPUT="$DIR/test_input.json" + +PASS=0 +FAIL=0 + +run_test() { + local name="$1" query="$2" expected="$3" + result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT" "$query" --format raw 2>&1) || true + result=$(echo "$result" | tr -d '[:space:]') + expected_trimmed=$(echo "$expected" | tr -d '[:space:]') + if [ "$result" = "$expected_trimmed" ]; then + echo " PASS: $name" + PASS=$((PASS + 1)) + else + echo " FAIL: $name" + echo " expected: $expected" + echo " got: $result" + FAIL=$((FAIL + 1)) + fi +} + +echo "=== V2G Policy Tests ===" +echo "--- Input: battery=80%, 60kWh cap, v2g=true, gridStress=true ---" +echo "--- Data: minCharge=20%, chargingRate=8, v2gRate=12 ---" + +# v2g_eligible: battery 80% > 20% min, v2gCapable=true, charger supports +run_test "v2g_eligible" \ + "data.deg.contracts.v2g.v2g_eligible" \ + "true" + +# should_discharge: eligible + gridStress=true +run_test "should_discharge" \ + "data.deg.contracts.v2g.should_discharge" \ + "true" + +# max_discharge_kwh: current = 60 * (80/100) = 48, min = 60 * (20/100) = 12, diff = 36 +run_test "max_discharge_kwh" \ + "data.deg.contracts.v2g.max_discharge_kwh" \ + "36" + +# net_payment: (30 * 8) - (10 * 12) = 240 - 120 = 120 (owner pays net) +run_test "net_payment" \ + "data.deg.contracts.v2g.net_payment" \ + "120" + +echo "" +echo "--- Testing V2G ineligible (low battery) ---" + +INPUT_LOW=$(mktemp) +cat > "$INPUT_LOW" <<'EOF' +{ + "vehicleBatteryPct": 15, + "vehicleBatteryKwh": 60, + "v2gCapable": true, + "gridStress": true, + "totalChargedKwh": 5.0, + "totalDischargedKwh": 20.0 +} +EOF + +# v2g_eligible: 15% < 20% min → undefined +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_LOW" \ + "data.deg.contracts.v2g.v2g_eligible" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ -z "$result" ] || [ "$result" = "undefined" ]; then + echo " PASS: v2g_eligible (low battery — ineligible)" + PASS=$((PASS + 1)) +else + echo " FAIL: v2g_eligible (should be undefined for low battery)" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# should_discharge: not eligible → undefined +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_LOW" \ + "data.deg.contracts.v2g.should_discharge" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ -z "$result" ] || [ "$result" = "undefined" ]; then + echo " PASS: should_discharge (ineligible — blocked)" + PASS=$((PASS + 1)) +else + echo " FAIL: should_discharge (should be undefined)" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# max_discharge_kwh: current=9, min=12 → 0 (can't discharge) +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_LOW" \ + "data.deg.contracts.v2g.max_discharge_kwh" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "0" ]; then + echo " PASS: max_discharge_kwh (below min charge = 0)" + PASS=$((PASS + 1)) +else + echo " FAIL: max_discharge_kwh (below min)" + echo " expected: 0" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +# net_payment: (5 * 8) - (20 * 12) = 40 - 240 = -200 (owner receives money) +result=$(opa eval -d "$POLICY" -d "$DATA" -i "$INPUT_LOW" \ + "data.deg.contracts.v2g.net_payment" --format raw 2>&1) || true +result=$(echo "$result" | tr -d '[:space:]') +if [ "$result" = "-200" ]; then + echo " PASS: net_payment (owner earns: -200)" + PASS=$((PASS + 1)) +else + echo " FAIL: net_payment (owner earns)" + echo " expected: -200" + echo " got: $result" + FAIL=$((FAIL + 1)) +fi + +rm -f "$INPUT_LOW" + +echo "" +echo "Results: $PASS passed, $FAIL failed" +[ "$FAIL" -eq 0 ] diff --git a/specification/policies/tests/v2g/test_data.json b/specification/policies/tests/v2g/test_data.json new file mode 100644 index 00000000..bd8d552a --- /dev/null +++ b/specification/policies/tests/v2g/test_data.json @@ -0,0 +1,15 @@ +{ + "contract": { + "contractType": "V2G", + "inputs": { + "minChargeLevelPct": 20 + }, + "terms": { + "chargingRate": 8.0, + "v2gRate": 12.0 + } + }, + "charger_state": { + "v2gSupported": true + } +} diff --git a/specification/policies/tests/v2g/test_input.json b/specification/policies/tests/v2g/test_input.json new file mode 100644 index 00000000..8f639e75 --- /dev/null +++ b/specification/policies/tests/v2g/test_input.json @@ -0,0 +1,8 @@ +{ + "vehicleBatteryPct": 80, + "vehicleBatteryKwh": 60, + "v2gCapable": true, + "gridStress": true, + "totalChargedKwh": 30.0, + "totalDischargedKwh": 10.0 +} diff --git a/specification/policies/v2g.rego b/specification/policies/v2g.rego new file mode 100644 index 00000000..046c2c32 --- /dev/null +++ b/specification/policies/v2g.rego @@ -0,0 +1,50 @@ +# DEG Contract Policy — Vehicle-to-Grid (V2G) +# +# Composite policy combining EV charging with demand flexibility. +# Evaluates V2G eligibility, discharge decisions, and net payment direction. +# +# input: current vehicle/charger state (battery level, V2G capability, grid stress) +# data.contract: the committed DEGContract terms +# data.charger_state: real-time charger/EVSE state + +package deg.contracts.v2g + +import rego.v1 + +# --- V2G Eligibility --- + +v2g_eligible if { + input.vehicleBatteryPct > data.contract.inputs.minChargeLevelPct + input.v2gCapable == true + data.charger_state.v2gSupported == true +} + +# --- Discharge Decision --- + +should_discharge if { + v2g_eligible + input.gridStress == true +} + +# Max discharge without going below min charge +max_discharge_kwh := kwh if { + current_kwh := input.vehicleBatteryKwh * (input.vehicleBatteryPct / 100) + min_kwh := input.vehicleBatteryKwh * (data.contract.inputs.minChargeLevelPct / 100) + kwh := current_kwh - min_kwh + kwh > 0 +} + +max_discharge_kwh := 0 if { + current_kwh := input.vehicleBatteryKwh * (input.vehicleBatteryPct / 100) + min_kwh := input.vehicleBatteryKwh * (data.contract.inputs.minChargeLevelPct / 100) + current_kwh <= min_kwh +} + +# --- Net Payment Direction --- + +# Positive = EV owner pays, Negative = EV owner receives +net_payment := amount if { + charged := input.totalChargedKwh * data.contract.terms.chargingRate + discharged := input.totalDischargedKwh * data.contract.terms.v2gRate + amount := charged - discharged +} diff --git a/specification/schema/deg_contract_schema.yaml b/specification/schema/deg_contract_schema.yaml new file mode 100644 index 00000000..6e84f970 --- /dev/null +++ b/specification/schema/deg_contract_schema.yaml @@ -0,0 +1,609 @@ +openapi: 3.1.1 +info: + title: DEG Contract-as-Code Schema (v0.1) + version: 0.1.0-draft + description: > + Generalized contract schema for Digital Energy Grid (DEG) use cases. + Unifies EV Charging, P2P Energy Trading, and Demand Flexibility under + a single contract-as-code model that maps to Beckn Protocol v2 slots. + + This schema defines the structure for: + - DEGContract: The top-level contract template/agreement + - DEGRole: Participating roles with inputs and obligations + - DEGTerm: Condition-obligation pairs (policy expressions) + - EnergySpecification: What energy product is contracted + - FulfillmentSpecification: How the contract is physically fulfilled + - SettlementSpecification: How the contract is financially settled + +components: + schemas: + ######################################################################## + # DEG CONTRACT — Top-level contract template/agreement + ######################################################################## + DEGContract: + type: object + additionalProperties: true + required: [contractType, version, roles, terms] + x-jsonld: + "@context": ./context.jsonld + "@type": DEGContract + description: > + A generalized energy contract. Published as partially-filled template + in Offer.offerAttributes (catalog), then fully filled in + Order.orderAttributes (committed agreement). + properties: + + contractType: + type: string + enum: [EV_CHARGING, P2P_ENERGY_TRADE, DEMAND_FLEXIBILITY, V2G, COMPOSITE] + description: Primary contract classification. + x-jsonld: { "@id": contractType } + + version: + type: string + pattern: '^\d+\.\d+\.\d+$' + description: Semantic version of this contract template. + example: "1.0.0" + x-jsonld: { "@id": version } + + contractState: + type: string + enum: [TEMPLATE, NEGOTIATED, COMMITTED, ACTIVE, SETTLED, CANCELLED, EXPIRED] + description: > + Current state of the contract. TEMPLATE in catalog, + progresses through Beckn lifecycle. + x-jsonld: { "@id": contractState } + + roles: + type: array + minItems: 2 + items: + $ref: '#/components/schemas/DEGRole' + description: Participating roles with their inputs and obligations. + x-jsonld: { "@id": roles, "@container": "@set" } + + terms: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/DEGTerm' + description: Contract terms. Each is a condition-obligation pair. + x-jsonld: { "@id": terms, "@container": "@set" } + + energySpec: + $ref: '#/components/schemas/EnergySpecification' + x-jsonld: { "@id": energySpec } + + fulfillmentSpec: + $ref: '#/components/schemas/FulfillmentSpecification' + x-jsonld: { "@id": fulfillmentSpec } + + settlementSpec: + $ref: '#/components/schemas/SettlementSpecification' + x-jsonld: { "@id": settlementSpec } + + revenueModel: + $ref: '#/components/schemas/RevenueModel' + description: > + Multi-party revenue flow model. Declares all monetary flows + between roles. Sum of all flows must equal zero (net-zero invariant). + Enables any agent to compute its role's net revenue position. + x-jsonld: { "@id": revenueModel } + + ######################################################################## + # DEG ROLE — A participating party slot + ######################################################################## + DEGRole: + type: object + additionalProperties: true + required: [roleId, roleType] + x-jsonld: + "@context": ./context.jsonld + "@type": DEGRole + description: > + A role in the contract. When party is null, this is an open slot + in the template waiting for a party to accept. + properties: + + roleId: + type: string + description: Unique role identifier within this contract. + example: "seller" + x-jsonld: { "@id": roleId } + + roleType: + type: string + enum: [PRODUCER, CONSUMER, AGGREGATOR, GRID_OPERATOR, CPO, UTILITY, PROSUMER, DISCOM] + description: Functional role classification. + x-jsonld: { "@id": roleType } + + party: + type: object + nullable: true + description: > + Filled when a party accepts this role. null = open slot. + Maps to Beckn Provider (for sellers) or Buyer (for buyers). + properties: + partyId: + type: string + description: Unique identifier for the party. + x-jsonld: { "@id": partyId } + partyType: + type: string + enum: [PROVIDER, BUYER] + description: Whether this party maps to Beckn Provider or Buyer. + x-jsonld: { "@id": partyType } + platformId: + type: string + description: BAP or BPP subscriber ID on the Beckn network. + x-jsonld: { "@id": platformId } + attributes: + type: object + additionalProperties: true + description: > + Role-specific attributes. References existing attribute packs + (EnergyCustomer, ChargingService, etc.) via @context/@type. + x-jsonld: { "@id": attributes } + x-jsonld: { "@id": party } + + inputs: + type: array + items: + $ref: '#/components/schemas/RoleInput' + description: Parameters this role must supply. + x-jsonld: { "@id": inputs, "@container": "@set" } + + obligations: + type: array + items: + type: string + description: References to DEGTerm.termId where this role has an obligation. + x-jsonld: { "@id": obligations } + + ######################################################################## + # ROLE INPUT — A parameter slot within a role + ######################################################################## + RoleInput: + type: object + additionalProperties: true + required: [inputId, inputType] + x-jsonld: + "@context": ./context.jsonld + "@type": RoleInput + properties: + + inputId: + type: string + description: Unique input identifier. + example: "source_meter_id" + x-jsonld: { "@id": inputId } + + inputType: + type: string + enum: [PARAMETER, ITEM_REF, CREDENTIAL, SIGNAL, METER_ID, LOCATION, DER_ID] + description: Classification of this input. + x-jsonld: { "@id": inputType } + + required: + type: boolean + default: true + x-jsonld: { "@id": required } + + description: + type: string + x-jsonld: { "@id": description } + + schema: + type: object + description: JSON Schema for validating the input value. + x-jsonld: { "@id": schema } + + value: + description: > + The actual value. null or absent = unfilled slot (to be + provided by accepting party). + x-jsonld: { "@id": value } + + filledBy: + type: string + description: roleId of the party that fills this input. Default is the role owner. + x-jsonld: { "@id": filledBy } + + providedAt: + type: string + enum: [TEMPLATE, NEGOTIATION, COMMITMENT, FULFILLMENT] + description: > + When in the Beckn lifecycle this input must be provided. + TEMPLATE = on_search, NEGOTIATION = select, COMMITMENT = init/confirm, + FULFILLMENT = status/update. + x-jsonld: { "@id": providedAt } + + ######################################################################## + # DEG TERM — A condition-obligation pair + ######################################################################## + DEGTerm: + type: object + additionalProperties: true + required: [termId, termType] + x-jsonld: + "@context": ./context.jsonld + "@type": DEGTerm + properties: + + termId: + type: string + description: Unique term identifier. + example: "delivery_obligation" + x-jsonld: { "@id": termId } + + termType: + type: string + enum: [DELIVERY, PAYMENT, SIGNAL_RESPONSE, QUALITY, DEVIATION_PENALTY, CANCELLATION, ENROLLMENT] + description: Classification of this term. + x-jsonld: { "@id": termType } + + description: + type: string + description: Human-readable description of the term (Ricardian contract element). + x-jsonld: { "@id": description } + + condition: + $ref: '#/components/schemas/PolicyExpression' + description: When this term activates. + x-jsonld: { "@id": condition } + + obligation: + $ref: '#/components/schemas/PolicyExpression' + description: What must be true for this term to be satisfied. + x-jsonld: { "@id": obligation } + + consequence: + $ref: '#/components/schemas/PolicyExpression' + description: What happens if the obligation is violated. + x-jsonld: { "@id": consequence } + + appliesTo: + type: array + items: { type: string } + description: roleIds this term applies to. + x-jsonld: { "@id": appliesTo } + + ######################################################################## + # POLICY EXPRESSION — Declarative condition/rule + ######################################################################## + PolicyExpression: + type: object + additionalProperties: true + description: > + Declarative condition or rule. Supports three levels of complexity: + 1. Simple expr string (for basic conditions) + 2. Structured rule tree (for compound conditions) + 3. External policy reference (for complex Rego/Arazzo logic) + x-jsonld: + "@context": ./context.jsonld + "@type": PolicyExpression + properties: + + expr: + type: string + description: > + Simple expression string. JSONPath-like syntax over contract state. + Example: "deliveredQuantity >= 10.0 AND deliveredQuantity <= 50.0" + x-jsonld: { "@id": expr } + + rule: + type: object + description: Structured rule for compound conditions. + properties: + operator: + type: string + enum: [AND, OR, NOT, GTE, LTE, GT, LT, EQ, NEQ, WITHIN, BETWEEN, IF_THEN, IN] + operands: + type: array + items: + oneOf: + - $ref: '#/components/schemas/PolicyExpression' + - type: object + properties: + field: { type: string } + value: {} + x-jsonld: { "@id": rule } + + policyRef: + type: object + description: Reference to external policy (Rego, Arazzo workflow, webhook). + properties: + type: + type: string + enum: [REGO, ARAZZO, WEBHOOK] + description: Type of external policy. + uri: + type: string + format: uri + description: URI to the policy file or endpoint. + package: + type: string + description: For Rego, the package path. + entrypoint: + type: string + description: For Rego, the rule to evaluate. + workflowId: + type: string + description: For Arazzo, the workflow identifier. + stepId: + type: string + description: For Arazzo, the step identifier. + x-jsonld: { "@id": policyRef } + + ######################################################################## + # ENERGY SPECIFICATION — What is being contracted + ######################################################################## + EnergySpecification: + type: object + additionalProperties: true + x-jsonld: + "@context": ./context.jsonld + "@type": EnergySpecification + properties: + + energyType: + type: string + enum: [ELECTRICAL, THERMAL, GAS] + x-jsonld: { "@id": energyType } + + sourceType: + type: string + enum: [SOLAR, WIND, HYDRO, GRID, BATTERY, BIOMASS, ANY] + x-jsonld: { "@id": sourceType } + + quantity: + allOf: + - $ref: 'https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/attributes.yaml#/components/schemas/Quantity' + x-jsonld: { "@id": quantity } + + qualityConstraints: + type: object + properties: + greenCertRequired: + type: boolean + default: false + maxCarbonIntensity: + type: number + description: Maximum carbon intensity in gCO2/kWh. + powerFactor: + type: number + minimum: 0 + maximum: 1 + voltageRange: + type: object + properties: + min: { type: number } + max: { type: number } + x-jsonld: { "@id": qualityConstraints } + + ######################################################################## + # FULFILLMENT SPECIFICATION — How the contract is physically fulfilled + ######################################################################## + FulfillmentSpecification: + type: object + additionalProperties: true + x-jsonld: + "@context": ./context.jsonld + "@type": FulfillmentSpecification + properties: + + deliveryMode: + type: string + enum: [EV_CHARGING, BATTERY_SWAP, GRID_INJECTION, V2G, LOAD_CURTAILMENT, LOAD_SHIFT, VIRTUAL] + x-jsonld: { "@id": deliveryMode } + + deliveryWindow: + allOf: + - $ref: 'https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/attributes.yaml#/components/schemas/TimePeriod' + x-jsonld: { "@id": deliveryWindow } + + location: + allOf: + - $ref: 'https://raw.githubusercontent.com/beckn/protocol-specifications-new/refs/heads/main/schema/core/v2/attributes.yaml#/components/schemas/Location' + x-jsonld: { "@id": location } + + meterIds: + type: array + items: { type: string } + description: Meter identifiers for measuring fulfillment. + x-jsonld: { "@id": meterIds } + + signals: + type: array + items: + $ref: '#/components/schemas/SignalSpec' + description: Real-time signals used during fulfillment. + x-jsonld: { "@id": signals } + + ######################################################################## + # SIGNAL SPEC — A real-time signal source + ######################################################################## + SignalSpec: + type: object + additionalProperties: true + x-jsonld: + "@context": ./context.jsonld + "@type": SignalSpec + properties: + + signalType: + type: string + enum: [GRID_FREQUENCY, PRICE_SIGNAL, CURTAILMENT_COMMAND, START_STOP, METER_READING, WEATHER] + x-jsonld: { "@id": signalType } + + source: + type: string + description: URI of the signal source. + x-jsonld: { "@id": source } + + protocol: + type: string + enum: [MQTT, HTTP_WEBHOOK, OCPP, OPENADR, WEBSOCKET, IEC_61850] + x-jsonld: { "@id": protocol } + + sampleRate: + type: string + description: How often the signal is sampled (ISO 8601 duration). + example: "PT15M" + x-jsonld: { "@id": sampleRate } + + ######################################################################## + # SETTLEMENT SPECIFICATION — How the contract is financially settled + ######################################################################## + SettlementSpecification: + type: object + additionalProperties: true + x-jsonld: + "@context": ./context.jsonld + "@type": SettlementSpecification + properties: + + paymentType: + type: string + enum: [PRE_ORDER, POST_DELIVERY, ESCROW, SUBSCRIPTION, SETTLEMENT_CYCLE] + x-jsonld: { "@id": paymentType } + + currency: + type: string + description: ISO 4217 currency code. + example: "INR" + x-jsonld: { "@id": currency } + + settlementCycle: + type: string + enum: [IMMEDIATE, 15_MIN, HOURLY, DAILY, WEEKLY, MONTHLY] + description: How often settlement occurs. + x-jsonld: { "@id": settlementCycle } + + escrowRequired: + type: boolean + default: false + x-jsonld: { "@id": escrowRequired } + + acceptedPaymentMethods: + type: array + items: + type: string + enum: [UPI, CREDIT_CARD, DEBIT_CARD, WALLET, BANK_TRANSFER, ENERGY_CREDIT] + x-jsonld: { "@id": acceptedPaymentMethods } + + ######################################################################## + # REVENUE MODEL — Multi-party net-zero monetary flows + ######################################################################## + RevenueModel: + type: object + additionalProperties: true + required: [flows] + x-jsonld: + "@context": ./context.jsonld + "@type": RevenueModel + description: > + Declares all monetary flows between contract roles. The net-zero + invariant requires that sum(all flow.computedAmount) == 0 at settlement. + Any agent can compute its role's net position by filtering flows + where it is `from` (outflow) or `to` (inflow). + properties: + + flows: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/RevenueFlow' + description: Directed monetary flows between roles. + x-jsonld: { "@id": flows, "@container": "@set" } + + netZeroCheck: + $ref: '#/components/schemas/PolicyExpression' + description: > + Optional validation expression for the net-zero invariant. + Default: sum(flows[*].computedAmount) == 0 + x-jsonld: { "@id": netZeroCheck } + + ######################################################################## + # REVENUE FLOW — A single directed monetary transfer + ######################################################################## + RevenueFlow: + type: object + additionalProperties: true + required: [flowId, from, to, flowType, formula] + x-jsonld: + "@context": ./context.jsonld + "@type": RevenueFlow + description: > + A directed monetary flow from one role to another. + Positive computedAmount = transfer in the from→to direction. + properties: + + flowId: + type: string + description: Unique flow identifier within the contract. + example: "energy_payment" + x-jsonld: { "@id": flowId } + + from: + type: string + description: roleId of the paying party. + example: "buyer" + x-jsonld: { "@id": from } + + to: + type: string + description: roleId of the receiving party. + example: "seller" + x-jsonld: { "@id": to } + + flowType: + type: string + enum: + - ENERGY_PAYMENT + - WHEELING_CHARGE + - PLATFORM_FEE + - INCENTIVE + - DEVIATION_PENALTY + - CANCELLATION_FEE + - TAX + - CREDIT + - ESCROW_RELEASE + - GRID_SERVICE_FEE + - GREEN_CERTIFICATE + description: Classification of the monetary flow. + x-jsonld: { "@id": flowType } + + formula: + $ref: '#/components/schemas/PolicyExpression' + description: > + Expression that computes the flow amount from contract state. + Can be a simple expr, structured rule, or Rego policy reference. + x-jsonld: { "@id": formula } + + currency: + type: string + default: "INR" + description: ISO 4217 currency code for this flow. + x-jsonld: { "@id": currency } + + computedAmount: + type: number + description: > + Filled at settlement time. The evaluated result of the formula. + Positive = transfer in the from→to direction. + Negative = reversal (refund, credit). + x-jsonld: { "@id": computedAmount } + + triggeredBy: + type: string + description: > + termId that triggers this flow. Links revenue to contract terms. + When the referenced term's condition is met, this flow is activated. + x-jsonld: { "@id": triggeredBy } + + schedule: + type: string + enum: [IMMEDIATE, ON_DELIVERY, ON_SETTLEMENT, ON_EVENT, RECURRING] + description: When this flow is executed/settled. + x-jsonld: { "@id": schedule }