From f681e134cc6c9a30513558ea8b495c4fc93b580f Mon Sep 17 00:00:00 2001 From: stewart hu Date: Tue, 1 Sep 2026 21:50:22 -0400 Subject: [PATCH] M1: domain schemas, contracts pipeline, ledger and snapshot services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - packages/domain: zod schemas for the 07-scenario object set with a schemaRegistry driving schema export, fixture generation, and Python module naming; decimal-string/sha256/curve primitives enforce the docs/11 §3.3 representation rules at the type layer - contracts/: 12 JSON Schemas (draft 2020-12), 12 golden fixtures, 3 invalid fixtures crafted to fail on both sides (float money, bad digest, missing concurrency version) - skills-py: generated pydantic models (committed, never hand-edited), regeneration script, mirror pytest using JSON-mode validation — dual-side contract tests agree on all 15 fixtures - packages/services: content-addressed immutable snapshot store (memory + fs, rejects floats), LedgerService v1 with optimistic concurrency and the P7 constraint cascade (monthly position bounds day-ahead bids; no-anchor bids rejected), quality-gate stub - cascade band shape is an OPEN-QUESTION A5 placeholder (pro-rata daily share ±band) — see ledger.ts checkCascade All green: tsc typecheck, 41 TS tests, 15 Python tests. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019u5SLNweVio6ozJX7yfxQr 🔮 View transcript: https://logs.lojong.info/s/e8u90k3t33w590r7b5y7yzqh --- .../fixtures-invalid/approval/bad-digest.json | 28 + .../position_update/missing-version.json | 110 ++ .../proposal/bid-float-money.json | 239 +++ contracts/fixtures/approval/approve.json | 28 + contracts/fixtures/decision_case/da-bid.json | 23 + contracts/fixtures/envelope/bid-envelope.json | 30 + .../event_envelope/situation-published.json | 11 + .../fixtures/execution_permit/active.json | 11 + .../da-aggregation-unit.json | 215 +++ .../fixtures/forecast_bundle/load-da.json | 320 ++++ contracts/fixtures/ledger_view/basic.json | 15 + .../fixtures/position_update/award-da.json | 111 ++ contracts/fixtures/proposal/bid-basic.json | 239 +++ .../fixtures/resource_profile/storage.json | 17 + .../fixtures/situation_report/normal-day.json | 18 + contracts/schema/approval.json | 136 ++ contracts/schema/decision_case.json | 134 ++ contracts/schema/envelope.json | 143 ++ contracts/schema/event_envelope.json | 44 + contracts/schema/execution_permit.json | 62 + contracts/schema/flexibility_envelope.json | 120 ++ contracts/schema/forecast_bundle.json | 164 ++ contracts/schema/ledger_view.json | 112 ++ contracts/schema/position_update.json | 94 + contracts/schema/proposal.json | 255 +++ contracts/schema/resource_profile.json | 102 + contracts/schema/situation_report.json | 80 + package-lock.json | 1700 +++++++++++++++++ package.json | 14 + packages/domain/package.json | 23 + packages/domain/scripts/export-schemas.ts | 22 + packages/domain/scripts/make-fixtures.ts | 244 +++ packages/domain/src/approval.ts | 38 + packages/domain/src/case.ts | 28 + packages/domain/src/common.ts | 35 + packages/domain/src/envelope.ts | 48 + packages/domain/src/event.ts | 17 + packages/domain/src/forecast.ts | 22 + packages/domain/src/index.ts | 40 + packages/domain/src/ledger.ts | 32 + packages/domain/src/proposal.ts | 75 + packages/domain/src/resource.ts | 36 + packages/domain/src/situation.ts | 21 + packages/domain/test/fixtures.test.ts | 41 + packages/domain/tsconfig.json | 8 + packages/services/package.json | 21 + packages/services/src/index.ts | 3 + packages/services/src/ledger.ts | 102 + packages/services/src/quality.ts | 20 + packages/services/src/snapshot.ts | 85 + packages/services/test/ledger.test.ts | 77 + packages/services/test/snapshot.test.ts | 39 + packages/services/tsconfig.json | 8 + skills-py/requirements.txt | 3 + skills-py/scripts/generate_models.sh | 16 + skills-py/tests/test_fixtures.py | 37 + skills-py/vpp_contracts/__init__.py | 2 + skills-py/vpp_contracts/approval.py | 68 + skills-py/vpp_contracts/decision_case.py | 51 + skills-py/vpp_contracts/envelope.py | 93 + skills-py/vpp_contracts/event_envelope.py | 21 + skills-py/vpp_contracts/execution_permit.py | 21 + .../vpp_contracts/flexibility_envelope.py | 59 + skills-py/vpp_contracts/forecast_bundle.py | 82 + skills-py/vpp_contracts/ledger_view.py | 67 + skills-py/vpp_contracts/position_update.py | 51 + skills-py/vpp_contracts/proposal.py | 132 ++ skills-py/vpp_contracts/resource_profile.py | 42 + skills-py/vpp_contracts/situation_report.py | 43 + tsconfig.base.json | 15 + 70 files changed, 6463 insertions(+) create mode 100644 contracts/fixtures-invalid/approval/bad-digest.json create mode 100644 contracts/fixtures-invalid/position_update/missing-version.json create mode 100644 contracts/fixtures-invalid/proposal/bid-float-money.json create mode 100644 contracts/fixtures/approval/approve.json create mode 100644 contracts/fixtures/decision_case/da-bid.json create mode 100644 contracts/fixtures/envelope/bid-envelope.json create mode 100644 contracts/fixtures/event_envelope/situation-published.json create mode 100644 contracts/fixtures/execution_permit/active.json create mode 100644 contracts/fixtures/flexibility_envelope/da-aggregation-unit.json create mode 100644 contracts/fixtures/forecast_bundle/load-da.json create mode 100644 contracts/fixtures/ledger_view/basic.json create mode 100644 contracts/fixtures/position_update/award-da.json create mode 100644 contracts/fixtures/proposal/bid-basic.json create mode 100644 contracts/fixtures/resource_profile/storage.json create mode 100644 contracts/fixtures/situation_report/normal-day.json create mode 100644 contracts/schema/approval.json create mode 100644 contracts/schema/decision_case.json create mode 100644 contracts/schema/envelope.json create mode 100644 contracts/schema/event_envelope.json create mode 100644 contracts/schema/execution_permit.json create mode 100644 contracts/schema/flexibility_envelope.json create mode 100644 contracts/schema/forecast_bundle.json create mode 100644 contracts/schema/ledger_view.json create mode 100644 contracts/schema/position_update.json create mode 100644 contracts/schema/proposal.json create mode 100644 contracts/schema/resource_profile.json create mode 100644 contracts/schema/situation_report.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 packages/domain/package.json create mode 100644 packages/domain/scripts/export-schemas.ts create mode 100644 packages/domain/scripts/make-fixtures.ts create mode 100644 packages/domain/src/approval.ts create mode 100644 packages/domain/src/case.ts create mode 100644 packages/domain/src/common.ts create mode 100644 packages/domain/src/envelope.ts create mode 100644 packages/domain/src/event.ts create mode 100644 packages/domain/src/forecast.ts create mode 100644 packages/domain/src/index.ts create mode 100644 packages/domain/src/ledger.ts create mode 100644 packages/domain/src/proposal.ts create mode 100644 packages/domain/src/resource.ts create mode 100644 packages/domain/src/situation.ts create mode 100644 packages/domain/test/fixtures.test.ts create mode 100644 packages/domain/tsconfig.json create mode 100644 packages/services/package.json create mode 100644 packages/services/src/index.ts create mode 100644 packages/services/src/ledger.ts create mode 100644 packages/services/src/quality.ts create mode 100644 packages/services/src/snapshot.ts create mode 100644 packages/services/test/ledger.test.ts create mode 100644 packages/services/test/snapshot.test.ts create mode 100644 packages/services/tsconfig.json create mode 100644 skills-py/requirements.txt create mode 100755 skills-py/scripts/generate_models.sh create mode 100644 skills-py/tests/test_fixtures.py create mode 100644 skills-py/vpp_contracts/__init__.py create mode 100644 skills-py/vpp_contracts/approval.py create mode 100644 skills-py/vpp_contracts/decision_case.py create mode 100644 skills-py/vpp_contracts/envelope.py create mode 100644 skills-py/vpp_contracts/event_envelope.py create mode 100644 skills-py/vpp_contracts/execution_permit.py create mode 100644 skills-py/vpp_contracts/flexibility_envelope.py create mode 100644 skills-py/vpp_contracts/forecast_bundle.py create mode 100644 skills-py/vpp_contracts/ledger_view.py create mode 100644 skills-py/vpp_contracts/position_update.py create mode 100644 skills-py/vpp_contracts/proposal.py create mode 100644 skills-py/vpp_contracts/resource_profile.py create mode 100644 skills-py/vpp_contracts/situation_report.py create mode 100644 tsconfig.base.json diff --git a/contracts/fixtures-invalid/approval/bad-digest.json b/contracts/fixtures-invalid/approval/bad-digest.json new file mode 100644 index 0000000..c355c83 --- /dev/null +++ b/contracts/fixtures-invalid/approval/bad-digest.json @@ -0,0 +1,28 @@ +{ + "id": "appr-001", + "proposal_digest": "not-a-digest", + "decision": "APPROVE", + "approver": { + "id": "user-trader-01", + "role": "senior-trader" + }, + "scope": { + "effect_type": "BID", + "limits": { + "max_energy_mwh": "1500.0" + } + }, + "validity": { + "from": "2026-03-14T08:30:00Z", + "to": "2026-03-15T23:59:59Z" + }, + "evidence_versions": { + "policy_pack_version": "2026.03", + "ledger_version": 42, + "data_snapshot_refs": [ + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ] + }, + "comment": "within monthly position band", + "decided_at": "2026-03-14T08:30:00Z" +} diff --git a/contracts/fixtures-invalid/position_update/missing-version.json b/contracts/fixtures-invalid/position_update/missing-version.json new file mode 100644 index 0000000..a38114b --- /dev/null +++ b/contracts/fixtures-invalid/position_update/missing-version.json @@ -0,0 +1,110 @@ +{ + "id": "pos-001", + "timescale": "DAY_AHEAD", + "period": "2026-03-15", + "kind": "AWARD", + "energy_mwh": "1180.0", + "curve": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3" + ] + }, + "source_ref": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +} diff --git a/contracts/fixtures-invalid/proposal/bid-float-money.json b/contracts/fixtures-invalid/proposal/bid-float-money.json new file mode 100644 index 0000000..4903582 --- /dev/null +++ b/contracts/fixtures-invalid/proposal/bid-float-money.json @@ -0,0 +1,239 @@ +{ + "id": "prop-001", + "type": "BID", + "timescale": "DAY_AHEAD", + "originator": { + "agent": "trading-agent", + "trigger": "SCHEDULED", + "task_id": "task-001" + }, + "payload": { + "kind": "BID", + "market_date": "2026-03-15", + "prices_yuan_per_mwh": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50" + ] + }, + "quantities_mwh": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5" + ] + }, + "expected_revenue_yuan": 510600 + }, + "lineage": { + "tool_calls": [ + { + "tool_call_id": "tc-001", + "tool": "bid-optimization-milp", + "version": "1.0.0", + "inputs_ref": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "outputs_ref": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } + ], + "data_refs": [ + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ], + "ledger_version": 42, + "policy_pack_version": "2026.03" + }, + "envelope_ref": "env-bid-001", + "digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "status": "DRAFT", + "created_at": "2026-03-14T08:30:00Z" +} diff --git a/contracts/fixtures/approval/approve.json b/contracts/fixtures/approval/approve.json new file mode 100644 index 0000000..433c9cd --- /dev/null +++ b/contracts/fixtures/approval/approve.json @@ -0,0 +1,28 @@ +{ + "id": "appr-001", + "proposal_digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "decision": "APPROVE", + "approver": { + "id": "user-trader-01", + "role": "senior-trader" + }, + "scope": { + "effect_type": "BID", + "limits": { + "max_energy_mwh": "1500.0" + } + }, + "validity": { + "from": "2026-03-14T08:30:00Z", + "to": "2026-03-15T23:59:59Z" + }, + "evidence_versions": { + "policy_pack_version": "2026.03", + "ledger_version": 42, + "data_snapshot_refs": [ + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ] + }, + "comment": "within monthly position band", + "decided_at": "2026-03-14T08:30:00Z" +} diff --git a/contracts/fixtures/decision_case/da-bid.json b/contracts/fixtures/decision_case/da-bid.json new file mode 100644 index 0000000..56474c6 --- /dev/null +++ b/contracts/fixtures/decision_case/da-bid.json @@ -0,0 +1,23 @@ +{ + "id": "case-001", + "kind": "DAY_AHEAD_BID", + "objective": "Complete day-ahead bid for 2026-03-15", + "owner": "user-trader-01", + "deadline": "2026-03-14T10:00:00Z", + "status": "AWAITING_APPROVAL", + "refs": { + "evidence": [ + "sit-001", + "fc-001" + ], + "proposals": [ + "prop-001" + ], + "approvals": [], + "permits": [], + "scenarios": [], + "outcome": null + }, + "opened_at": "2026-03-14T06:00:00Z", + "closed_at": null +} diff --git a/contracts/fixtures/envelope/bid-envelope.json b/contracts/fixtures/envelope/bid-envelope.json new file mode 100644 index 0000000..cccbb8b --- /dev/null +++ b/contracts/fixtures/envelope/bid-envelope.json @@ -0,0 +1,30 @@ +{ + "id": "env-bid-001", + "scope": { + "proposal_type": "BID", + "timescales": [ + "DAY_AHEAD" + ], + "resource_set": "pool-hubei-01" + }, + "bounds": { + "price_deviation_pct": "5.0", + "max_energy_mwh": "1500.0" + }, + "validity": { + "from": "2026-03-14T06:00:00Z", + "to": "2026-03-15T23:59:59Z" + }, + "approval": { + "level": "L2", + "approved_by": [ + "user-ops-lead", + "user-risk-officer" + ] + }, + "escalation": { + "max_consecutive_deviations": 3, + "deviation_threshold_pct": "10.0" + }, + "status": "ACTIVE" +} diff --git a/contracts/fixtures/event_envelope/situation-published.json b/contracts/fixtures/event_envelope/situation-published.json new file mode 100644 index 0000000..9d32fe4 --- /dev/null +++ b/contracts/fixtures/event_envelope/situation-published.json @@ -0,0 +1,11 @@ +{ + "event_id": "evt-001", + "event_type": "SituationReportPublished", + "schema_version": "1.0.0", + "occurred_at": "2026-03-14T06:00:00Z", + "causation_id": "task-001", + "correlation_id": "case-001", + "payload": { + "situation_report_id": "sit-001" + } +} diff --git a/contracts/fixtures/execution_permit/active.json b/contracts/fixtures/execution_permit/active.json new file mode 100644 index 0000000..8e87fb2 --- /dev/null +++ b/contracts/fixtures/execution_permit/active.json @@ -0,0 +1,11 @@ +{ + "id": "permit-001", + "proposal_digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "issued_at": "2026-03-14T08:30:00Z", + "expires_at": "2026-03-15T23:59:59Z", + "effect_limits": { + "max_energy_mwh": "1500.0" + }, + "revoked_at": null, + "issuer": "authority-service" +} diff --git a/contracts/fixtures/flexibility_envelope/da-aggregation-unit.json b/contracts/fixtures/flexibility_envelope/da-aggregation-unit.json new file mode 100644 index 0000000..dea0dee --- /dev/null +++ b/contracts/fixtures/flexibility_envelope/da-aggregation-unit.json @@ -0,0 +1,215 @@ +{ + "id": "flex-001", + "provider": { + "unit_id": "agg-unit-wuhan-01", + "level": "AGGREGATION_UNIT" + }, + "market_date": "2026-03-15", + "up_mw": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5", + "8.5" + ] + }, + "down_mw": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0", + "6.0" + ] + }, + "valid_until": "2026-03-15T23:59:59Z", + "schema_version": "1.0.0", + "signature": null +} diff --git a/contracts/fixtures/forecast_bundle/load-da.json b/contracts/fixtures/forecast_bundle/load-da.json new file mode 100644 index 0000000..0a0fe9d --- /dev/null +++ b/contracts/fixtures/forecast_bundle/load-da.json @@ -0,0 +1,320 @@ +{ + "id": "fc-001", + "kind": "LOAD", + "market_date": "2026-03-15", + "unit": "mw", + "quantiles": { + "p10": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1", + "40.1" + ] + }, + "p50": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7", + "45.7" + ] + }, + "p90": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3", + "52.3" + ] + } + }, + "model": { + "name": "load-forecast", + "version": "1.2.0" + }, + "features_snapshot_ref": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "generated_at": "2026-03-14T06:00:00Z" +} diff --git a/contracts/fixtures/ledger_view/basic.json b/contracts/fixtures/ledger_view/basic.json new file mode 100644 index 0000000..0c34b59 --- /dev/null +++ b/contracts/fixtures/ledger_view/basic.json @@ -0,0 +1,15 @@ +{ + "version": 43, + "entries": [ + { + "id": "pos-000", + "timescale": "MONTHLY", + "period": "2026-03", + "kind": "CONTRACT", + "energy_mwh": "36000.0", + "curve": null, + "source_ref": "contract-2026-03-001", + "recorded_at": "2026-03-14T06:00:00Z" + } + ] +} diff --git a/contracts/fixtures/position_update/award-da.json b/contracts/fixtures/position_update/award-da.json new file mode 100644 index 0000000..af1e2b6 --- /dev/null +++ b/contracts/fixtures/position_update/award-da.json @@ -0,0 +1,111 @@ +{ + "id": "pos-001", + "timescale": "DAY_AHEAD", + "period": "2026-03-15", + "kind": "AWARD", + "energy_mwh": "1180.0", + "curve": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3", + "12.3" + ] + }, + "source_ref": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "expected_version": 42 +} diff --git a/contracts/fixtures/proposal/bid-basic.json b/contracts/fixtures/proposal/bid-basic.json new file mode 100644 index 0000000..93d83ba --- /dev/null +++ b/contracts/fixtures/proposal/bid-basic.json @@ -0,0 +1,239 @@ +{ + "id": "prop-001", + "type": "BID", + "timescale": "DAY_AHEAD", + "originator": { + "agent": "trading-agent", + "trigger": "SCHEDULED", + "task_id": "task-001" + }, + "payload": { + "kind": "BID", + "market_date": "2026-03-15", + "prices_yuan_per_mwh": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50", + "425.50" + ] + }, + "quantities_mwh": { + "interval_minutes": 15, + "date": "2026-03-15", + "values": [ + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5", + "12.5" + ] + }, + "expected_revenue_yuan": "510600.00" + }, + "lineage": { + "tool_calls": [ + { + "tool_call_id": "tc-001", + "tool": "bid-optimization-milp", + "version": "1.0.0", + "inputs_ref": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "outputs_ref": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + } + ], + "data_refs": [ + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ], + "ledger_version": 42, + "policy_pack_version": "2026.03" + }, + "envelope_ref": "env-bid-001", + "digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "status": "DRAFT", + "created_at": "2026-03-14T08:30:00Z" +} diff --git a/contracts/fixtures/resource_profile/storage.json b/contracts/fixtures/resource_profile/storage.json new file mode 100644 index 0000000..706e6f4 --- /dev/null +++ b/contracts/fixtures/resource_profile/storage.json @@ -0,0 +1,17 @@ +{ + "resource_id": "res-storage-01", + "name": "Wuhan industrial park storage #1", + "type": "STORAGE", + "rated_power_mw": "10.0", + "certified_adjustable_mw": "8.0", + "confidence": "0.92", + "reliability_score": "0.88", + "constraints": { + "min_duration_min": 60, + "recovery_rate_mw_per_min": "0.5" + }, + "evidence_refs": [ + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ], + "updated_at": "2026-03-14T06:00:00Z" +} diff --git a/contracts/fixtures/situation_report/normal-day.json b/contracts/fixtures/situation_report/normal-day.json new file mode 100644 index 0000000..666673e --- /dev/null +++ b/contracts/fixtures/situation_report/normal-day.json @@ -0,0 +1,18 @@ +{ + "id": "sit-001", + "market_date": "2026-03-15", + "risk_level": "LOW", + "findings": [ + { + "kind": "TREND", + "summary": "mild temperatures, load near seasonal norm", + "refs": [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ] + } + ], + "forecast_refs": [ + "fc-001" + ], + "generated_at": "2026-03-14T06:00:00Z" +} diff --git a/contracts/schema/approval.json b/contracts/schema/approval.json new file mode 100644 index 0000000..2680a91 --- /dev/null +++ b/contracts/schema/approval.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "proposal_digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "decision": { + "type": "string", + "enum": [ + "APPROVE", + "REJECT" + ] + }, + "approver": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "role": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "role" + ], + "additionalProperties": false + }, + "scope": { + "type": "object", + "properties": { + "effect_type": { + "type": "string", + "enum": [ + "BID", + "INVITATION", + "CONTROL_PLAN", + "DISPATCH_PLAN" + ] + }, + "limits": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "effect_type", + "limits" + ], + "additionalProperties": false + }, + "validity": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "to": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + } + }, + "required": [ + "from", + "to" + ], + "additionalProperties": false + }, + "evidence_versions": { + "type": "object", + "properties": { + "policy_pack_version": { + "type": "string", + "minLength": 1 + }, + "ledger_version": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "data_snapshot_refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + }, + "required": [ + "policy_pack_version", + "ledger_version", + "data_snapshot_refs" + ], + "additionalProperties": false + }, + "comment": { + "type": "string" + }, + "decided_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + } + }, + "required": [ + "id", + "proposal_digest", + "decision", + "approver", + "scope", + "validity", + "evidence_versions", + "decided_at" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/approval.json", + "title": "Approval" +} diff --git a/contracts/schema/decision_case.json b/contracts/schema/decision_case.json new file mode 100644 index 0000000..7e61e1d --- /dev/null +++ b/contracts/schema/decision_case.json @@ -0,0 +1,134 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "enum": [ + "DAY_AHEAD_BID", + "DR_EVENT", + "DEVIATION_RESOLUTION", + "REVIEW", + "ADHOC_ANALYSIS" + ] + }, + "objective": { + "type": "string", + "minLength": 1 + }, + "owner": { + "type": "string", + "minLength": 1 + }, + "deadline": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "IN_PROGRESS", + "AWAITING_APPROVAL", + "CLOSED_DONE", + "CLOSED_ABORTED" + ] + }, + "refs": { + "type": "object", + "properties": { + "evidence": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "proposals": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "approvals": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "permits": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "scenarios": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "outcome": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "evidence", + "proposals", + "approvals", + "permits", + "scenarios", + "outcome" + ], + "additionalProperties": false + }, + "opened_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "closed_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "kind", + "objective", + "owner", + "deadline", + "status", + "refs", + "opened_at", + "closed_at" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/decision_case.json", + "title": "DecisionCase" +} diff --git a/contracts/schema/envelope.json b/contracts/schema/envelope.json new file mode 100644 index 0000000..279566a --- /dev/null +++ b/contracts/schema/envelope.json @@ -0,0 +1,143 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "scope": { + "type": "object", + "properties": { + "proposal_type": { + "type": "string", + "enum": [ + "BID", + "INVITATION", + "CONTROL_PLAN", + "DISPATCH_PLAN" + ] + }, + "timescales": { + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "enum": [ + "ANNUAL", + "MONTHLY", + "DAY_AHEAD", + "INTRADAY", + "REALTIME" + ] + } + }, + "resource_set": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "proposal_type", + "timescales", + "resource_set" + ], + "additionalProperties": false + }, + "bounds": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + }, + "validity": { + "type": "object", + "properties": { + "from": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "to": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + } + }, + "required": [ + "from", + "to" + ], + "additionalProperties": false + }, + "approval": { + "type": "object", + "properties": { + "level": { + "type": "string", + "enum": [ + "L1", + "L2", + "L3" + ] + }, + "approved_by": { + "minItems": 1, + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "level", + "approved_by" + ], + "additionalProperties": false + }, + "escalation": { + "type": "object", + "properties": { + "max_consecutive_deviations": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "deviation_threshold_pct": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + }, + "required": [ + "max_consecutive_deviations", + "deviation_threshold_pct" + ], + "additionalProperties": false + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "SUSPENDED", + "EXPIRED" + ] + } + }, + "required": [ + "id", + "scope", + "bounds", + "validity", + "approval", + "escalation", + "status" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/envelope.json", + "title": "Envelope" +} diff --git a/contracts/schema/event_envelope.json b/contracts/schema/event_envelope.json new file mode 100644 index 0000000..a0884c4 --- /dev/null +++ b/contracts/schema/event_envelope.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "event_id": { + "type": "string", + "minLength": 1 + }, + "event_type": { + "type": "string", + "minLength": 1 + }, + "schema_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "causation_id": { + "type": "string", + "minLength": 1 + }, + "correlation_id": { + "type": "string", + "minLength": 1 + }, + "payload": {} + }, + "required": [ + "event_id", + "event_type", + "schema_version", + "occurred_at", + "causation_id", + "correlation_id", + "payload" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/event_envelope.json", + "title": "EventEnvelope" +} diff --git a/contracts/schema/execution_permit.json b/contracts/schema/execution_permit.json new file mode 100644 index 0000000..07ea4d9 --- /dev/null +++ b/contracts/schema/execution_permit.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "proposal_digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "issued_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "effect_limits": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + }, + "revoked_at": { + "anyOf": [ + { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + { + "type": "null" + } + ] + }, + "issuer": { + "type": "string", + "const": "authority-service" + } + }, + "required": [ + "id", + "proposal_digest", + "issued_at", + "expires_at", + "effect_limits", + "revoked_at", + "issuer" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/execution_permit.json", + "title": "ExecutionPermit" +} diff --git a/contracts/schema/flexibility_envelope.json b/contracts/schema/flexibility_envelope.json new file mode 100644 index 0000000..0e2c70f --- /dev/null +++ b/contracts/schema/flexibility_envelope.json @@ -0,0 +1,120 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "provider": { + "type": "object", + "properties": { + "unit_id": { + "type": "string", + "minLength": 1 + }, + "level": { + "type": "string", + "enum": [ + "SITE", + "AGGREGATION_UNIT", + "PROVINCE" + ] + } + }, + "required": [ + "unit_id", + "level" + ], + "additionalProperties": false + }, + "market_date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "up_mw": { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + "down_mw": { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + "valid_until": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "schema_version": { + "type": "string", + "minLength": 1 + }, + "signature": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "provider", + "market_date", + "up_mw", + "down_mw", + "valid_until", + "schema_version", + "signature" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/flexibility_envelope.json", + "title": "FlexibilityEnvelope" +} diff --git a/contracts/schema/forecast_bundle.json b/contracts/schema/forecast_bundle.json new file mode 100644 index 0000000..5ff1cbb --- /dev/null +++ b/contracts/schema/forecast_bundle.json @@ -0,0 +1,164 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "enum": [ + "LOAD", + "PV", + "PRICE" + ] + }, + "market_date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "unit": { + "type": "string", + "enum": [ + "mw", + "yuan_per_mwh" + ] + }, + "quantiles": { + "type": "object", + "properties": { + "p10": { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + "p50": { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + "p90": { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + } + }, + "required": [ + "p10", + "p50", + "p90" + ], + "additionalProperties": false + }, + "model": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "name", + "version" + ], + "additionalProperties": false + }, + "features_snapshot_ref": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "generated_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + } + }, + "required": [ + "id", + "kind", + "market_date", + "unit", + "quantiles", + "model", + "features_snapshot_ref", + "generated_at" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/forecast_bundle.json", + "title": "ForecastBundle" +} diff --git a/contracts/schema/ledger_view.json b/contracts/schema/ledger_view.json new file mode 100644 index 0000000..d338c16 --- /dev/null +++ b/contracts/schema/ledger_view.json @@ -0,0 +1,112 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "version": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "entries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "timescale": { + "type": "string", + "enum": [ + "ANNUAL", + "MONTHLY", + "DAY_AHEAD", + "INTRADAY", + "REALTIME" + ] + }, + "period": { + "type": "string", + "pattern": "^\\d{4}(-\\d{2}(-\\d{2})?)?$" + }, + "kind": { + "type": "string", + "enum": [ + "CONTRACT", + "BID_SUBMITTED", + "AWARD", + "EXTERNAL_COMMITMENT" + ] + }, + "energy_mwh": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + }, + "curve": { + "anyOf": [ + { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "source_ref": { + "type": "string", + "minLength": 1 + }, + "recorded_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + } + }, + "required": [ + "id", + "timescale", + "period", + "kind", + "energy_mwh", + "curve", + "source_ref", + "recorded_at" + ], + "additionalProperties": false + } + } + }, + "required": [ + "version", + "entries" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/ledger_view.json", + "title": "LedgerView" +} diff --git a/contracts/schema/position_update.json b/contracts/schema/position_update.json new file mode 100644 index 0000000..32dac02 --- /dev/null +++ b/contracts/schema/position_update.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "timescale": { + "type": "string", + "enum": [ + "ANNUAL", + "MONTHLY", + "DAY_AHEAD", + "INTRADAY", + "REALTIME" + ] + }, + "period": { + "type": "string", + "pattern": "^\\d{4}(-\\d{2}(-\\d{2})?)?$" + }, + "kind": { + "type": "string", + "enum": [ + "CONTRACT", + "BID_SUBMITTED", + "AWARD", + "EXTERNAL_COMMITMENT" + ] + }, + "energy_mwh": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + }, + "curve": { + "anyOf": [ + { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "source_ref": { + "type": "string", + "minLength": 1 + }, + "expected_version": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "id", + "timescale", + "period", + "kind", + "energy_mwh", + "curve", + "source_ref", + "expected_version" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/position_update.json", + "title": "PositionUpdate" +} diff --git a/contracts/schema/proposal.json b/contracts/schema/proposal.json new file mode 100644 index 0000000..bbeb95b --- /dev/null +++ b/contracts/schema/proposal.json @@ -0,0 +1,255 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "timescale": { + "type": "string", + "enum": [ + "ANNUAL", + "MONTHLY", + "DAY_AHEAD", + "INTRADAY", + "REALTIME" + ] + }, + "originator": { + "type": "object", + "properties": { + "agent": { + "type": "string", + "minLength": 1 + }, + "trigger": { + "type": "string", + "enum": [ + "MANUAL", + "EVENT", + "SCHEDULED" + ] + }, + "task_id": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "agent", + "trigger", + "task_id" + ], + "additionalProperties": false + }, + "lineage": { + "type": "object", + "properties": { + "tool_calls": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tool_call_id": { + "type": "string", + "minLength": 1 + }, + "tool": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "minLength": 1 + }, + "inputs_ref": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "outputs_ref": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + }, + "required": [ + "tool_call_id", + "tool", + "version", + "inputs_ref", + "outputs_ref" + ], + "additionalProperties": false + } + }, + "data_refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + }, + "ledger_version": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "policy_pack_version": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "tool_calls", + "data_refs", + "ledger_version", + "policy_pack_version" + ], + "additionalProperties": false + }, + "envelope_ref": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] + }, + "digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "RULE_CHECK", + "SIMULATION", + "ENVELOPE_CHECK", + "PENDING_HUMAN", + "AUTO_APPROVED", + "APPROVED", + "FRESH_CHECK", + "STALE", + "AUTHORIZED", + "RELEASED", + "EXECUTING", + "COMPLETED", + "ROLLED_BACK", + "REJECTED" + ] + }, + "created_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + }, + "type": { + "type": "string", + "const": "BID" + }, + "payload": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "BID" + }, + "market_date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "prices_yuan_per_mwh": { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + "quantities_mwh": { + "type": "object", + "properties": { + "interval_minutes": { + "type": "number", + "const": 15 + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "values": { + "minItems": 96, + "maxItems": 96, + "type": "array", + "items": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + } + }, + "required": [ + "interval_minutes", + "date", + "values" + ], + "additionalProperties": false + }, + "expected_revenue_yuan": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + } + }, + "required": [ + "kind", + "market_date", + "prices_yuan_per_mwh", + "quantities_mwh", + "expected_revenue_yuan" + ], + "additionalProperties": false + } + }, + "required": [ + "id", + "timescale", + "originator", + "lineage", + "envelope_ref", + "digest", + "status", + "created_at", + "type", + "payload" + ], + "additionalProperties": false + } + ], + "$id": "https://vpp-ai-platform/contracts/proposal.json", + "title": "Proposal" +} diff --git a/contracts/schema/resource_profile.json b/contracts/schema/resource_profile.json new file mode 100644 index 0000000..bcc089e --- /dev/null +++ b/contracts/schema/resource_profile.json @@ -0,0 +1,102 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "resource_id": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [ + "STORAGE", + "PV", + "EV_CHARGER", + "HVAC", + "INDUSTRIAL_LOAD", + "MICROGRID", + "BASE_STATION" + ] + }, + "rated_power_mw": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + }, + "certified_adjustable_mw": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + }, + "confidence": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + }, + "reliability_score": { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + }, + "constraints": { + "type": "object", + "properties": { + "min_duration_min": { + "anyOf": [ + { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + }, + "recovery_rate_mw_per_min": { + "anyOf": [ + { + "type": "string", + "pattern": "^-?\\d+(\\.\\d+)?$" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "min_duration_min", + "recovery_rate_mw_per_min" + ], + "additionalProperties": false + }, + "evidence_refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + }, + "updated_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + } + }, + "required": [ + "resource_id", + "name", + "type", + "rated_power_mw", + "certified_adjustable_mw", + "confidence", + "reliability_score", + "constraints", + "evidence_refs", + "updated_at" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/resource_profile.json", + "title": "ResourceProfile" +} diff --git a/contracts/schema/situation_report.json b/contracts/schema/situation_report.json new file mode 100644 index 0000000..ebb72df --- /dev/null +++ b/contracts/schema/situation_report.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "market_date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH", + "EXTREME" + ] + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "TREND", + "ANOMALY", + "RISK", + "ATTRIBUTION" + ] + }, + "summary": { + "type": "string", + "minLength": 1 + }, + "refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + }, + "required": [ + "kind", + "summary", + "refs" + ], + "additionalProperties": false + } + }, + "forecast_refs": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "generated_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$" + } + }, + "required": [ + "id", + "market_date", + "risk_level", + "findings", + "forecast_refs", + "generated_at" + ], + "additionalProperties": false, + "$id": "https://vpp-ai-platform/contracts/situation_report.json", + "title": "SituationReport" +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f09ddae --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1700 @@ +{ + "name": "vpp-ai-platform", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vpp-ai-platform", + "workspaces": [ + "packages/*" + ] + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", + "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vpp/domain": { + "resolved": "packages/domain", + "link": true + }, + "node_modules/@vpp/services": { + "resolved": "packages/services", + "link": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tsx": { + "version": "4.23.13", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz", + "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/zod": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", + "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "packages/domain": { + "name": "@vpp/domain", + "version": "0.1.0", + "dependencies": { + "zod": "^4.1.0" + }, + "devDependencies": { + "@types/node": "^26.4.1", + "tsx": "^4.19.0", + "typescript": "^5.6.0", + "vitest": "^3.0.0" + } + }, + "packages/services": { + "name": "@vpp/services", + "version": "0.1.0", + "dependencies": { + "@vpp/domain": "*", + "decimal.js-light": "^2.5.1" + }, + "devDependencies": { + "@types/node": "^26.4.1", + "typescript": "^5.6.0", + "vitest": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e052a90 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "vpp-ai-platform", + "private": true, + "type": "module", + "workspaces": [ + "packages/*" + ], + "scripts": { + "test": "npm run test --workspaces --if-present", + "export:schemas": "npm run export:schemas -w @vpp/domain", + "make:fixtures": "npm run make:fixtures -w @vpp/domain", + "check": "npm run export:schemas && npm test" + } +} diff --git a/packages/domain/package.json b/packages/domain/package.json new file mode 100644 index 0000000..a41634a --- /dev/null +++ b/packages/domain/package.json @@ -0,0 +1,23 @@ +{ + "name": "@vpp/domain", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "test": "vitest run", + "export:schemas": "tsx scripts/export-schemas.ts", + "make:fixtures": "tsx scripts/make-fixtures.ts" + }, + "dependencies": { + "zod": "^4.1.0" + }, + "devDependencies": { + "@types/node": "^26.4.1", + "tsx": "^4.19.0", + "typescript": "^5.6.0", + "vitest": "^3.0.0" + } +} diff --git a/packages/domain/scripts/export-schemas.ts b/packages/domain/scripts/export-schemas.ts new file mode 100644 index 0000000..f395752 --- /dev/null +++ b/packages/domain/scripts/export-schemas.ts @@ -0,0 +1,22 @@ +/** + * Exports every registry schema to contracts/schema/.json (JSON Schema, + * draft 2020-12). These committed files are the neutral cross-language contract + * (ADR-0002); the Python side generates pydantic models from them. + */ +import { mkdirSync, writeFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { z } from 'zod' +import { schemaRegistry } from '../src/index.js' + +const outDir = fileURLToPath(new URL('../../../contracts/schema/', import.meta.url)) +mkdirSync(outDir, { recursive: true }) + +const pascal = (s: string) => s.split('_').map((w) => w[0]!.toUpperCase() + w.slice(1)).join('') + +for (const [name, schema] of Object.entries(schemaRegistry)) { + const json = z.toJSONSchema(schema) as Record + json['$id'] = `https://vpp-ai-platform/contracts/${name}.json` + json['title'] = pascal(name) // becomes the pydantic class name + writeFileSync(`${outDir}${name}.json`, JSON.stringify(json, null, 2) + '\n') + console.log(`exported ${name}.json`) +} diff --git a/packages/domain/scripts/make-fixtures.ts b/packages/domain/scripts/make-fixtures.ts new file mode 100644 index 0000000..5025f0b --- /dev/null +++ b/packages/domain/scripts/make-fixtures.ts @@ -0,0 +1,244 @@ +/** + * Generates golden fixtures into contracts/fixtures//*.json and invalid + * fixtures into contracts/fixtures-invalid//*.json. Run once and commit; + * fixtures are reviewed via diff. Invalid cases are crafted to fail on BOTH the + * zod and pydantic sides (dual-side contract test, docs/11 §3.2). + */ +import { mkdirSync, writeFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { schemaRegistry } from '../src/index.js' + +const root = fileURLToPath(new URL('../../../contracts/', import.meta.url)) + +const REF_A = 'a'.repeat(64) +const REF_B = 'b'.repeat(64) +const REF_C = 'c'.repeat(64) +const DATE = '2026-03-15' +const T0 = '2026-03-14T06:00:00Z' +const T1 = '2026-03-14T08:30:00Z' +const T2 = '2026-03-15T23:59:59Z' + +const curve = (v: string, date = DATE) => ({ + interval_minutes: 15, + date, + values: Array.from({ length: 96 }, () => v), +}) + +const valid: Record> = { + proposal: { + 'bid-basic': { + id: 'prop-001', + type: 'BID', + timescale: 'DAY_AHEAD', + originator: { agent: 'trading-agent', trigger: 'SCHEDULED', task_id: 'task-001' }, + payload: { + kind: 'BID', + market_date: DATE, + prices_yuan_per_mwh: curve('425.50'), + quantities_mwh: curve('12.5'), + expected_revenue_yuan: '510600.00', + }, + lineage: { + tool_calls: [ + { + tool_call_id: 'tc-001', + tool: 'bid-optimization-milp', + version: '1.0.0', + inputs_ref: REF_A, + outputs_ref: REF_B, + }, + ], + data_refs: [REF_C], + ledger_version: 42, + policy_pack_version: '2026.03', + }, + envelope_ref: 'env-bid-001', + digest: REF_B, + status: 'DRAFT', + created_at: T1, + }, + }, + approval: { + approve: { + id: 'appr-001', + proposal_digest: REF_B, + decision: 'APPROVE', + approver: { id: 'user-trader-01', role: 'senior-trader' }, + scope: { effect_type: 'BID', limits: { max_energy_mwh: '1500.0' } }, + validity: { from: T1, to: T2 }, + evidence_versions: { + policy_pack_version: '2026.03', + ledger_version: 42, + data_snapshot_refs: [REF_C], + }, + comment: 'within monthly position band', + decided_at: T1, + }, + }, + execution_permit: { + active: { + id: 'permit-001', + proposal_digest: REF_B, + issued_at: T1, + expires_at: T2, + effect_limits: { max_energy_mwh: '1500.0' }, + revoked_at: null, + issuer: 'authority-service', + }, + }, + envelope: { + 'bid-envelope': { + id: 'env-bid-001', + scope: { proposal_type: 'BID', timescales: ['DAY_AHEAD'], resource_set: 'pool-hubei-01' }, + bounds: { price_deviation_pct: '5.0', max_energy_mwh: '1500.0' }, + validity: { from: T0, to: T2 }, + approval: { level: 'L2', approved_by: ['user-ops-lead', 'user-risk-officer'] }, + escalation: { max_consecutive_deviations: 3, deviation_threshold_pct: '10.0' }, + status: 'ACTIVE', + }, + }, + flexibility_envelope: { + 'da-aggregation-unit': { + id: 'flex-001', + provider: { unit_id: 'agg-unit-wuhan-01', level: 'AGGREGATION_UNIT' }, + market_date: DATE, + up_mw: curve('8.5'), + down_mw: curve('6.0'), + valid_until: T2, + schema_version: '1.0.0', + signature: null, + }, + }, + position_update: { + 'award-da': { + id: 'pos-001', + timescale: 'DAY_AHEAD', + period: DATE, + kind: 'AWARD', + energy_mwh: '1180.0', + curve: curve('12.3'), + source_ref: REF_B, + expected_version: 42, + }, + }, + ledger_view: { + basic: { + version: 43, + entries: [ + { + id: 'pos-000', + timescale: 'MONTHLY', + period: '2026-03', + kind: 'CONTRACT', + energy_mwh: '36000.0', + curve: null, + source_ref: 'contract-2026-03-001', + recorded_at: T0, + }, + ], + }, + }, + forecast_bundle: { + 'load-da': { + id: 'fc-001', + kind: 'LOAD', + market_date: DATE, + unit: 'mw', + quantiles: { p10: curve('40.1'), p50: curve('45.7'), p90: curve('52.3') }, + model: { name: 'load-forecast', version: '1.2.0' }, + features_snapshot_ref: REF_A, + generated_at: T0, + }, + }, + situation_report: { + 'normal-day': { + id: 'sit-001', + market_date: DATE, + risk_level: 'LOW', + findings: [ + { kind: 'TREND', summary: 'mild temperatures, load near seasonal norm', refs: [REF_A] }, + ], + forecast_refs: ['fc-001'], + generated_at: T0, + }, + }, + resource_profile: { + storage: { + resource_id: 'res-storage-01', + name: 'Wuhan industrial park storage #1', + type: 'STORAGE', + rated_power_mw: '10.0', + certified_adjustable_mw: '8.0', + confidence: '0.92', + reliability_score: '0.88', + constraints: { min_duration_min: 60, recovery_rate_mw_per_min: '0.5' }, + evidence_refs: [REF_C], + updated_at: T0, + }, + }, + decision_case: { + 'da-bid': { + id: 'case-001', + kind: 'DAY_AHEAD_BID', + objective: 'Complete day-ahead bid for 2026-03-15', + owner: 'user-trader-01', + deadline: '2026-03-14T10:00:00Z', + status: 'AWAITING_APPROVAL', + refs: { + evidence: ['sit-001', 'fc-001'], + proposals: ['prop-001'], + approvals: [], + permits: [], + scenarios: [], + outcome: null, + }, + opened_at: T0, + closed_at: null, + }, + }, + event_envelope: { + 'situation-published': { + event_id: 'evt-001', + event_type: 'SituationReportPublished', + schema_version: '1.0.0', + occurred_at: T0, + causation_id: 'task-001', + correlation_id: 'case-001', + payload: { situation_report_id: 'sit-001' }, + }, + }, +} + +// Each invalid case breaks exactly one rule and must fail on both sides. +const invalid: Record> = { + proposal: { + // money as JSON float — the forbidden representation (docs/11 §3.3) + 'bid-float-money': structuredClone(valid['proposal']!['bid-basic']!), + }, + approval: { + // digest not sha256 hex + 'bad-digest': { ...(valid['approval']!['approve'] as object), proposal_digest: 'not-a-digest' }, + }, + position_update: { + // missing optimistic-concurrency field + 'missing-version': (() => { + const o = structuredClone(valid['position_update']!['award-da']) as Record + delete o['expected_version'] + return o + })(), + }, +} +;(invalid['proposal']!['bid-float-money'] as any).payload.expected_revenue_yuan = 510600.0 + +const write = (dir: string, sets: Record>) => { + for (const [name, cases] of Object.entries(sets)) { + mkdirSync(`${root}${dir}/${name}`, { recursive: true }) + for (const [caseName, obj] of Object.entries(cases)) { + writeFileSync(`${root}${dir}/${name}/${caseName}.json`, JSON.stringify(obj, null, 2) + '\n') + } + } +} + +write('fixtures', valid) +write('fixtures-invalid', invalid) +console.log('fixtures written') diff --git a/packages/domain/src/approval.ts b/packages/domain/src/approval.ts new file mode 100644 index 0000000..6974fe3 --- /dev/null +++ b/packages/domain/src/approval.ts @@ -0,0 +1,38 @@ +import { z } from 'zod' +import { DecimalString, Id, IsoUtc, SnapshotRef } from './common.js' +import { ProposalType } from './proposal.js' + +export const ApprovalLevel = z.enum(['L1', 'L2', 'L3']) + +/** Binds to an exact proposal digest and validity window; stale when evidence changes (docs/03 §2.1). */ +export const Approval = z.object({ + id: Id, + proposal_digest: SnapshotRef, + decision: z.enum(['APPROVE', 'REJECT']), + approver: z.object({ id: Id, role: z.string().min(1) }), + scope: z.object({ + effect_type: ProposalType, + limits: z.record(z.string(), DecimalString), + }), + validity: z.object({ from: IsoUtc, to: IsoUtc }), + evidence_versions: z.object({ + policy_pack_version: z.string().min(1), + ledger_version: z.int().nonnegative(), + data_snapshot_refs: z.array(SnapshotRef), + }), + comment: z.string().optional(), + decided_at: IsoUtc, +}) +export type Approval = z.infer + +/** Short-lived, revocable. Gateways accept only (Proposal, Permit) pairs (I4, docs/03 §2.2). */ +export const ExecutionPermit = z.object({ + id: Id, + proposal_digest: SnapshotRef, + issued_at: IsoUtc, + expires_at: IsoUtc, + effect_limits: z.record(z.string(), DecimalString), + revoked_at: IsoUtc.nullable(), + issuer: z.literal('authority-service'), +}) +export type ExecutionPermit = z.infer diff --git a/packages/domain/src/case.ts b/packages/domain/src/case.ts new file mode 100644 index 0000000..1a06875 --- /dev/null +++ b/packages/domain/src/case.ts @@ -0,0 +1,28 @@ +import { z } from 'zod' +import { Id, IsoUtc } from './common.js' + +export const CaseKind = z.enum(['DAY_AHEAD_BID', 'DR_EVENT', 'DEVIATION_RESOLUTION', 'REVIEW', 'ADHOC_ANALYSIS']) + +/** + * Operator's unit of accountability (docs/02 §5, ADR-0008): one objective, + * one owner, one deadline, a completion contract. References, never copies. + */ +export const DecisionCase = z.object({ + id: Id, + kind: CaseKind, + objective: z.string().min(1), + owner: Id, + deadline: IsoUtc, + status: z.enum(['OPEN', 'IN_PROGRESS', 'AWAITING_APPROVAL', 'CLOSED_DONE', 'CLOSED_ABORTED']), + refs: z.object({ + evidence: z.array(Id), + proposals: z.array(Id), + approvals: z.array(Id), + permits: z.array(Id), + scenarios: z.array(Id), + outcome: Id.nullable(), + }), + opened_at: IsoUtc, + closed_at: IsoUtc.nullable(), +}) +export type DecisionCase = z.infer diff --git a/packages/domain/src/common.ts b/packages/domain/src/common.ts new file mode 100644 index 0000000..e31b4da --- /dev/null +++ b/packages/domain/src/common.ts @@ -0,0 +1,35 @@ +import { z } from 'zod' + +/** + * Fixed-point decimal encoded as a string. Money/energy/prices must never be + * JS numbers or JSON floats (docs/11 §3.3) — TS `number` and Python `float` + * disagree exactly where settlement reconciliation cares. + */ +export const DecimalString = z + .string() + .regex(/^-?\d+(\.\d+)?$/, 'fixed-point decimal encoded as string') +export type DecimalString = z.infer + +/** ISO8601 UTC timestamp. Market-interval semantics use {date, interval} instead. */ +export const IsoUtc = z.iso.datetime() + +/** Market date in Asia/Shanghai semantics (docs/11 §3.3). */ +export const MarketDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/) +export const MarketMonth = z.string().regex(/^\d{4}-\d{2}$/) + +export const Id = z.string().min(1) + +/** sha256 hex reference to an immutable content-addressed snapshot. */ +export const SnapshotRef = z.string().regex(/^[0-9a-f]{64}$/) +export type SnapshotRef = z.infer + +export const Timescale = z.enum(['ANNUAL', 'MONTHLY', 'DAY_AHEAD', 'INTRADAY', 'REALTIME']) +export type Timescale = z.infer + +/** 96 × 15-min values for one market date; index 0 = 00:00–00:15 Asia/Shanghai. */ +export const Curve96 = z.object({ + interval_minutes: z.literal(15), + date: MarketDate, + values: z.array(DecimalString).length(96), +}) +export type Curve96 = z.infer diff --git a/packages/domain/src/envelope.ts b/packages/domain/src/envelope.ts new file mode 100644 index 0000000..f8842c3 --- /dev/null +++ b/packages/domain/src/envelope.ts @@ -0,0 +1,48 @@ +import { z } from 'zod' +import { Curve96, DecimalString, Id, IsoUtc, MarketDate, Timescale } from './common.js' +import { ApprovalLevel } from './approval.js' +import { ProposalType } from './proposal.js' + +/** + * Authority envelope (授权包络, downward): human-pre-approved autonomy bounds. + * Bound keys are type-specific and documented in docs/03 §3; initial values are + * OPEN-QUESTION B1/B2 — never hardcode them. + */ +export const Envelope = z.object({ + id: Id, + scope: z.object({ + proposal_type: ProposalType, + timescales: z.array(Timescale).min(1), + resource_set: Id, + }), + bounds: z.record(z.string(), DecimalString), + validity: z.object({ from: IsoUtc, to: IsoUtc }), + approval: z.object({ level: ApprovalLevel, approved_by: z.array(Id).min(1) }), + escalation: z.object({ + max_consecutive_deviations: z.int().positive(), + deviation_threshold_pct: DecimalString, + }), + status: z.enum(['ACTIVE', 'SUSPENDED', 'EXPIRED']), +}) +export type Envelope = z.infer + +/** + * Capability envelope (灵活性包络, upward): adjustable capacity report from an + * aggregation unit — the same schema crosses the federation boundary in + * phase 2 (docs/10 §5, no-rework reservation #1). + */ +export const FlexibilityEnvelope = z.object({ + id: Id, + provider: z.object({ + unit_id: Id, + level: z.enum(['SITE', 'AGGREGATION_UNIT', 'PROVINCE']), + }), + market_date: MarketDate, + up_mw: Curve96, + down_mw: Curve96, + valid_until: IsoUtc, + schema_version: z.string().min(1), + /** Signing arrives with federation (phase 2); null within-province. */ + signature: z.string().nullable(), +}) +export type FlexibilityEnvelope = z.infer diff --git a/packages/domain/src/event.ts b/packages/domain/src/event.ts new file mode 100644 index 0000000..d3fb74e --- /dev/null +++ b/packages/domain/src/event.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { Id, IsoUtc } from './common.js' + +/** + * Typed event wrapper for the outbox/bus (docs/11 §1.3). causation/correlation + * ids let audit and review walk causal chains through the event log. + */ +export const EventEnvelope = z.object({ + event_id: Id, + event_type: z.string().min(1), + schema_version: z.string().regex(/^\d+\.\d+\.\d+$/), + occurred_at: IsoUtc, + causation_id: Id, + correlation_id: Id, + payload: z.unknown(), +}) +export type EventEnvelope = z.infer diff --git a/packages/domain/src/forecast.ts b/packages/domain/src/forecast.ts new file mode 100644 index 0000000..0cb07dc --- /dev/null +++ b/packages/domain/src/forecast.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' +import { Curve96, Id, IsoUtc, MarketDate, SnapshotRef } from './common.js' + +/** + * Forecast output contract (docs/05 §2.1): quantile intervals are mandatory — + * bid risk assessment depends on calibrated bands, not point values. + */ +export const ForecastBundle = z.object({ + id: Id, + kind: z.enum(['LOAD', 'PV', 'PRICE']), + market_date: MarketDate, + unit: z.enum(['mw', 'yuan_per_mwh']), + quantiles: z.object({ + p10: Curve96, + p50: Curve96, + p90: Curve96, + }), + model: z.object({ name: z.string().min(1), version: z.string().min(1) }), + features_snapshot_ref: SnapshotRef, + generated_at: IsoUtc, +}) +export type ForecastBundle = z.infer diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts new file mode 100644 index 0000000..0962ead --- /dev/null +++ b/packages/domain/src/index.ts @@ -0,0 +1,40 @@ +import { z } from 'zod' +import { Approval, ExecutionPermit } from './approval.js' +import { DecisionCase } from './case.js' +import { Envelope, FlexibilityEnvelope } from './envelope.js' +import { EventEnvelope } from './event.js' +import { ForecastBundle } from './forecast.js' +import { LedgerView, PositionUpdate } from './ledger.js' +import { Proposal } from './proposal.js' +import { ResourceProfile } from './resource.js' +import { SituationReport } from './situation.js' + +export * from './common.js' +export * from './proposal.js' +export * from './approval.js' +export * from './envelope.js' +export * from './ledger.js' +export * from './forecast.js' +export * from './situation.js' +export * from './resource.js' +export * from './case.js' +export * from './event.js' + +/** + * Registry driving the contracts pipeline: keys become schema/fixture/module + * names on both the TS and Python sides (underscores — Python module names). + */ +export const schemaRegistry: Record = { + proposal: Proposal, + approval: Approval, + execution_permit: ExecutionPermit, + envelope: Envelope, + flexibility_envelope: FlexibilityEnvelope, + position_update: PositionUpdate, + ledger_view: LedgerView, + forecast_bundle: ForecastBundle, + situation_report: SituationReport, + resource_profile: ResourceProfile, + decision_case: DecisionCase, + event_envelope: EventEnvelope, +} diff --git a/packages/domain/src/ledger.ts b/packages/domain/src/ledger.ts new file mode 100644 index 0000000..b806ae4 --- /dev/null +++ b/packages/domain/src/ledger.ts @@ -0,0 +1,32 @@ +import { z } from 'zod' +import { Curve96, DecimalString, Id, IsoUtc, Timescale } from './common.js' + +export const PositionKind = z.enum(['CONTRACT', 'BID_SUBMITTED', 'AWARD', 'EXTERNAL_COMMITMENT']) + +/** Period granularity follows timescale: YYYY | YYYY-MM | YYYY-MM-DD. */ +const Period = z.string().regex(/^\d{4}(-\d{2}(-\d{2})?)?$/) + +export const PositionUpdate = z.object({ + id: Id, + timescale: Timescale, + period: Period, + kind: PositionKind, + energy_mwh: DecimalString, + curve: Curve96.nullable(), + /** Proposal digest, settlement id, or contract id that caused this entry. */ + source_ref: z.string().min(1), + /** Optimistic concurrency: append fails unless this matches current version. */ + expected_version: z.int().nonnegative(), +}) +export type PositionUpdate = z.infer + +export const PositionEntry = PositionUpdate.omit({ expected_version: true }).extend({ + recorded_at: IsoUtc, +}) +export type PositionEntry = z.infer + +export const LedgerView = z.object({ + version: z.int().nonnegative(), + entries: z.array(PositionEntry), +}) +export type LedgerView = z.infer diff --git a/packages/domain/src/proposal.ts b/packages/domain/src/proposal.ts new file mode 100644 index 0000000..af42948 --- /dev/null +++ b/packages/domain/src/proposal.ts @@ -0,0 +1,75 @@ +import { z } from 'zod' +import { Curve96, DecimalString, Id, IsoUtc, MarketDate, SnapshotRef, Timescale } from './common.js' + +export const ProposalType = z.enum(['BID', 'INVITATION', 'CONTROL_PLAN', 'DISPATCH_PLAN']) +export type ProposalType = z.infer + +export const TriggerKind = z.enum(['MANUAL', 'EVENT', 'SCHEDULED']) + +/** Full lifecycle per docs/03 §2 (incl. FRESH_CHECK/STALE/AUTHORIZED from the staleness model). */ +export const ProposalStatus = z.enum([ + 'DRAFT', + 'RULE_CHECK', + 'SIMULATION', + 'ENVELOPE_CHECK', + 'PENDING_HUMAN', + 'AUTO_APPROVED', + 'APPROVED', + 'FRESH_CHECK', + 'STALE', + 'AUTHORIZED', + 'RELEASED', + 'EXECUTING', + 'COMPLETED', + 'ROLLED_BACK', + 'REJECTED', +]) + +export const ToolCallRef = z.object({ + tool_call_id: Id, + tool: Id, + version: z.string().min(1), + inputs_ref: SnapshotRef, + outputs_ref: SnapshotRef, +}) + +/** + * Provenance attached to every proposal (docs/03 §1). Rule check enforces that + * every numeric payload field traces to one of these tool calls (invariant/P2). + */ +export const Lineage = z.object({ + tool_calls: z.array(ToolCallRef), + data_refs: z.array(SnapshotRef), + ledger_version: z.int().nonnegative(), + policy_pack_version: z.string().min(1), +}) + +/** Day-ahead bid: per-interval price/quantity pairs. */ +export const BidPayload = z.object({ + kind: z.literal('BID'), + market_date: MarketDate, + prices_yuan_per_mwh: Curve96, + quantities_mwh: Curve96, + expected_revenue_yuan: DecimalString, +}) + +const ProposalBase = z.object({ + id: Id, + timescale: Timescale, + originator: z.object({ agent: Id, trigger: TriggerKind, task_id: Id }), + lineage: Lineage, + envelope_ref: Id.nullable(), + /** sha256 over canonical payload + lineage. Approvals and permits bind to this (I3/I4). */ + digest: SnapshotRef, + status: ProposalStatus, + created_at: IsoUtc, +}) + +/** + * M1 carries the BID variant only; INVITATION / CONTROL_PLAN / DISPATCH_PLAN + * payloads arrive with their milestones (ROADMAP M4 / phase 2). + */ +export const Proposal = z.discriminatedUnion('type', [ + ProposalBase.extend({ type: z.literal('BID'), payload: BidPayload }), +]) +export type Proposal = z.infer diff --git a/packages/domain/src/resource.ts b/packages/domain/src/resource.ts new file mode 100644 index 0000000..737cc51 --- /dev/null +++ b/packages/domain/src/resource.ts @@ -0,0 +1,36 @@ +import { z } from 'zod' +import { DecimalString, Id, IsoUtc, SnapshotRef } from './common.js' + +export const ResourceType = z.enum([ + 'STORAGE', + 'PV', + 'EV_CHARGER', + 'HVAC', + 'INDUSTRIAL_LOAD', + 'MICROGRID', + 'BASE_STATION', +]) + +/** + * Resource profile (docs/02 §2.2). reliability_score is written back by the + * review loop; certified capacity must reference recent execution evidence + * (profile staleness is top-3 risk, docs/13 §0). + */ +export const ResourceProfile = z.object({ + resource_id: Id, + name: z.string().min(1), + type: ResourceType, + rated_power_mw: DecimalString, + certified_adjustable_mw: DecimalString, + /** 0–1 confidence in the certified figure. */ + confidence: DecimalString, + /** 0–1, updated by ReviewFinding writebacks. */ + reliability_score: DecimalString, + constraints: z.object({ + min_duration_min: z.int().positive().nullable(), + recovery_rate_mw_per_min: DecimalString.nullable(), + }), + evidence_refs: z.array(SnapshotRef), + updated_at: IsoUtc, +}) +export type ResourceProfile = z.infer diff --git a/packages/domain/src/situation.ts b/packages/domain/src/situation.ts new file mode 100644 index 0000000..15a76d9 --- /dev/null +++ b/packages/domain/src/situation.ts @@ -0,0 +1,21 @@ +import { z } from 'zod' +import { Id, IsoUtc, MarketDate, SnapshotRef } from './common.js' + +export const RiskLevel = z.enum(['LOW', 'MEDIUM', 'HIGH', 'EXTREME']) + +/** Analysis agent output (docs/02 §2.1). EXTREME triggers the abnormal-day protocol (docs/13 §1). */ +export const SituationReport = z.object({ + id: Id, + market_date: MarketDate, + risk_level: RiskLevel, + findings: z.array( + z.object({ + kind: z.enum(['TREND', 'ANOMALY', 'RISK', 'ATTRIBUTION']), + summary: z.string().min(1), + refs: z.array(SnapshotRef), + }), + ), + forecast_refs: z.array(Id), + generated_at: IsoUtc, +}) +export type SituationReport = z.infer diff --git a/packages/domain/test/fixtures.test.ts b/packages/domain/test/fixtures.test.ts new file mode 100644 index 0000000..5a5bf7e --- /dev/null +++ b/packages/domain/test/fixtures.test.ts @@ -0,0 +1,41 @@ +import { readdirSync, readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' +import { schemaRegistry } from '../src/index.js' + +const root = fileURLToPath(new URL('../../../contracts/', import.meta.url)) + +const casesIn = (dir: string): Array<[string, string, string]> => { + const out: Array<[string, string, string]> = [] + for (const name of readdirSync(`${root}${dir}`)) { + for (const file of readdirSync(`${root}${dir}/${name}`)) { + out.push([name, file, `${root}${dir}/${name}/${file}`]) + } + } + return out +} + +describe('golden fixtures validate against domain schemas', () => { + it.each(casesIn('fixtures'))('%s / %s', (name, _file, path) => { + const schema = schemaRegistry[name] + expect(schema, `no schema registered for fixture dir '${name}'`).toBeDefined() + const data = JSON.parse(readFileSync(path, 'utf8')) + const result = schema!.safeParse(data) + expect(result.success, JSON.stringify(result.error?.issues, null, 2)).toBe(true) + }) +}) + +describe('invalid fixtures are rejected', () => { + it.each(casesIn('fixtures-invalid'))('%s / %s', (name, _file, path) => { + const schema = schemaRegistry[name] + expect(schema).toBeDefined() + const data = JSON.parse(readFileSync(path, 'utf8')) + expect(schema!.safeParse(data).success).toBe(false) + }) +}) + +describe('every registered schema has at least one golden fixture', () => { + it.each(Object.keys(schemaRegistry))('%s', (name) => { + expect(readdirSync(`${root}fixtures/${name}`).length).toBeGreaterThan(0) + }) +}) diff --git a/packages/domain/tsconfig.json b/packages/domain/tsconfig.json new file mode 100644 index 0000000..2332c75 --- /dev/null +++ b/packages/domain/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true + }, + "include": ["src", "scripts", "test"] +} diff --git a/packages/services/package.json b/packages/services/package.json new file mode 100644 index 0000000..b3699d4 --- /dev/null +++ b/packages/services/package.json @@ -0,0 +1,21 @@ +{ + "name": "@vpp/services", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "test": "vitest run" + }, + "dependencies": { + "@vpp/domain": "*", + "decimal.js-light": "^2.5.1" + }, + "devDependencies": { + "@types/node": "^26.4.1", + "typescript": "^5.6.0", + "vitest": "^3.0.0" + } +} diff --git a/packages/services/src/index.ts b/packages/services/src/index.ts new file mode 100644 index 0000000..ce1398d --- /dev/null +++ b/packages/services/src/index.ts @@ -0,0 +1,3 @@ +export * from './snapshot.js' +export * from './ledger.js' +export * from './quality.js' diff --git a/packages/services/src/ledger.ts b/packages/services/src/ledger.ts new file mode 100644 index 0000000..c476b1e --- /dev/null +++ b/packages/services/src/ledger.ts @@ -0,0 +1,102 @@ +import { Decimal } from 'decimal.js-light' +import type { LedgerView, PositionEntry, PositionUpdate, Timescale } from '@vpp/domain' +import { PositionUpdate as PositionUpdateSchema } from '@vpp/domain' + +export class LedgerConcurrencyError extends Error { + constructor(expected: number, actual: number) { + super(`ledger version mismatch: update expected ${expected}, current is ${actual}`) + this.name = 'LedgerConcurrencyError' + } +} + +export class CascadeViolation extends Error { + constructor(message: string) { + super(message) + this.name = 'CascadeViolation' + } +} + +export interface LedgerConfig { + /** + * Allowed relative deviation of a day-ahead bid's daily energy from the + * pro-rata daily share of the monthly contracted position. + * OPEN-QUESTION A5: the real Hubei decomposition-band rule may differ in + * shape (cumulative month-to-date band? asymmetric?) — see docs/open-questions.md. + */ + daMonthlyDeviationBand: string // e.g. "0.05" = ±5% + clock?: () => string +} + +/** + * Position ledger v1 (docs/00 §4, P7): in-memory, optimistic concurrency, + * per-timescale views, and the constraint cascade — upper-timescale positions + * bound lower-timescale writes. Storage adapter (Postgres) arrives with M3. + */ +export class LedgerService { + private version = 0 + private entries: PositionEntry[] = [] + + constructor(private readonly cfg: LedgerConfig) {} + + read(): LedgerView { + return { version: this.version, entries: [...this.entries] } + } + + viewByTimescale(timescale: Timescale): PositionEntry[] { + return this.entries.filter((e) => e.timescale === timescale) + } + + append(update: PositionUpdate): LedgerView { + PositionUpdateSchema.parse(update) + if (update.expected_version !== this.version) { + throw new LedgerConcurrencyError(update.expected_version, this.version) + } + this.checkCascade(update) + + const { expected_version: _ignored, ...rest } = update + const entry: PositionEntry = { + ...rest, + recorded_at: this.cfg.clock?.() ?? new Date().toISOString(), + } + this.entries.push(entry) + this.version += 1 + return this.read() + } + + /** + * Constraint cascade (P7): a DAY_AHEAD bid submission must stay within the + * band around the pro-rata daily share of the MONTHLY contracted position. + * No monthly position for the month → nothing to cascade from → reject + * (bidding without a position of record is a lineage failure, not a default-allow). + */ + private checkCascade(update: PositionUpdate): void { + if (update.kind !== 'BID_SUBMITTED' || update.timescale !== 'DAY_AHEAD') return + + const month = update.period.slice(0, 7) + const monthly = this.entries.filter( + (e) => e.timescale === 'MONTHLY' && e.kind === 'CONTRACT' && e.period === month, + ) + if (monthly.length === 0) { + throw new CascadeViolation( + `no MONTHLY CONTRACT position recorded for ${month}; day-ahead bid has no cascade anchor`, + ) + } + + const contracted = monthly.reduce((sum, e) => sum.add(new Decimal(e.energy_mwh)), new Decimal(0)) + const daysInMonth = new Date( + Date.UTC(Number(month.slice(0, 4)), Number(month.slice(5, 7)), 0), + ).getUTCDate() + const dailyShare = contracted.div(daysInMonth) + const band = new Decimal(this.cfg.daMonthlyDeviationBand) + const min = dailyShare.mul(new Decimal(1).sub(band)) + const max = dailyShare.mul(new Decimal(1).add(band)) + const bid = new Decimal(update.energy_mwh) + + if (bid.lt(min) || bid.gt(max)) { + throw new CascadeViolation( + `day-ahead bid ${bid.toString()} MWh outside monthly cascade band ` + + `[${min.toString()}, ${max.toString()}] (contracted ${contracted.toString()} MWh / ${daysInMonth} days ± ${band.mul(100).toString()}%)`, + ) + } + } +} diff --git a/packages/services/src/quality.ts b/packages/services/src/quality.ts new file mode 100644 index 0000000..0273785 --- /dev/null +++ b/packages/services/src/quality.ts @@ -0,0 +1,20 @@ +import type { Curve96 } from '@vpp/domain' + +export interface QualityVerdict { + ok: boolean + issues: string[] +} + +/** + * Data-quality gate stub (docs/05 §3.2). Full checks (missing-rate thresholds, + * jump detection, timestamp alignment, drift) arrive with the ingestion + * pipeline in M1 follow-up work; the interface is fixed now so callers can + * depend on it. Quality flags propagate through lineage (docs/13 §5). + */ +export function qualityGate(curve: Curve96): QualityVerdict { + const issues: string[] = [] + if (new Set(curve.values).size === 1 && curve.values[0] === '0') { + issues.push('flat-zero curve: likely telemetry outage, not real data') + } + return { ok: issues.length === 0, issues } +} diff --git a/packages/services/src/snapshot.ts b/packages/services/src/snapshot.ts new file mode 100644 index 0000000..f1b80a3 --- /dev/null +++ b/packages/services/src/snapshot.ts @@ -0,0 +1,85 @@ +import { createHash } from 'node:crypto' +import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs' +import { join } from 'node:path' + +/** + * Content-addressed immutable snapshot store (invariant I7: decisions must be + * replayable from evidence). The ref IS the sha256 of canonical content, so + * a stored snapshot can never change under its ref by construction. + */ +export interface SnapshotStore { + put(value: unknown): string + get(ref: string): unknown | undefined + has(ref: string): boolean +} + +/** Deterministic JSON: recursively sorted object keys, no whitespace variance. */ +export const canonicalJson = (value: unknown): string => + JSON.stringify(sortKeys(value)) + +const sortKeys = (v: unknown): unknown => { + if (Array.isArray(v)) return v.map(sortKeys) + if (v !== null && typeof v === 'object') { + return Object.fromEntries( + Object.entries(v as Record) + .sort(([a], [b]) => (a < b ? -1 : 1)) + .map(([k, val]) => [k, sortKeys(val)]), + ) + } + if (typeof v === 'number' && !Number.isInteger(v)) { + // Floats in snapshots would make canonicalization platform-dependent and + // violate the decimal-string rule upstream (docs/11 §3.3). + throw new Error('non-integer number in snapshot content — use decimal strings') + } + return v +} + +export const contentRef = (canonical: string): string => + createHash('sha256').update(canonical, 'utf8').digest('hex') + +export class MemorySnapshotStore implements SnapshotStore { + private readonly byRef = new Map() + + put(value: unknown): string { + const canonical = canonicalJson(value) + const ref = contentRef(canonical) + if (!this.byRef.has(ref)) this.byRef.set(ref, canonical) + return ref + } + + get(ref: string): unknown | undefined { + const canonical = this.byRef.get(ref) + return canonical === undefined ? undefined : JSON.parse(canonical) + } + + has(ref: string): boolean { + return this.byRef.has(ref) + } +} + +/** Filesystem adapter: one file per ref, write-once. */ +export class FsSnapshotStore implements SnapshotStore { + constructor(private readonly dir: string) { + mkdirSync(dir, { recursive: true }) + } + + private path(ref: string): string { + return join(this.dir, `${ref}.json`) + } + + put(value: unknown): string { + const canonical = canonicalJson(value) + const ref = contentRef(canonical) + if (!existsSync(this.path(ref))) writeFileSync(this.path(ref), canonical, { flag: 'wx' }) + return ref + } + + get(ref: string): unknown | undefined { + if (!this.has(ref)) return undefined + return JSON.parse(readFileSync(this.path(ref), 'utf8')) + } + + has(ref: string): boolean { + return existsSync(this.path(ref)) + } +} diff --git a/packages/services/test/ledger.test.ts b/packages/services/test/ledger.test.ts new file mode 100644 index 0000000..f057363 --- /dev/null +++ b/packages/services/test/ledger.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from 'vitest' +import type { PositionUpdate } from '@vpp/domain' +import { CascadeViolation, LedgerConcurrencyError, LedgerService } from '../src/ledger.js' + +const clock = () => '2026-03-14T08:00:00Z' + +const monthlyContract: PositionUpdate = { + id: 'pos-monthly', + timescale: 'MONTHLY', + period: '2026-03', + kind: 'CONTRACT', + energy_mwh: '37200.0', // 1200 MWh/day over 31 days + curve: null, + source_ref: 'contract-2026-03-001', + expected_version: 0, +} + +const daBid = (energy: string, expectedVersion: number): PositionUpdate => ({ + id: `pos-da-${energy}`, + timescale: 'DAY_AHEAD', + period: '2026-03-15', + kind: 'BID_SUBMITTED', + energy_mwh: energy, + curve: null, + source_ref: 'b'.repeat(64), + expected_version: expectedVersion, +}) + +const makeLedger = () => new LedgerService({ daMonthlyDeviationBand: '0.05', clock }) + +describe('constraint cascade (P7): monthly position bounds day-ahead bids', () => { + it('accepts a bid within the ±5% band of the pro-rata daily share', () => { + const ledger = makeLedger() + ledger.append(monthlyContract) + const view = ledger.append(daBid('1240.0', 1)) // daily share 1200, band [1140, 1260] + expect(view.version).toBe(2) + }) + + it('rejects a bid above the band', () => { + const ledger = makeLedger() + ledger.append(monthlyContract) + expect(() => ledger.append(daBid('1300.0', 1))).toThrow(CascadeViolation) + }) + + it('rejects a bid below the band', () => { + const ledger = makeLedger() + ledger.append(monthlyContract) + expect(() => ledger.append(daBid('1000.0', 1))).toThrow(CascadeViolation) + }) + + it('rejects a day-ahead bid with no monthly anchor (no default-allow)', () => { + const ledger = makeLedger() + expect(() => ledger.append(daBid('1200.0', 0))).toThrow(CascadeViolation) + }) +}) + +describe('optimistic concurrency', () => { + it('rejects an append against a stale version', () => { + const ledger = makeLedger() + ledger.append(monthlyContract) + expect(() => ledger.append({ ...monthlyContract, id: 'pos-2', expected_version: 0 })).toThrow( + LedgerConcurrencyError, + ) + }) +}) + +describe('views', () => { + it('filters by timescale and stamps recorded_at from the injected clock', () => { + const ledger = makeLedger() + ledger.append(monthlyContract) + ledger.append(daBid('1200.0', 1)) + const monthly = ledger.viewByTimescale('MONTHLY') + expect(monthly).toHaveLength(1) + expect(monthly[0]!.recorded_at).toBe(clock()) + expect(ledger.viewByTimescale('DAY_AHEAD')).toHaveLength(1) + }) +}) diff --git a/packages/services/test/snapshot.test.ts b/packages/services/test/snapshot.test.ts new file mode 100644 index 0000000..29c4953 --- /dev/null +++ b/packages/services/test/snapshot.test.ts @@ -0,0 +1,39 @@ +import { mkdtempSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' +import { FsSnapshotStore, MemorySnapshotStore, canonicalJson, contentRef } from '../src/snapshot.js' + +describe.each([ + ['memory', () => new MemorySnapshotStore()], + ['fs', () => new FsSnapshotStore(mkdtempSync(join(tmpdir(), 'vpp-snap-')))], +])('snapshot store (%s)', (_label, make) => { + it('is content-addressed: same content → same ref, regardless of key order', () => { + const store = make() + const ref1 = store.put({ b: '2', a: '1' }) + const ref2 = store.put({ a: '1', b: '2' }) + expect(ref1).toBe(ref2) + expect(ref1).toMatch(/^[0-9a-f]{64}$/) + }) + + it('round-trips content and reports presence', () => { + const store = make() + const value = { curve: ['1.0', '2.5'], meta: { unit: 'mw' } } + const ref = store.put(value) + expect(store.has(ref)).toBe(true) + expect(store.get(ref)).toEqual(value) + expect(store.get('f'.repeat(64))).toBeUndefined() + }) + + it('ref verifies against canonical content (checksummed by construction)', () => { + const store = make() + const value = { x: '42.0' } + const ref = store.put(value) + expect(contentRef(canonicalJson(store.get(ref)))).toBe(ref) + }) + + it('rejects float content (decimal-string rule, docs/11 §3.3)', () => { + const store = make() + expect(() => store.put({ price: 425.5 })).toThrow(/decimal strings/) + }) +}) diff --git a/packages/services/tsconfig.json b/packages/services/tsconfig.json new file mode 100644 index 0000000..35707f6 --- /dev/null +++ b/packages/services/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true + }, + "include": ["src", "test"] +} diff --git a/skills-py/requirements.txt b/skills-py/requirements.txt new file mode 100644 index 0000000..8a4aa06 --- /dev/null +++ b/skills-py/requirements.txt @@ -0,0 +1,3 @@ +pydantic>=2.7 +datamodel-code-generator>=0.26 +pytest>=8.0 diff --git a/skills-py/scripts/generate_models.sh b/skills-py/scripts/generate_models.sh new file mode 100755 index 0000000..a4f0421 --- /dev/null +++ b/skills-py/scripts/generate_models.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Regenerates skills-py/vpp_contracts from contracts/schema (ADR-0002). +# Generated files are committed but NEVER hand-edited. +set -euo pipefail +cd "$(dirname "$0")/.." + +datamodel-codegen \ + --input ../contracts/schema \ + --input-file-type jsonschema \ + --output vpp_contracts \ + --output-model-type pydantic_v2.BaseModel \ + --target-python-version 3.11 \ + --use-standard-collections \ + --disable-timestamp + +echo "models regenerated in skills-py/vpp_contracts" diff --git a/skills-py/tests/test_fixtures.py b/skills-py/tests/test_fixtures.py new file mode 100644 index 0000000..01bafa4 --- /dev/null +++ b/skills-py/tests/test_fixtures.py @@ -0,0 +1,37 @@ +"""Dual-side contract test (docs/11 §3.2): the same golden fixtures the TS side +validates must validate against the generated pydantic models, and the same +invalid fixtures must be rejected. Disagreement between sides fails CI.""" + +import importlib +from pathlib import Path + +import pytest +from pydantic import BaseModel, ValidationError + +ROOT = Path(__file__).resolve().parents[2] / "contracts" + + +def model_for(name: str) -> type[BaseModel]: + module = importlib.import_module(f"vpp_contracts.{name}") + class_name = "".join(part.capitalize() for part in name.split("_")) + return getattr(module, class_name) + + +def cases(dirname: str) -> list[tuple[str, Path]]: + out = [] + for schema_dir in sorted((ROOT / dirname).iterdir()): + for f in sorted(schema_dir.glob("*.json")): + out.append((schema_dir.name, f)) + return out + + +@pytest.mark.parametrize("name,path", cases("fixtures"), ids=lambda p: str(p)) +def test_golden_fixture_validates(name: str, path: Path) -> None: + # JSON-mode validation — exactly how a skill service receives these objects. + model_for(name).model_validate_json(path.read_text()) + + +@pytest.mark.parametrize("name,path", cases("fixtures-invalid"), ids=lambda p: str(p)) +def test_invalid_fixture_rejected(name: str, path: Path) -> None: + with pytest.raises(ValidationError): + model_for(name).model_validate_json(path.read_text()) diff --git a/skills-py/vpp_contracts/__init__.py b/skills-py/vpp_contracts/__init__.py new file mode 100644 index 0000000..62ca2ff --- /dev/null +++ b/skills-py/vpp_contracts/__init__.py @@ -0,0 +1,2 @@ +# generated by datamodel-codegen: +# filename: schema diff --git a/skills-py/vpp_contracts/approval.py b/skills-py/vpp_contracts/approval.py new file mode 100644 index 0000000..d25f6c1 --- /dev/null +++ b/skills-py/vpp_contracts/approval.py @@ -0,0 +1,68 @@ +# generated by datamodel-codegen: +# filename: approval.json + +from __future__ import annotations + +from enum import StrEnum + +from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, conint, constr + + +class Decision(StrEnum): + APPROVE = 'APPROVE' + REJECT = 'REJECT' + + +class Approver(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + role: constr(min_length=1) + + +class EffectType(StrEnum): + BID = 'BID' + INVITATION = 'INVITATION' + CONTROL_PLAN = 'CONTROL_PLAN' + DISPATCH_PLAN = 'DISPATCH_PLAN' + + +class Scope(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + effect_type: EffectType + limits: dict[str, constr(pattern=r'^-?\d+(\.\d+)?$')] + + +class Validity(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + from_: AwareDatetime = Field(..., alias='from') + to: AwareDatetime + + +class EvidenceVersions(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + policy_pack_version: constr(min_length=1) + ledger_version: conint(ge=0, le=9007199254740991) + data_snapshot_refs: list[constr(pattern=r'^[0-9a-f]{64}$')] + + +class Approval(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + proposal_digest: constr(pattern=r'^[0-9a-f]{64}$') + decision: Decision + approver: Approver + scope: Scope + validity: Validity + evidence_versions: EvidenceVersions + comment: str | None = None + decided_at: AwareDatetime diff --git a/skills-py/vpp_contracts/decision_case.py b/skills-py/vpp_contracts/decision_case.py new file mode 100644 index 0000000..960697e --- /dev/null +++ b/skills-py/vpp_contracts/decision_case.py @@ -0,0 +1,51 @@ +# generated by datamodel-codegen: +# filename: decision_case.json + +from __future__ import annotations + +from enum import StrEnum + +from pydantic import AwareDatetime, BaseModel, ConfigDict, constr + + +class Kind(StrEnum): + DAY_AHEAD_BID = 'DAY_AHEAD_BID' + DR_EVENT = 'DR_EVENT' + DEVIATION_RESOLUTION = 'DEVIATION_RESOLUTION' + REVIEW = 'REVIEW' + ADHOC_ANALYSIS = 'ADHOC_ANALYSIS' + + +class Status(StrEnum): + OPEN = 'OPEN' + IN_PROGRESS = 'IN_PROGRESS' + AWAITING_APPROVAL = 'AWAITING_APPROVAL' + CLOSED_DONE = 'CLOSED_DONE' + CLOSED_ABORTED = 'CLOSED_ABORTED' + + +class Refs(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + evidence: list[constr(min_length=1)] + proposals: list[constr(min_length=1)] + approvals: list[constr(min_length=1)] + permits: list[constr(min_length=1)] + scenarios: list[constr(min_length=1)] + outcome: constr(min_length=1) | None + + +class DecisionCase(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + kind: Kind + objective: constr(min_length=1) + owner: constr(min_length=1) + deadline: AwareDatetime + status: Status + refs: Refs + opened_at: AwareDatetime + closed_at: AwareDatetime | None diff --git a/skills-py/vpp_contracts/envelope.py b/skills-py/vpp_contracts/envelope.py new file mode 100644 index 0000000..f555e23 --- /dev/null +++ b/skills-py/vpp_contracts/envelope.py @@ -0,0 +1,93 @@ +# generated by datamodel-codegen: +# filename: envelope.json + +from __future__ import annotations + +from enum import StrEnum + +from pydantic import ( + AwareDatetime, + BaseModel, + ConfigDict, + Field, + RootModel, + conint, + constr, +) + + +class ProposalType(StrEnum): + BID = 'BID' + INVITATION = 'INVITATION' + CONTROL_PLAN = 'CONTROL_PLAN' + DISPATCH_PLAN = 'DISPATCH_PLAN' + + +class Timescale(StrEnum): + ANNUAL = 'ANNUAL' + MONTHLY = 'MONTHLY' + DAY_AHEAD = 'DAY_AHEAD' + INTRADAY = 'INTRADAY' + REALTIME = 'REALTIME' + + +class Scope(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + proposal_type: ProposalType + timescales: list[Timescale] = Field(..., min_length=1) + resource_set: constr(min_length=1) + + +class Validity(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + from_: AwareDatetime = Field(..., alias='from') + to: AwareDatetime + + +class Level(StrEnum): + L1 = 'L1' + L2 = 'L2' + L3 = 'L3' + + +class ApprovedByItem(RootModel[constr(min_length=1)]): + root: constr(min_length=1) + + +class Approval(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + level: Level + approved_by: list[ApprovedByItem] = Field(..., min_length=1) + + +class Escalation(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + max_consecutive_deviations: conint(le=9007199254740991, gt=0) + deviation_threshold_pct: constr(pattern=r'^-?\d+(\.\d+)?$') + + +class Status(StrEnum): + ACTIVE = 'ACTIVE' + SUSPENDED = 'SUSPENDED' + EXPIRED = 'EXPIRED' + + +class Envelope(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + scope: Scope + bounds: dict[str, constr(pattern=r'^-?\d+(\.\d+)?$')] + validity: Validity + approval: Approval + escalation: Escalation + status: Status diff --git a/skills-py/vpp_contracts/event_envelope.py b/skills-py/vpp_contracts/event_envelope.py new file mode 100644 index 0000000..6108344 --- /dev/null +++ b/skills-py/vpp_contracts/event_envelope.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: event_envelope.json + +from __future__ import annotations + +from typing import Any + +from pydantic import AwareDatetime, BaseModel, ConfigDict, constr + + +class EventEnvelope(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + event_id: constr(min_length=1) + event_type: constr(min_length=1) + schema_version: constr(pattern=r'^\d+\.\d+\.\d+$') + occurred_at: AwareDatetime + causation_id: constr(min_length=1) + correlation_id: constr(min_length=1) + payload: Any diff --git a/skills-py/vpp_contracts/execution_permit.py b/skills-py/vpp_contracts/execution_permit.py new file mode 100644 index 0000000..b510936 --- /dev/null +++ b/skills-py/vpp_contracts/execution_permit.py @@ -0,0 +1,21 @@ +# generated by datamodel-codegen: +# filename: execution_permit.json + +from __future__ import annotations + +from typing import Literal + +from pydantic import AwareDatetime, BaseModel, ConfigDict, constr + + +class ExecutionPermit(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + proposal_digest: constr(pattern=r'^[0-9a-f]{64}$') + issued_at: AwareDatetime + expires_at: AwareDatetime + effect_limits: dict[str, constr(pattern=r'^-?\d+(\.\d+)?$')] + revoked_at: AwareDatetime | None + issuer: Literal['authority-service'] diff --git a/skills-py/vpp_contracts/flexibility_envelope.py b/skills-py/vpp_contracts/flexibility_envelope.py new file mode 100644 index 0000000..d7b21ae --- /dev/null +++ b/skills-py/vpp_contracts/flexibility_envelope.py @@ -0,0 +1,59 @@ +# generated by datamodel-codegen: +# filename: flexibility_envelope.json + +from __future__ import annotations + +from enum import StrEnum +from typing import Literal + +from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel, constr + + +class Level(StrEnum): + SITE = 'SITE' + AGGREGATION_UNIT = 'AGGREGATION_UNIT' + PROVINCE = 'PROVINCE' + + +class Provider(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + unit_id: constr(min_length=1) + level: Level + + +class Value(RootModel[constr(pattern=r'^-?\d+(\.\d+)?$')]): + root: constr(pattern=r'^-?\d+(\.\d+)?$') + + +class UpMw(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class DownMw(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class FlexibilityEnvelope(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + provider: Provider + market_date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + up_mw: UpMw + down_mw: DownMw + valid_until: AwareDatetime + schema_version: constr(min_length=1) + signature: str | None diff --git a/skills-py/vpp_contracts/forecast_bundle.py b/skills-py/vpp_contracts/forecast_bundle.py new file mode 100644 index 0000000..4f95bd0 --- /dev/null +++ b/skills-py/vpp_contracts/forecast_bundle.py @@ -0,0 +1,82 @@ +# generated by datamodel-codegen: +# filename: forecast_bundle.json + +from __future__ import annotations + +from enum import StrEnum +from typing import Literal + +from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel, constr + + +class Kind(StrEnum): + LOAD = 'LOAD' + PV = 'PV' + PRICE = 'PRICE' + + +class Unit(StrEnum): + mw = 'mw' + yuan_per_mwh = 'yuan_per_mwh' + + +class Value(RootModel[constr(pattern=r'^-?\d+(\.\d+)?$')]): + root: constr(pattern=r'^-?\d+(\.\d+)?$') + + +class P10(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class P50(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class P90(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class Quantiles(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + p10: P10 + p50: P50 + p90: P90 + + +class Model(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: constr(min_length=1) + version: constr(min_length=1) + + +class ForecastBundle(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + kind: Kind + market_date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + unit: Unit + quantiles: Quantiles + model: Model + features_snapshot_ref: constr(pattern=r'^[0-9a-f]{64}$') + generated_at: AwareDatetime diff --git a/skills-py/vpp_contracts/ledger_view.py b/skills-py/vpp_contracts/ledger_view.py new file mode 100644 index 0000000..51e40f9 --- /dev/null +++ b/skills-py/vpp_contracts/ledger_view.py @@ -0,0 +1,67 @@ +# generated by datamodel-codegen: +# filename: ledger_view.json + +from __future__ import annotations + +from enum import StrEnum +from typing import Literal + +from pydantic import ( + AwareDatetime, + BaseModel, + ConfigDict, + Field, + RootModel, + conint, + constr, +) + + +class Timescale(StrEnum): + ANNUAL = 'ANNUAL' + MONTHLY = 'MONTHLY' + DAY_AHEAD = 'DAY_AHEAD' + INTRADAY = 'INTRADAY' + REALTIME = 'REALTIME' + + +class Kind(StrEnum): + CONTRACT = 'CONTRACT' + BID_SUBMITTED = 'BID_SUBMITTED' + AWARD = 'AWARD' + EXTERNAL_COMMITMENT = 'EXTERNAL_COMMITMENT' + + +class Value(RootModel[constr(pattern=r'^-?\d+(\.\d+)?$')]): + root: constr(pattern=r'^-?\d+(\.\d+)?$') + + +class Curve(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class Entry(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + timescale: Timescale + period: constr(pattern=r'^\d{4}(-\d{2}(-\d{2})?)?$') + kind: Kind + energy_mwh: constr(pattern=r'^-?\d+(\.\d+)?$') + curve: Curve | None + source_ref: constr(min_length=1) + recorded_at: AwareDatetime + + +class LedgerView(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + version: conint(ge=0, le=9007199254740991) + entries: list[Entry] diff --git a/skills-py/vpp_contracts/position_update.py b/skills-py/vpp_contracts/position_update.py new file mode 100644 index 0000000..3cfb292 --- /dev/null +++ b/skills-py/vpp_contracts/position_update.py @@ -0,0 +1,51 @@ +# generated by datamodel-codegen: +# filename: position_update.json + +from __future__ import annotations + +from enum import StrEnum +from typing import Literal + +from pydantic import BaseModel, ConfigDict, Field, RootModel, conint, constr + + +class Timescale(StrEnum): + ANNUAL = 'ANNUAL' + MONTHLY = 'MONTHLY' + DAY_AHEAD = 'DAY_AHEAD' + INTRADAY = 'INTRADAY' + REALTIME = 'REALTIME' + + +class Kind(StrEnum): + CONTRACT = 'CONTRACT' + BID_SUBMITTED = 'BID_SUBMITTED' + AWARD = 'AWARD' + EXTERNAL_COMMITMENT = 'EXTERNAL_COMMITMENT' + + +class Value(RootModel[constr(pattern=r'^-?\d+(\.\d+)?$')]): + root: constr(pattern=r'^-?\d+(\.\d+)?$') + + +class Curve(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class PositionUpdate(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + timescale: Timescale + period: constr(pattern=r'^\d{4}(-\d{2}(-\d{2})?)?$') + kind: Kind + energy_mwh: constr(pattern=r'^-?\d+(\.\d+)?$') + curve: Curve | None + source_ref: constr(min_length=1) + expected_version: conint(ge=0, le=9007199254740991) diff --git a/skills-py/vpp_contracts/proposal.py b/skills-py/vpp_contracts/proposal.py new file mode 100644 index 0000000..b7d104e --- /dev/null +++ b/skills-py/vpp_contracts/proposal.py @@ -0,0 +1,132 @@ +# generated by datamodel-codegen: +# filename: proposal.json + +from __future__ import annotations + +from enum import StrEnum +from typing import Literal + +from pydantic import ( + AwareDatetime, + BaseModel, + ConfigDict, + Field, + RootModel, + conint, + constr, +) + + +class Timescale(StrEnum): + ANNUAL = 'ANNUAL' + MONTHLY = 'MONTHLY' + DAY_AHEAD = 'DAY_AHEAD' + INTRADAY = 'INTRADAY' + REALTIME = 'REALTIME' + + +class Trigger(StrEnum): + MANUAL = 'MANUAL' + EVENT = 'EVENT' + SCHEDULED = 'SCHEDULED' + + +class Originator(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + agent: constr(min_length=1) + trigger: Trigger + task_id: constr(min_length=1) + + +class ToolCall(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + tool_call_id: constr(min_length=1) + tool: constr(min_length=1) + version: constr(min_length=1) + inputs_ref: constr(pattern=r'^[0-9a-f]{64}$') + outputs_ref: constr(pattern=r'^[0-9a-f]{64}$') + + +class Lineage(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + tool_calls: list[ToolCall] + data_refs: list[constr(pattern=r'^[0-9a-f]{64}$')] + ledger_version: conint(ge=0, le=9007199254740991) + policy_pack_version: constr(min_length=1) + + +class Status(StrEnum): + DRAFT = 'DRAFT' + RULE_CHECK = 'RULE_CHECK' + SIMULATION = 'SIMULATION' + ENVELOPE_CHECK = 'ENVELOPE_CHECK' + PENDING_HUMAN = 'PENDING_HUMAN' + AUTO_APPROVED = 'AUTO_APPROVED' + APPROVED = 'APPROVED' + FRESH_CHECK = 'FRESH_CHECK' + STALE = 'STALE' + AUTHORIZED = 'AUTHORIZED' + RELEASED = 'RELEASED' + EXECUTING = 'EXECUTING' + COMPLETED = 'COMPLETED' + ROLLED_BACK = 'ROLLED_BACK' + REJECTED = 'REJECTED' + + +class Value(RootModel[constr(pattern=r'^-?\d+(\.\d+)?$')]): + root: constr(pattern=r'^-?\d+(\.\d+)?$') + + +class PricesYuanPerMwh(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class QuantitiesMwh(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + interval_minutes: Literal[15] + date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + values: list[Value] = Field(..., max_length=96, min_length=96) + + +class Payload(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Literal['BID'] + market_date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + prices_yuan_per_mwh: PricesYuanPerMwh + quantities_mwh: QuantitiesMwh + expected_revenue_yuan: constr(pattern=r'^-?\d+(\.\d+)?$') + + +class Proposal1(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + timescale: Timescale + originator: Originator + lineage: Lineage + envelope_ref: constr(min_length=1) | None + digest: constr(pattern=r'^[0-9a-f]{64}$') + status: Status + created_at: AwareDatetime + type: Literal['BID'] + payload: Payload + + +class Proposal(RootModel[Proposal1]): + root: Proposal1 = Field(..., title='Proposal') diff --git a/skills-py/vpp_contracts/resource_profile.py b/skills-py/vpp_contracts/resource_profile.py new file mode 100644 index 0000000..5eeef8a --- /dev/null +++ b/skills-py/vpp_contracts/resource_profile.py @@ -0,0 +1,42 @@ +# generated by datamodel-codegen: +# filename: resource_profile.json + +from __future__ import annotations + +from enum import StrEnum + +from pydantic import AwareDatetime, BaseModel, ConfigDict, conint, constr + + +class Type(StrEnum): + STORAGE = 'STORAGE' + PV = 'PV' + EV_CHARGER = 'EV_CHARGER' + HVAC = 'HVAC' + INDUSTRIAL_LOAD = 'INDUSTRIAL_LOAD' + MICROGRID = 'MICROGRID' + BASE_STATION = 'BASE_STATION' + + +class Constraints(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + min_duration_min: conint(le=9007199254740991, gt=0) | None + recovery_rate_mw_per_min: constr(pattern=r'^-?\d+(\.\d+)?$') | None + + +class ResourceProfile(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + resource_id: constr(min_length=1) + name: constr(min_length=1) + type: Type + rated_power_mw: constr(pattern=r'^-?\d+(\.\d+)?$') + certified_adjustable_mw: constr(pattern=r'^-?\d+(\.\d+)?$') + confidence: constr(pattern=r'^-?\d+(\.\d+)?$') + reliability_score: constr(pattern=r'^-?\d+(\.\d+)?$') + constraints: Constraints + evidence_refs: list[constr(pattern=r'^[0-9a-f]{64}$')] + updated_at: AwareDatetime diff --git a/skills-py/vpp_contracts/situation_report.py b/skills-py/vpp_contracts/situation_report.py new file mode 100644 index 0000000..15b7522 --- /dev/null +++ b/skills-py/vpp_contracts/situation_report.py @@ -0,0 +1,43 @@ +# generated by datamodel-codegen: +# filename: situation_report.json + +from __future__ import annotations + +from enum import StrEnum + +from pydantic import AwareDatetime, BaseModel, ConfigDict, constr + + +class RiskLevel(StrEnum): + LOW = 'LOW' + MEDIUM = 'MEDIUM' + HIGH = 'HIGH' + EXTREME = 'EXTREME' + + +class Kind(StrEnum): + TREND = 'TREND' + ANOMALY = 'ANOMALY' + RISK = 'RISK' + ATTRIBUTION = 'ATTRIBUTION' + + +class Finding(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + kind: Kind + summary: constr(min_length=1) + refs: list[constr(pattern=r'^[0-9a-f]{64}$')] + + +class SituationReport(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: constr(min_length=1) + market_date: constr(pattern=r'^\d{4}-\d{2}-\d{2}$') + risk_level: RiskLevel + findings: list[Finding] + forecast_refs: list[constr(min_length=1)] + generated_at: AwareDatetime diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..f4c96aa --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "strict": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "declaration": true, + "verbatimModuleSyntax": true + } +}