vpp-ai-platform/skills-py/vpp_skills/potential.py

74 lines
3.0 KiB
Python
Raw Permalink Normal View History

M4: resource agent, envelopes live, review loop, insight cards - packages/domain: AwardNotice, DISPATCH_PLAN proposal payload, ExecutionReport, MeteringRecord, potential-assessment and dispatch-optimization contracts, ReviewFinding (+ typed writebacks), SemanticMemoryEntry, EnvelopeChangeRequest, InsightCard — exported with fixtures on both sides. - skills-py: potential-assessment (certified × rolling fulfilment, evidence days) and dispatch-optimization (per-interval LP on HiGHS, shortfall reported) skills + routes + tests. - packages/services: dispatch rules in the policy pack (over-allocation, award anchor, lineage integrity for allocations); PowerBalanceSimulator; SimulationGateway (permit-only, idempotent, seeded execute → ExecutionReports); envelope deviation-streak suspension + apply(); ReviewService (attribution, reliability EWMA writeback, semantic memory, envelope recommendations as change requests); dispatch assembler; skill client methods. - packages/runtime: resource agent; award-decomposition, review and envelope-review workflows; lifecycle selects simulator/gateway by proposal type; trigger hooks for awards, execution reports, metering; decide() resumes either lifecycle or envelope-review runs; insight cards API. - Tests: docs/07 D-1 16:00 and D+1 end to end; reliability score 0.9 → 0.880 and the next assessment de-rates capacity; envelope suspension on a seeded 3-day streak; WIDEN request applied only by a human. 184 TS + 80 Python. - docs/open-questions: B10 (reliability/potential parameters). README and CLAUDE.md status → M4 done, M5 next. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UoYoGYzHkFyv3ALenkRPhA
2026-09-02 19:31:12 -04:00
"""Adjustable-potential assessment skill v1 (docs/05 §2.2 可调潜力辨识).
For each resource: adjustable capacity = certified capacity × rolling
fulfillment rate (delivered / planned over the evidence window), with
confidence from the profile's own confidence, the reliability score and the
amount of evidence. A resource with no execution history keeps its certified
figure at the profile's confidence — the "画像时效性" risk (docs/13 §0) is
made visible through `evidence_days` rather than hidden.
The de-rating exponent and evidence saturation are placeholders: OPEN-QUESTION
B10 (可靠性/潜力辨识口径). The target accuracy (≥ 90%) is judged by the eval
harness on real execution feedback (docs/12 L2).
"""
from __future__ import annotations
from decimal import Decimal
import numpy as np
from vpp_contracts.potential_assessment_request import PotentialAssessmentRequest
from vpp_contracts.potential_assessment_result import PotentialAssessmentResult
from . import SKILL_VERSIONS
from .numeric import INTERVALS, SCALE_MW, curve, quantize
SKILL = "potential-assessment"
EVIDENCE_WINDOW_DAYS = 14 # OPEN-QUESTION B10
EVIDENCE_SATURATION_DAYS = 7 # OPEN-QUESTION B10: confidence saturates after this many days
def assess_potential(req: PotentialAssessmentRequest) -> PotentialAssessmentResult:
assessments = []
total = np.zeros(INTERVALS)
for r in req.resources:
profile = r.profile
certified = float(Decimal(profile.certified_adjustable_mw))
recent = sorted(r.fulfillment, key=lambda f: f.market_date)[-EVIDENCE_WINDOW_DAYS:]
planned = sum(Decimal(f.planned_mwh) for f in recent)
delivered = sum(Decimal(f.delivered_mwh) for f in recent)
if planned > 0:
rate = min(Decimal(1), delivered / planned)
evidence_days = len(recent)
else:
rate = None
evidence_days = 0
derate = float(rate) if rate is not None else 1.0
adjustable = np.full(INTERVALS, certified * derate)
evidence_factor = min(1.0, evidence_days / EVIDENCE_SATURATION_DAYS)
base_conf = float(Decimal(profile.confidence))
reliability = float(Decimal(profile.reliability_score))
confidence = base_conf * (0.5 + 0.5 * evidence_factor) * (0.5 + 0.5 * reliability)
total += adjustable
assessments.append(
{
"resource_id": profile.resource_id,
"adjustable_mw": curve(adjustable, req.market_date, SCALE_MW),
"confidence": quantize(confidence, 3),
"fulfillment_rate": quantize(rate, 4) if rate is not None else None,
"evidence_days": evidence_days,
}
)
return PotentialAssessmentResult.model_validate(
{
"market_date": req.market_date,
"assessments": assessments,
"total_adjustable_mw": curve(total, req.market_date, SCALE_MW),
"skill_version": SKILL_VERSIONS[SKILL],
}
)