Stress round 6 validated three falsification arms, and every validated approach rested only on run-level declarations nobody can attribute to one approach. declare_requirement now takes a required approach_id (a mandate id or own-proposal; an unknown id is refused naming the valid ones), and a ValidatedProposal whose approach has neither a mandate requirement nor a declaration under its own id becomes validator.Unsupported - a Rejection subclass carrying the validator's own ruling, reported as `unsupported` in coverage, the outcome artefact, the settlement and the judge, and never counted or summed. The rule is active whenever the debate held the declaration tool, the micro base included; the road and pre-pass paths are untouched. Declaration quality is not judged, so the rule can be satisfied by declaring any document the run read. The v1 gate's row 6 probes pass; its artefact half reads IKKE MÅLT because stress round 6 predates approach-addressed declarations, and IKKE MÅLT is never green - it fails the exit code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
492 lines
25 KiB
Python
492 lines
25 KiB
Python
"""Typed IR for a run MANDATE — what a domain expert commissions a run to evaluate, and what
|
|
that run is meant to achieve (Trekk A1).
|
|
|
|
Pure module — imports **only** ``pydantic`` + stdlib. No ``agent_framework``, and deliberately
|
|
no ``contracts`` (which pulls MAF in transitively via ``backends``), so this module stays
|
|
D7-portable and is guarded by ``tests/test_okf.py::test_okf_is_maf_free`` alongside ``okf.py``
|
|
and ``dimension.py``.
|
|
|
|
**A mandate is not a dimension.** ``dimension.admits`` FILTERS what may pass the scoping gate;
|
|
a mandate DIRECTS what the run shall actually spend its attempts on. The two compose: a run may
|
|
be commissioned to evaluate three approaches *and* be scoped to one cost axis.
|
|
|
|
**A mandate is not a goal, either.** The numeric target has exactly one home already
|
|
(``contracts.GoalContract`` / ``--goals``); duplicating it here would put the same number in two
|
|
places, and two copies of one number drift apart (the ``(p)`` precedent — one quantisation order,
|
|
one source). The mandate carries the run's *intent* in plain language; the figure is read from
|
|
the goal config and merely RESTATED in the run announcement.
|
|
|
|
Two refusals are load-bearing, both at construction time:
|
|
|
|
* an **empty commission** (no approaches and no own proposals) — a run with nothing to do is a
|
|
caller error, not a result (mirrors ``budget.BudgetRefused``'s startup refusal: a pass that can
|
|
afford zero projects is refused before anything loads);
|
|
* a **duplicate approach id**, including one claiming the reserved ``OWN_PROPOSAL_ID`` — ``id`` is
|
|
the key each row of the coverage report is written under, so two rows sharing one key would
|
|
silently collapse into one. That is the S3.2 key-collision class, and silence is exactly what
|
|
the coverage report exists to prevent.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
from portfolio_optimiser.ir import AffectedItem, CostBaseline, SavingsProposal
|
|
|
|
#: Coverage-report id for the run's OWN (non-commissioned) proposal. Reserved: an expert approach
|
|
#: may not claim it, because the two rows would collapse onto one key.
|
|
OWN_PROPOSAL_ID = "own-proposal"
|
|
|
|
|
|
class BindingRequirement(BaseModel):
|
|
"""The ONE document in the knowledge base that BINDS a direction (P19 DEL A).
|
|
|
|
Measured over two paid stress rounds (P16 § 3, P18 § 1): **0 of 26** fasit concepts were ever
|
|
opened, in both rounds, while every run still produced proposals — so a direction could be
|
|
committed to, quantified and validated without a single requirement of the corpus having been
|
|
read. P18 closed the navigation side of that (a window, a named refusal for an invented path)
|
|
and the number did not move, which is what made it a ROLE question rather than a ladder one:
|
|
nothing in the loop ever asked the model to name what binds it.
|
|
|
|
``path`` is bundle-relative exactly as a listing gave it — the same string ``read_file`` takes,
|
|
so it can be checked against what the run actually opened without a second normalisation.
|
|
``ref`` is the requirement's OWN number as the document's frontmatter states it, never a
|
|
paraphrase: it is what a reader searches the corpus with, and what the judge matches the fasit
|
|
on.
|
|
|
|
Both are required with ``min_length=1``. A half-declared requirement would be worse than none:
|
|
it reads as a citation and points at nothing, which is the fabricated-provenance class
|
|
``write_concept_file`` refuses and ``RunFailure`` names in its own docstring.
|
|
"""
|
|
|
|
path: str = Field(min_length=1)
|
|
ref: str = Field(min_length=1)
|
|
|
|
|
|
class Approach(BaseModel):
|
|
"""One approach a domain expert wants evaluated for a project.
|
|
|
|
``description`` is the expert's own prose reason for wanting it tried; it is fed to the
|
|
proposer VERBATIM, because the reason is the part the model cannot infer from the cost data.
|
|
"""
|
|
|
|
id: str = Field(min_length=1)
|
|
label: str = Field(min_length=1)
|
|
description: str = ""
|
|
#: The cost codes this approach claims to save against, named by the expert (S7b).
|
|
#:
|
|
#: Which lines of a priced schedule an approach touches is JUDGEMENT, and it is the expert's
|
|
#: to exercise: a deterministic source that took "all of them" would make ``claimed_saving_nok
|
|
#: <= affected total`` slack enough to admit almost anything, and one that guessed a subset
|
|
#: would put its own guess behind the expert's name. Empty means the approach is not
|
|
#: quantifiable deterministically — ``candidate_from_approach`` refuses by name rather than
|
|
#: choosing.
|
|
affected_codes: tuple[str, ...] = ()
|
|
#: The expert's OWN estimate of what this approach saves, in NOK (S7b). ``None`` = not stated.
|
|
#:
|
|
#: **Per approach, never per mandate**, and that follows from ``settle``'s own rule: commissioned
|
|
#: approaches are ALTERNATIVES and are never summed, so one figure at mandate level would be
|
|
#: ambiguous across N of them.
|
|
#:
|
|
#: **This is not the run's TARGET, and the distinction is load-bearing.** The module docstring
|
|
#: refuses to carry a numeric goal because ``contracts.GoalContract`` / ``--goals`` owns exactly
|
|
#: one — and two copies of one number drift apart (the ``(p)`` precedent). A target says what the
|
|
#: RUN must achieve and is compared against the ledger; this says what ONE candidate claims and
|
|
#: is what the deterministic validator falsifies. They are different facts with different
|
|
#: owners, so this is a new number rather than a second copy of that one.
|
|
claimed_saving_nok: float | None = None
|
|
#: Which knowledge base this approach belongs to (§ C.7), by the base's id — exactly as
|
|
#: ``okf.reconcile_bundle_id`` resolves it (the base's own declaration where it makes one, the
|
|
#: mount's basename otherwise). DEFAULTS to empty, meaning "no base
|
|
#: named": a legitimate statement when the run has only one base to name, and what keeps every
|
|
#: mandate written before multi-base existed valid and dispatchable unchanged.
|
|
#:
|
|
#: The field is a ROUTING key, never a claim about content. It says which pipeline the approach
|
|
#: must be evaluated in, and ``route_by_bundle`` is the one place that reads it.
|
|
bundle_id: str = ""
|
|
#: The ONE requirement of the knowledge base that binds this direction (P19 DEL A).
|
|
#:
|
|
#: DEFAULTS to ``None``, which keeps every mandate written before today valid and dispatchable
|
|
#: unchanged — the ``bundle_id`` precedent, and the honest reading of a commission whose author
|
|
#: named no requirement. ``None`` is therefore "none stated", never "none exists": the loop
|
|
#: that MINTS an approach must say which of the two it means (``why_none`` on the hypothesis
|
|
#: line), because a direction the base has no requirement for is a finding, while one nobody
|
|
#: looked for is a silence.
|
|
requirement: BindingRequirement | None = None
|
|
|
|
|
|
class Mandate(BaseModel):
|
|
"""The commission for one run: what to evaluate, and what the run is for.
|
|
|
|
``allow_own_proposals`` defaults to ``True`` so that naming approaches never silently forbids
|
|
the system from adding its own — the operator's requirement is "these **and/or** your own",
|
|
and the permissive half is the one that matches today's behaviour.
|
|
"""
|
|
|
|
objective: str = Field(min_length=1)
|
|
approaches: tuple[Approach, ...] = ()
|
|
allow_own_proposals: bool = True
|
|
success_criteria: str = ""
|
|
|
|
@model_validator(mode="after")
|
|
def _commission_is_not_empty(self) -> Mandate:
|
|
if not self.approaches and not self.allow_own_proposals:
|
|
raise ValueError(
|
|
"empty commission: a mandate with no approaches and allow_own_proposals=false "
|
|
"gives the run nothing to evaluate"
|
|
)
|
|
return self
|
|
|
|
@model_validator(mode="after")
|
|
def _approach_ids_are_unique_and_unreserved(self) -> Mandate:
|
|
seen: set[str] = set()
|
|
for approach in self.approaches:
|
|
if approach.id == OWN_PROPOSAL_ID:
|
|
raise ValueError(
|
|
f"approach id {OWN_PROPOSAL_ID!r} is reserved for the run's own proposal"
|
|
)
|
|
if approach.id in seen:
|
|
raise ValueError(f"duplicate approach id: {approach.id!r}")
|
|
seen.add(approach.id)
|
|
return self
|
|
|
|
|
|
class MandateRoutingError(ValueError):
|
|
"""A commission that cannot be executed against the bases it was given (§ C.7).
|
|
|
|
**A ``ValueError`` by construction, and that is a measurement rather than a taxonomy note.**
|
|
økt 57 paid for the opposite: ``ExplorationError`` is a ``RuntimeError`` and therefore fell
|
|
outside ``run.main``'s ``(ValueError, FileNotFoundError, ValidationError)`` refusal tuple and
|
|
outside ``hosting``'s 400 arm, so a caller's configuration mistake would have left as a
|
|
traceback on one surface and a 500 — the crash channel — on the other. A routing refusal is
|
|
exactly that class of caller mistake, so it is born inside both nets instead of being
|
|
retrofitted into them later.
|
|
"""
|
|
|
|
|
|
class MandateCandidateError(ValueError):
|
|
"""A commissioned approach that cannot be quantified as written (S7b).
|
|
|
|
A ``ValueError`` for ``MandateRoutingError``'s measured reason: a caller's configuration mistake
|
|
must land on ``run.main``'s refusal tuple and ``hosting``'s 400 arm, never the crash channel.
|
|
|
|
Raised, never worked around. The two fields this needs — the estimate and the cost codes — are
|
|
the expert's to write, and the alternatives are both worse than a refusal: inventing a figure
|
|
puts a number nobody stands behind through a gate whose whole purpose is to falsify numbers,
|
|
and defaulting the codes to the whole schedule quietly widens the only bound
|
|
(``claimed <= affected total``) that a baseline-copied candidate does not satisfy by
|
|
construction. Validation, NEVER repair — ``write_concept_file``'s rule.
|
|
"""
|
|
|
|
|
|
def candidate_from_approach(
|
|
approach: Approach, *, baseline: CostBaseline, project_id: str
|
|
) -> SavingsProposal:
|
|
"""Build ONE candidate ``SavingsProposal`` from a commissioned approach and the project's own
|
|
priced schedule — the deterministic candidate source beside ``generate_via_llm`` (S7b).
|
|
|
|
**The split of authorship is the whole design.** The expert says WHAT (``label`` -> ``measure``,
|
|
verbatim), WHICH lines (``affected_codes``) and HOW MUCH (``claimed_saving_nok``); the bundle's
|
|
own baseline says at what QUANTITY and PRICE. Nothing here originates a number: every quantity
|
|
and unit cost is copied from ``baseline``, which on the S7b path is itself derived from a table
|
|
the producer rendered into the bundle (``okf.derive_cost_baseline``). ``project_id`` comes from
|
|
the run, which is the one party that already knows it.
|
|
|
|
**No judgement, and every gap is a refusal by name** — the ``derive_cost_baseline`` contract one
|
|
level up: a missing estimate, no named codes, or a code the baseline does not carry. The known
|
|
codes are listed in that last message because the operator's next move is to correct the mandate
|
|
against the document.
|
|
|
|
**Order follows the expert's own naming**, not the baseline's dict order: the mandate is what a
|
|
person wrote and reads back, and a reordered list would make a settlement harder to check against
|
|
the commission it settles.
|
|
|
|
**Honesty limits, stated.** ``assumptions`` is left EMPTY: an uncertainty band cannot be derived
|
|
from a single price, and inventing one would move the Monte Carlo's samples away from the
|
|
document's own figure. The consequence is measured rather than hidden — ``validator._monte_carlo``
|
|
falls back to each item's ``unit_cost``, so P10 == P50 == P90 and stage 3 reports percentiles that
|
|
carry no uncertainty. What still binds is stage 0 (reconciliation), stage 2/4b (the CBC solve
|
|
against ``MAX_SAVING_FRACTION``) and pydantic's ``claimed <= total``. And ``measure`` is not only
|
|
prose: ``validate_proposal`` stage 5 looks ``METHOD_CAPS`` up by it, so an expert's label reaches
|
|
the method cap only if it IS a registered method name — naming the method is a separate decision
|
|
this function cannot make for them.
|
|
|
|
:raises MandateCandidateError: the approach does not carry what a candidate needs.
|
|
:raises pydantic.ValidationError: the expert's figure exceeds the named lines' own total (a
|
|
``ValueError`` too, so it lands on the same refusal surfaces).
|
|
"""
|
|
if approach.claimed_saving_nok is None:
|
|
raise MandateCandidateError(
|
|
f"approach {approach.id!r} states no claimed_saving_nok, so there is no saving to "
|
|
"validate; the estimate is the expert's to write and is never invented here"
|
|
)
|
|
if not approach.affected_codes:
|
|
raise MandateCandidateError(
|
|
f"approach {approach.id!r} names no affected_codes, so which cost lines it saves "
|
|
"against is unknown; defaulting to the whole schedule would widen the only bound a "
|
|
"derived candidate does not already satisfy"
|
|
)
|
|
unknown = [code for code in approach.affected_codes if code not in baseline.items]
|
|
if unknown:
|
|
raise MandateCandidateError(
|
|
f"approach {approach.id!r} names cost code(s) {', '.join(sorted(unknown))}, which the "
|
|
f"project's cost baseline does not carry; it carries: {', '.join(sorted(baseline.items))}"
|
|
)
|
|
|
|
return SavingsProposal(
|
|
project_id=project_id,
|
|
measure=approach.label,
|
|
affected_items=[
|
|
AffectedItem(
|
|
code=code,
|
|
quantity=baseline.items[code].quantity,
|
|
unit_cost=baseline.items[code].unit_cost,
|
|
)
|
|
for code in approach.affected_codes
|
|
],
|
|
claimed_saving_nok=approach.claimed_saving_nok,
|
|
)
|
|
|
|
|
|
def route_by_bundle(mandate: Mandate, bundle_ids: Sequence[str]) -> tuple[tuple[str, Mandate], ...]:
|
|
"""Partition one commission into one sub-mandate PER knowledge base (§ C.7).
|
|
|
|
This is the whole of "multi-base", and it is deliberately a partition rather than a widening.
|
|
``run_project`` derives four single-valued things from THE bundle it is given — the project
|
|
(``_project_from_bundle`` fail-fasts when the bundle's own ``project_id`` is not the requested
|
|
one), the validator's stage-0 cost baseline, the agents' read context and the ExpeL query key —
|
|
and returns ONE ``RunResult`` with ONE ``ProvenanceStamp``. A second ``bundle_dir`` on that
|
|
signature would force a silent pick-one for all four. § C.7 says the same thing in its own
|
|
words: *pipelinen kjøres per bundle som i dag* — N calls, not one call taking N.
|
|
|
|
Order is taken from ``bundle_ids``, never from first appearance among the approaches, so the
|
|
dispatch's spend order is a property of how the run was configured rather than of how a model
|
|
happened to sequence its hypotheses.
|
|
|
|
**Fail-fast on a commission that cannot be executed as written**, mirroring ``load_mandate``'s
|
|
contract for exactly its reason: a run must never proceed on a *silently degraded* commission,
|
|
because the coverage report would then describe work nobody ordered. Two ways that happens, and
|
|
both refuse by name rather than resolving by position (the S3.2 key-collision class):
|
|
|
|
* an approach naming a base that was not configured;
|
|
* an approach naming NO base while more than one is configured — with a single base there is no
|
|
other value the field could take, so resolving it there is the only answer rather than a
|
|
guess, and it is what keeps every pre-multi-base mandate dispatchable unchanged.
|
|
|
|
A base that no approach names is NOT run: a run costs money and the commission ordered nothing
|
|
for it. The single-base case keeps its own rule above, so an own-proposals-only mandate still
|
|
reaches the one base it could possibly mean.
|
|
|
|
:raises MandateRoutingError: no bases configured, or an approach that cannot be routed.
|
|
"""
|
|
if not bundle_ids:
|
|
raise MandateRoutingError(
|
|
"a mandate cannot be routed against zero knowledge bases: an empty plan reads as "
|
|
"'there was nothing to do', which is indistinguishable from a commission that was "
|
|
"fully evaluated against nothing"
|
|
)
|
|
|
|
known = tuple(bundle_ids)
|
|
sole = known[0] if len(known) == 1 else None
|
|
grouped: dict[str, list[Approach]] = {bundle_id: [] for bundle_id in known}
|
|
for approach in mandate.approaches:
|
|
target = approach.bundle_id or sole
|
|
if target is None:
|
|
raise MandateRoutingError(
|
|
f"approach {approach.id!r} names no knowledge base and {len(known)} are "
|
|
f"configured ({', '.join(known)}); which one it belongs to is not something this "
|
|
"layer may decide on the expert's behalf"
|
|
)
|
|
if target not in grouped:
|
|
raise MandateRoutingError(
|
|
f"approach {approach.id!r} names knowledge base {target!r}, which is not "
|
|
f"configured for this run; configured: {', '.join(known)}"
|
|
)
|
|
grouped[target].append(approach)
|
|
|
|
return tuple(
|
|
(
|
|
bundle_id,
|
|
mandate.model_copy(update={"approaches": tuple(grouped[bundle_id])}),
|
|
)
|
|
for bundle_id in known
|
|
if grouped[bundle_id] or sole is not None
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ApproachOutcome:
|
|
"""What became of ONE commissioned approach — one row of the run's coverage report.
|
|
|
|
``not_evaluated`` is the row that earns this type its keep: an approach the run never got to
|
|
(budget exhausted, pass stopped) must be reported as *not evaluated*, never omitted. An omitted
|
|
row is indistinguishable from an approach nobody ordered, which is exactly the silence krav 1
|
|
is asking us to remove.
|
|
|
|
``detail`` carries the validator's reason on a rejection, or why an approach went unevaluated;
|
|
``saving_nok`` is set only for a validated row (the claimed figure the validator admitted).
|
|
"""
|
|
|
|
id: str
|
|
label: str
|
|
#: ``unsupported`` (row 6): the numbers held but the approach declared no binding requirement,
|
|
#: so it is neither a success nor a numeric rejection. Never counted as validated.
|
|
status: Literal["validated", "rejected", "unsupported", "not_evaluated"]
|
|
detail: str = ""
|
|
saving_nok: float | None = None
|
|
|
|
|
|
def load_mandate(path: str | Path) -> Mandate:
|
|
"""Fail-fast standalone loader for a run mandate (mirrors ``dimension.load_dimension``).
|
|
|
|
A mandate is *authoritative startup input*, so loading is fail-fast: a missing file raises
|
|
``FileNotFoundError`` and malformed/invalid content raises ``pydantic.ValidationError``. This
|
|
is the deliberate contrast to the tolerant verdict-inbox RAW layer
|
|
(``verdicts.load_verdicts_from_dir``), which skips bad files rather than raising — a run must
|
|
never proceed on a *silently degraded* commission, because the coverage report would then
|
|
describe work nobody ordered.
|
|
|
|
:raises FileNotFoundError: ``path`` does not point at an existing file.
|
|
:raises pydantic.ValidationError: the content is not JSON, or violates the ``Mandate`` schema.
|
|
"""
|
|
p = Path(path)
|
|
if not p.is_file():
|
|
raise FileNotFoundError(f"mandate not found: {str(path)!r}")
|
|
return Mandate.model_validate_json(p.read_text(encoding="utf-8"))
|
|
|
|
|
|
def announce(
|
|
mandate: Mandate,
|
|
*,
|
|
project_id: str,
|
|
max_rounds: int,
|
|
max_tokens: int,
|
|
dimension_label: str | None = None,
|
|
goal_nok: float | None = None,
|
|
external_services: tuple[str, ...] = (),
|
|
) -> str:
|
|
"""Render the run announcement: what this run will do, BEFORE the first (paid) model call.
|
|
|
|
Deterministic and byte-stable — no wall clock, and nothing ordered by set iteration — so it
|
|
can be golden-tested. English, like every other line this CLI prints; the Norwegian
|
|
explanation of what the block means belongs in ``docs/bestille-en-kjoring.md``, next to the
|
|
domain expert who reads it.
|
|
|
|
Optional lines are OMITTED rather than rendered empty: an announcement that printed
|
|
``Scoped to: -`` would imply a scope decision nobody made.
|
|
|
|
``external_services`` is the egress declaration. It is empty until a run is given MCP servers
|
|
(Trekk B); when it is non-empty, every server named here may be contacted — and a run never
|
|
reaches a service it did not announce. The "no external services" wording is deliberate: an
|
|
omitted line reads the same as an unchecked one.
|
|
"""
|
|
lines = [
|
|
f"Run mandate for {project_id}",
|
|
f" Objective: {mandate.objective}",
|
|
]
|
|
|
|
if mandate.approaches:
|
|
suffix = " + the system's own proposals" if mandate.allow_own_proposals else " (only these)"
|
|
lines.append(
|
|
f" Evaluates: {len(mandate.approaches)} expert-proposed approach(es){suffix}"
|
|
)
|
|
lines.extend(
|
|
f" {n}. {a.id} — {a.label}"
|
|
for n, a in enumerate(mandate.approaches, start=1)
|
|
)
|
|
else:
|
|
lines.append(" Evaluates: the system's own proposals (none were commissioned)")
|
|
|
|
if dimension_label is not None:
|
|
lines.append(f" Scoped to: {dimension_label}")
|
|
if goal_nok is not None:
|
|
lines.append(f" Target: >= {goal_nok:.0f} NOK (restated from the goal config)")
|
|
lines.append(f" Stops at: {max_rounds} rounds / {max_tokens} tokens")
|
|
lines.append(
|
|
" Contacts: "
|
|
+ (", ".join(external_services) if external_services else "no external services")
|
|
)
|
|
if mandate.success_criteria:
|
|
lines.append(f" Success: {mandate.success_criteria}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def criteria_block(success_criteria: str) -> str:
|
|
"""The ONE rendering of a commission's success criteria INTO a prompt, or ``""``.
|
|
|
|
MEASURED (P19 F2): ``success_criteria`` reached ``announce`` and nothing else, so the operator's
|
|
own statement of what a good answer looks like was printed for a human and withheld from the
|
|
only reader who could act on it. The approach's ``description`` has always reached the
|
|
generation prompt (``generate._build_messages``); this is its run-level sibling.
|
|
|
|
ONE composer, for kø-(p): the debate task and any later prompt that carries the criteria must
|
|
say the same thing about them, and two renderings of one commission are free to disagree about
|
|
what the operator asked for.
|
|
|
|
Empty in, empty out — omission rather than an empty heading, the ``announce`` rule. That is
|
|
what keeps every prompt of every un-commissioned run, the demo's included, byte-identical.
|
|
"""
|
|
if not success_criteria:
|
|
return ""
|
|
return (
|
|
"\nWhat the commissioner counts as success (restated verbatim from the commission):\n"
|
|
f"{success_criteria}\n"
|
|
)
|
|
|
|
|
|
def settle(
|
|
coverage: tuple[ApproachOutcome, ...],
|
|
*,
|
|
goal_nok: float | None = None,
|
|
goal_reached: bool | None = None,
|
|
) -> str:
|
|
"""Render the settlement: what the run actually did about each commissioned approach.
|
|
|
|
Empty coverage renders an EMPTY string — a run without a mandate has nothing to settle, and a
|
|
header over zero rows would imply a commission that never existed.
|
|
|
|
**Commissioned approaches are ALTERNATIVES and are never summed.** Several of them usually
|
|
attack the same cost line, so a total would report money the project cannot realise — measured
|
|
on a real run, three approaches against one line each validated at 30000 NOK and an earlier
|
|
version of this function claimed a 90000 total. What is honest is how many held and which one
|
|
the run carries: a selection, not an arithmetic claim.
|
|
|
|
The goal verdict is **passed in**, never computed here: ``ledger.to_ore`` is the framework's one
|
|
NOK->øre conversion and the goal comparison already runs on quantised integers, but this cannot
|
|
import it without pulling ``verdicts`` — and with it ``agent_framework`` — into a deliberately
|
|
framework-neutral file, while a private copy of a money conversion is precisely the ``(p)``
|
|
defect. So the caller decides and this renders. Both ``goal_nok`` and ``goal_reached`` must be
|
|
given for the target line to appear; a figure without a decided verdict makes no claim.
|
|
"""
|
|
if not coverage:
|
|
return ""
|
|
|
|
lines = ["Mandate outcome"]
|
|
for row in coverage:
|
|
if row.status == "validated":
|
|
amount = f"{row.saving_nok:.0f} NOK" if row.saving_nok is not None else "-"
|
|
lines.append(f" {row.id:<20} VALIDATED {amount:>14} {row.label}")
|
|
elif row.status == "rejected":
|
|
lines.append(f" {row.id:<20} REJECTED {row.detail}")
|
|
elif row.status == "unsupported":
|
|
lines.append(f" {row.id:<20} UNSUPPORTED {row.detail}")
|
|
else:
|
|
lines.append(f" {row.id:<20} NOT EVALUATED {row.detail}")
|
|
|
|
held = [r for r in coverage if r.status == "validated"]
|
|
best = max((r.saving_nok or 0.0 for r in held), default=None)
|
|
tally = f" Validated: {len(held)} of {len(coverage)} approaches"
|
|
if best is not None:
|
|
tally += f" — best {best:.0f} NOK (the outcome this run carries)"
|
|
lines.append(tally)
|
|
if goal_nok is not None and goal_reached is not None:
|
|
lines.append(
|
|
f" Target: >= {goal_nok:.0f} NOK — "
|
|
f"{'reached' if goal_reached else 'not reached'}"
|
|
)
|
|
return "\n".join(lines)
|