import type { Approval, Curve96, Envelope, ForecastBundle, Proposal } from '@vpp/domain' import { AuthorityService } from '../src/authority.js' import { MemoryEventBus } from '../src/events.js' import { LedgerService } from '../src/ledger.js' import { LineageRecorder, assembleBidProposal, withLineage } from '../src/lineage.js' import { HUBEI_BID_PACK_PLACEHOLDER, PolicyEngine, hubeiSpotBiddingPack } from '../src/policy.js' import { MemorySnapshotStore } from '../src/snapshot.js' export const NOW = '2026-03-14T08:00:00Z' export const DATE = '2026-03-15' export const PACK_VERSION = '2026.03' export const curve = (v: string | string[], date = DATE): Curve96 => ({ interval_minutes: 15, date, values: Array.isArray(v) ? v : Array.from({ length: 96 }, () => v), }) export const priceForecast = (p50 = '425.50'): ForecastBundle => ({ id: 'fc-price', kind: 'PRICE', market_date: DATE, unit: 'yuan_per_mwh', quantiles: { p10: curve('360.00'), p50: curve(p50), p90: curve('490.00') }, model: { name: 'price-forecast', version: '1.0.0' }, features_snapshot_ref: 'a'.repeat(64), generated_at: NOW, }) /** A consistent little world: ledger with a monthly contract, a recorded MILP output, a proposal built from refs. */ export function world(opts: { qty?: string; price?: string; agent?: string } = {}) { const snapshots = new MemorySnapshotStore() const ledger = new LedgerService({ daMonthlyDeviationBand: '0.05', clock: () => NOW }) ledger.append({ id: 'contract-2026-03', timescale: 'MONTHLY', period: '2026-03', kind: 'CONTRACT', energy_mwh: '37200.0', curve: null, source_ref: 'contract-2026-03-001', expected_version: 0, }) // 1200 MWh/day → band [1140, 1260] const engine = new PolicyEngine([hubeiSpotBiddingPack(PACK_VERSION, HUBEI_BID_PACK_PLACEHOLDER)]) const lineage = new LineageRecorder() const events = new MemoryEventBus(() => NOW) const authority = new AuthorityService(undefined, () => 'permit-001') let n = 0 const milp = withLineage(lineage, snapshots, { tool: 'bid-optimization-milp', version: '1.0.0', idGen: () => `tc-${++n}` }, (_input: unknown) => ({ market_date: DATE, prices_yuan_per_mwh: curve(opts.price ?? '0.00'), quantities_mwh: curve(opts.qty ?? '12.5'), // 12.5 × 96 = 1200 MWh expected_revenue_yuan: '510600.00', })) const forecastTool = withLineage(lineage, snapshots, { tool: 'price-forecast', version: '1.0.0', idGen: () => `tc-${++n}` }, (_i: unknown) => priceForecast()) const proposal = lineage.run(() => { forecastTool({ kind: 'PRICE' }) const { call } = milp({ market_date: DATE }) return assembleBidProposal( { market_date: DATE, prices_ref: { tool_call_id: call.tool_call_id, path: 'prices_yuan_per_mwh' }, quantities_ref: { tool_call_id: call.tool_call_id, path: 'quantities_mwh' }, expected_revenue_ref: { tool_call_id: call.tool_call_id, path: 'expected_revenue_yuan' }, rationale: 'test', }, { id: 'prop-001', originator: { agent: opts.agent ?? 'trading-agent', trigger: 'SCHEDULED', task_id: 'task-001' }, lineage: lineage.current(), ledger_version: ledger.read().version, policy_pack_version: PACK_VERSION, snapshots, now: NOW, }, ) }) const ruleCtx = () => ({ ledger: ledger.read(), ledgerService: ledger, snapshots, now: NOW }) return { snapshots, ledger, engine, lineage, events, authority, proposal, ruleCtx } } export const envelope = (bounds: Record, over: Partial = {}): Envelope => ({ id: 'env-bid-001', scope: { proposal_type: 'BID', timescales: ['DAY_AHEAD'], resource_set: 'pool-hubei-01' }, bounds, validity: { from: '2026-03-01T00:00:00Z', to: '2026-03-31T23:59:59Z' }, approval: { level: 'L1', approved_by: ['user-ops-lead'] }, escalation: { max_consecutive_deviations: 3, deviation_threshold_pct: '10.0' }, status: 'ACTIVE', ...over, }) export const approvalFor = (p: Proposal, ledgerVersion: number, over: Partial = {}): Approval => ({ id: 'appr-001', proposal_digest: p.digest, decision: 'APPROVE', approver: { id: 'user-trader-01', role: 'senior-trader' }, scope: { effect_type: 'BID', limits: {} }, validity: { from: NOW, to: '2026-03-15T09:00:00Z' }, evidence_versions: { policy_pack_version: PACK_VERSION, ledger_version: ledgerVersion, data_snapshot_refs: [] }, decided_at: NOW, ...over, })