run_project KREVDE verdict_input og kjorte capture_verdict ubetinget; CLI-en
defaultet det til {"approved", "reviewed by expert"} og hosting listet det som
PAAKREVD. Netto: hver flaggloes kjoering myntet en ekspertgodkjenning ingen ga,
den gikk inn i den delte storen, og run_portfolio bar den inn i neste prosjekts
hypotese-prompt som en prior expert verdict -- paa flaten som ble overlevert
14.08. Non-goal 3, brutt i en soem.
RunResult.verdict er naa Verdict | None, og None er hva stillhet produserer:
ingenting myntes, ingenting lagres, ingenting varsles. Prinsippet sto allerede i
repoet -- RunFailure sin docstring: aa fylle et felt med en dummy legger
FABRIKKERT proveniens inn i aggregatet.
Traceability koster ingenting: RunResult.verdict_key (property, derivert fra
kandidaten) er verdicts.verdict_key sitt alt dokumenterte formaal -- identisk
med verdict.id naar en dom BLE gitt, og fortsatt meningsfull naar ingen ble det.
Det er den outboxen og den hostede responsen stempler.
Halv dom NEKTES paa begge doerer (FeedbackContract er eneste sted formen
valideres; CLI-en nekter ved navn FOER enhver mode-dispatch). Validering, aldri
reparasjon. De to mode-partisjonene fikk --decision/--rationale inn: kommentarene
sa ordrett at en aerlig nekt var uimplementerbar fordi de non-None
argparse-defaultene gjorde en eksplisitt verdi uskillbar fra defaulten -- med
defaultene borte er den implementerbar.
Hosting er WIDENING, ikke bryting: verdict_input flyttet fra _REQUIRED_FIELDS
til _OPTIONAL_FIELDS. Ingen ekstern kaller brekker.
AERLIGHETS-GRENSE: referanse-fixturens SYNTETISKE verdict_input-rader staar
uroert -- de er merket SYNTETISK paa fire steder og er reviewens F5 (maaling av
misjonspaastanden), ikke F2. Project.verdict_input er naa valgfri.
Load-bearing MAALT (tests/test_ungiven_verdict_loadbearing.py, 15 armer), aatte
mutasjoner alle roede mot HELE suiten + gronn kontroll 1080/5 og golden
demo-transcript.stdout BYTE-UENDRET (ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
En mutasjon falsifiserte testen foerst (vakuoes-gate-klassen, ellevte gang):
--report-armen brukte et bart --report, som nekter rc 1 uansett fordi --ledger
mangler.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
4.3 KiB
Python
103 lines
4.3 KiB
Python
"""Synthetic reference domain (D4): a small, fictional set of "anleggskostnad"
|
|
(construction-cost) projects with dummy data.
|
|
|
|
This is the framework's bundled example input — a portfolio of *independent*
|
|
projects the optimiser runs against. The framework finds cost-savings INSIDE
|
|
each project (Enhet B), so every project carries cost line items where a savings
|
|
measure could later be proposed and then deterministically validated.
|
|
|
|
It is a synthetic FIXTURE — not real data, and not the validated IR. The
|
|
deliberate data-source *contract* (JSON-Schema-validated config, B5) is a Fase 2
|
|
concern; here we keep a plain, typed loader over a bundled JSON file.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from importlib.resources import files
|
|
|
|
_DATA_RESOURCE = "data/reference_projects.json"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CostItem:
|
|
"""One cost line in a project's estimate."""
|
|
|
|
code: str
|
|
description: str
|
|
quantity: float
|
|
unit: str
|
|
unit_cost: float # NOK per unit
|
|
|
|
@property
|
|
def total_cost(self) -> float:
|
|
return self.quantity * self.unit_cost
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Project:
|
|
"""One independent construction-cost project (Enhet B operates inside this)."""
|
|
|
|
id: str
|
|
name: str
|
|
description: str
|
|
currency: str
|
|
cost_items: tuple[CostItem, ...]
|
|
docs_dir: str # absolute path to this project's bundled cost-docs folder (config-driven)
|
|
#: The Layer-2 expert decision/rationale, when an expert actually gave one. OPTIONAL and
|
|
#: ``None`` by default (F2, non-goal 3): a project nobody has reviewed has no honest value
|
|
#: here, and the previous required field made silence indistinguishable from an approval.
|
|
#: The shipped reference fixture DOES carry values, and labels them SYNTHETIC at every point —
|
|
#: that dataset's honesty is F5's subject (measuring the mission claim), not F2's.
|
|
verdict_input: dict[str, str] | None = None
|
|
bundle_dir: str | None = None # abs path to an OKF bundle backing this project (Fase 2a S2.0)
|
|
verdict_dir: str | None = None # abs path to this project's async verdict inbox (Fase 2a S2.0)
|
|
|
|
@property
|
|
def total_cost(self) -> float:
|
|
return sum((item.total_cost for item in self.cost_items), 0.0)
|
|
|
|
|
|
def load_reference_projects() -> tuple[Project, ...]:
|
|
"""Load the bundled synthetic reference projects (D4).
|
|
|
|
Each project's ``docs_dir`` is stored in the JSON relative to the package ``data/`` root
|
|
and resolved here to an absolute filesystem path; ``verdict_input`` carries the SYNTHETIC
|
|
Layer-2 expert decision/rationale when the row declares one, and is ``None`` when it does not
|
|
(F2). Missing keys raise ``KeyError`` (fail-fast, matching the existing loader contract) —
|
|
``verdict_input`` is deliberately NOT among them."""
|
|
resource = files("portfolio_optimiser").joinpath(_DATA_RESOURCE)
|
|
raw = json.loads(resource.read_text(encoding="utf-8"))
|
|
|
|
def _resolve(rel: str | None) -> str | None:
|
|
"""Optional config paths (bundle_dir/verdict_dir) resolve to absolute like docs_dir when set,
|
|
else stay None. Read via ``p.get(...)`` — never fail-fast: the shipped JSON omits both, so
|
|
every existing row keeps loading (backward-compatible; contrast the required-key reads)."""
|
|
return str(files("portfolio_optimiser").joinpath(f"data/{rel}")) if rel else None
|
|
|
|
return tuple(
|
|
Project(
|
|
id=p["id"],
|
|
name=p["name"],
|
|
description=p["description"],
|
|
currency=p["currency"],
|
|
cost_items=tuple(
|
|
CostItem(
|
|
code=c["code"],
|
|
description=c["description"],
|
|
quantity=c["quantity"],
|
|
unit=c["unit"],
|
|
unit_cost=c["unit_cost"],
|
|
)
|
|
for c in p["cost_items"]
|
|
),
|
|
docs_dir=str(files("portfolio_optimiser").joinpath(f"data/{p['docs_dir']}")),
|
|
# ``.get`` — never fail-fast: a reference row without a reviewed verdict is now legal
|
|
# (F2), and a missing key must mean "nobody reviewed it", not a load error.
|
|
verdict_input=p.get("verdict_input"),
|
|
bundle_dir=_resolve(p.get("bundle_dir")),
|
|
verdict_dir=_resolve(p.get("verdict_dir")),
|
|
)
|
|
for p in raw["projects"]
|
|
)
|