feat(fase2): wire Step-1 ExpeL retrieval into the hypothesis prompt
Closes maalbilde §5 gap #1 (the one missing "feedback-into-prompt" dataflow) for the OKF-bundle path. Before, ExpeL was computed AFTER generation into a discarded SessionContext, so a prior verdict could not influence any hypothesis (context_providers=0). - New okf.py: framework-neutral OKF bundle navigation (index + frontmatter + cross-links), pure stdlib, no agent_framework/mcp (D7-portable), enforced by test_okf_is_maf_free. - verdicts.py: seed_store_from_bundle + bundle_candidate_features build the ExpeL substrate + the pre-hypothesis query key from a bundle. - run_project(bundle_dir=...): folds the candidate's prior verdicts into the generation context BEFORE generate_via_llm; the road path is unchanged. Load-bearing (maalbilde §7): test_step1_expel_loadbearing proves a prior verdict reaches the hypothesis prompt and goes RED when the fold is detached (shown via TDD red->green). The marker is the minted verdict id (content hash) because docs_dir==bundle_dir lets keyword chunk-stuffing leak the realization rate; clean layer separation is Fase 2b. Suite 121->133 passed; mypy + ruff check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MHR8iKxJRxDiDfNw8HZmWE
This commit is contained in:
parent
2b1a1832ef
commit
d6d83d42b5
9 changed files with 529 additions and 7 deletions
|
|
@ -21,9 +21,12 @@ from __future__ import annotations
|
|||
import hashlib
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import ContextProvider, SessionContext
|
||||
|
||||
from portfolio_optimiser import okf
|
||||
|
||||
# Weights: the affected cost-code overlap dominates, then measure type, then magnitude.
|
||||
_W_CODES, _W_MEASURE, _W_MAGNITUDE = 0.60, 0.25, 0.15
|
||||
_MAGNITUDE_BUCKETS = [(0.0, 1e5), (1e5, 5e5), (5e5, 1e6), (1e6, float("inf"))]
|
||||
|
|
@ -196,3 +199,58 @@ def seed_store() -> VerdictStore:
|
|||
for vid, codes, mtype, saving, decision, desc in rows
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# --- OKF-bundle seeding (Fase 2a): turn a project's bundle into the ExpeL substrate ---
|
||||
|
||||
|
||||
def _features_from_ir(ir: dict[str, Any]) -> ProposalFeatures:
|
||||
"""Map a bundle's IR projection (``validator-input.json``) to the structural features the
|
||||
store ranks on: the affected cost-code set, the measure string, and the claimed magnitude."""
|
||||
return ProposalFeatures(
|
||||
affected_codes=frozenset(item["code"] for item in ir["affected_items"]),
|
||||
measure_type=ir["measure"],
|
||||
claimed_saving_nok=ir["claimed_saving_nok"],
|
||||
description=ir.get("measure", ""),
|
||||
)
|
||||
|
||||
|
||||
def bundle_candidate_features(bundle_dir: str) -> ProposalFeatures:
|
||||
"""The pre-hypothesis ExpeL query key: the candidate measure's structural features, read from
|
||||
the OKF bundle's IR projection. Available BEFORE any proposal is generated — which is what lets
|
||||
Step-1 retrieve prior verdicts and fold them into the hypothesis prompt (målbilde §2 step 1)."""
|
||||
return _features_from_ir(okf.load_ir_projection(bundle_dir))
|
||||
|
||||
|
||||
def _verdict_rationale(fm: dict[str, str]) -> str:
|
||||
"""Build the few-shot rationale from a ``type: verdict`` file's frontmatter, carrying the
|
||||
learning signal the deterministic validator cannot compute (the realization rate + expected
|
||||
actual). This is the ExpeL signal that must reach the next hypothesis."""
|
||||
base = fm.get("description", "")
|
||||
signal = [
|
||||
f"{label}={fm[key]}"
|
||||
for key, label in (
|
||||
("realization_rate", "realiseringsgrad"),
|
||||
("expected_actual_saving_nok", "forventet_faktisk_NOK"),
|
||||
)
|
||||
if fm.get(key)
|
||||
]
|
||||
return f"{base} [{'; '.join(signal)}]" if signal else base
|
||||
|
||||
|
||||
def seed_store_from_bundle(bundle_dir: str) -> VerdictStore:
|
||||
"""Build a ``VerdictStore`` from an OKF bundle's ``type: verdict`` files. Each verdict is keyed
|
||||
on the bundle's candidate features (so it retrieves for that measure) and carries the
|
||||
realization signal in its rationale. The seed verdict stands in for the durable HITL verdict a
|
||||
real expert would supply via the same folder interface (målbilde §3)."""
|
||||
features = bundle_candidate_features(bundle_dir)
|
||||
bundle = okf.navigate_bundle(bundle_dir)
|
||||
verdicts = [
|
||||
capture_verdict(
|
||||
features,
|
||||
vf.frontmatter.get("decision", "approved"),
|
||||
_verdict_rationale(vf.frontmatter),
|
||||
)
|
||||
for vf in bundle.verdicts
|
||||
]
|
||||
return VerdictStore(verdicts=verdicts)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue