refactor(examples): replace sector-specific example material with generic, fictitious examples

The context sets, the packaged knowledge bases and the example bundles are
replaced by one fictitious example set about IT operations in an invented
organisation: three context sets (serverrom-2027, driftsavtale-2027 and the
two-base drift-og-avtale-2027), two synthetic knowledge bases under
src/portfolio_optimiser/data/kunnskapsbaser and two example bundles under
src/portfolio_optimiser/data/bundles. Numbers, codes and structural values in
tests and fixtures are kept; names, ids and wording change. Dated measurement
documents that only recorded runs on the replaced material are deleted.

Gate figures measured on the new set are not comparable with earlier ones.
The exclusion gate from the previous commit is green: 0 tracked files hit
outside the shared/ subtree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-23 15:04:21 +02:00
commit 37547fe292
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
1147 changed files with 24138 additions and 9503 deletions

View file

@ -332,7 +332,7 @@ from portfolio_optimiser.ledger import LedgerEntry, SavingsLedger, to_ore
ledger = SavingsLedger()
ledger.add_realized(
LedgerEntry(
project_id="FV42-GSV-E1",
project_id="KONTOR-IT-E1",
dimension="rigg",
candidate_identity="33fba649cade8529",
amount_ore=to_ore(40000),
@ -593,7 +593,7 @@ when the seam is detached, so the loop cannot silently degrade into theater.
```bash
# Single-project, offline drill (builds contracts + clients, stops before the first model call):
uv run python -m portfolio_optimiser.run FV42-GSV-E1 --docs-dir <docs> --bundle-dir <bundle> --live-dry-run
uv run python -m portfolio_optimiser.run KONTOR-IT-E1 --docs-dir <docs> --bundle-dir <bundle> --live-dry-run
# Portfolio run with a savings goal checked against an accumulated ledger:
uv run python -m portfolio_optimiser.run --portfolio --goals goals.json --ledger ledger.json
# Read-only value report over an accumulated ledger (human table; add --json for JSON):
@ -607,7 +607,7 @@ when the seam is detached, so the loop cannot silently degrade into theater.
no outbox artefact, no wiki entry, no verdict.
```bash
uv run python -m portfolio_optimiser.run FV42-GSV-E1 --docs-dir <docs> --bundle-dir <bundle> \
uv run python -m portfolio_optimiser.run KONTOR-IT-E1 --docs-dir <docs> --bundle-dir <bundle> \
--explore "Find the cheapest saving worth testing here" --explore-config exploration.json
```
@ -692,7 +692,7 @@ when the seam is detached, so the loop cannot silently degrade into theater.
nobody ever asks.
```bash
uv run python -m portfolio_optimiser.run FV42-GSV-E1 --docs-dir <docs> --bundle-dir <bundle> \
uv run python -m portfolio_optimiser.run KONTOR-IT-E1 --docs-dir <docs> --bundle-dir <bundle> \
--explore "Find the cheapest saving worth testing here" --explore-config exploration.json \
--plan-review --outbox-dir out --run-id r1
```
@ -766,12 +766,12 @@ when the seam is detached, so the loop cannot silently degrade into theater.
```bash
# day 1 — park the review and exit
uv run python -m portfolio_optimiser.run FV42-GSV-E1 --docs-dir <docs> --bundle-dir <bundle> \
uv run python -m portfolio_optimiser.run KONTOR-IT-E1 --docs-dir <docs> --bundle-dir <bundle> \
--explore "Find the cheapest saving worth testing here" --explore-config exploration.json \
--checkpoint-dir checkpoints --outbox-dir out --run-id r1
# day N — a fresh process, resuming from what is on disk and nothing else
uv run python -m portfolio_optimiser.run FV42-GSV-E1 --docs-dir <docs> --bundle-dir <bundle> \
uv run python -m portfolio_optimiser.run KONTOR-IT-E1 --docs-dir <docs> --bundle-dir <bundle> \
--outbox-dir out --checkpoint-dir checkpoints --review-inbox inbox --resume r1
```
@ -829,7 +829,7 @@ when the seam is detached, so the loop cannot silently degrade into theater.
- **Deriving the cost baseline from the knowledge base** — `--derive-cost-baseline` (opt-in,
requires `--bundle-dir`). The deterministic validator anchors a proposal's `affected_items` to
the project's actual cost lines. Normally those come from a hand-written `cost-baseline.json` in
the bundle, or — on the road reference path — from the project's own `cost_items`. Neither is
the bundle, or — on the reference path — from the project's own `cost_items`. Neither is
available when the bundle was *ingested* from tender documents, so this third route reads the
priced schedule that is already in the base: one markdown table whose header names a cost code, a
quantity and a unit price. Both table forms the producer emits are read — the pipe tables its
@ -879,7 +879,7 @@ when the seam is detached, so the loop cannot silently degrade into theater.
- **An identifier that stands everywhere identifies nothing (P18).** The validator's stage 0b
grounds each proposed cost code in the run's non-model-authored input. Plain containment was not
enough: measured on a live run, a proposal put 250 000 NOK on a line coded `R761` — the knowledge
enough: measured on a live run, a proposal put 250 000 NOK on a line coded with the knowledge
base's own *name*, carried by all 2 756 of its documents — and the whole gate said `validated`.
A code now grounds only if it is at least 3 characters long **and** appears in fewer than 5 % of
the documents the input is made of, with an absolute floor of 10 documents so the share is never
@ -891,10 +891,10 @@ when the seam is detached, so the loop cannot silently degrade into theater.
can never reach the floor.
- **`--docs-dir` is optional when `--bundle-dir` is given (P18).** On the bundle path `docs_dir` is
never read — retrieval, the chunk tool and the citation check all live in the road branch — so
never read — retrieval, the chunk tool and the citation check all live in the reference branch — so
naming the same directory twice was a requirement for a path that ignores it. Both forms work;
neither turns retrieval into a substitute for ingesting documents into a knowledge base, and the
road path still requires a real `--docs-dir`.
reference path still requires a real `--docs-dir`.
- **Requiring the run to be anchored** — `--require-cost-baseline` (opt-in, requires
`--bundle-dir`). Without a baseline the validator's stage 0 is skipped, and the run says so on
@ -911,8 +911,8 @@ when the seam is detached, so the loop cannot silently degrade into theater.
```
- **The project's own price schedule** — `--cost-baseline FILE` (requires `--bundle-dir` or
`--across-bundle`). A knowledge base carries what is REQUIRED, not what things cost: a road
normal, a standard or a regulation has requirements and no amounts, so a run against one has
`--across-bundle`). A knowledge base carries what is REQUIRED, not what things cost: a
requirements standard or a regulation has requirements and no amounts, so a run against one has
nothing for the validator's stage 0 to reconcile against and that stage is skipped. The price
belongs to the project, and this is where you hand it over: FILE is a `cost-baseline.json` — the
same `{"project_id": …, "items": {"<code>": {"quantity": …, "unit_cost": …}}}` shape a bundle may
@ -992,10 +992,11 @@ when the seam is detached, so the loop cannot silently degrade into theater.
`filter` narrows a level instead of paging it: a case-insensitive substring over a document's
title and reference number (`req_number` / `prosessnr`) and over a subdirectory's path, answering
with `total_matches` beside `total`. A filter that matches nothing is an answer, never a refusal.
Measured 2026-09-14 over four delivered bases, default listing of the largest level in each:
`krav/N200` 169 974 → 1 537 characters, `krav/N100` 69 250 → 1 493, R761's root 110 912 → 479
(its cost was 2 728 *subdirectories*, which is why the window covers both kinds), `krav/N500`
39 853 → 1 453. The largest single call any caller can now make is ~7 600 characters.
Measured 2026-09-14 over the four requirement and process-catalogue bases used during
development, default listing of the largest level in each: 169 974 → 1 537 characters,
69 250 → 1 493, the process catalogue's root 110 912 → 479 (its cost was 2 728
*subdirectories*, which is why the window covers both kinds), and 39 853 → 1 453. The largest
single call any caller can now make is ~7 600 characters.
A path the caller **invented** is refused by name rather than failing opaquely: `read_file` on a
path the base does not hold answers `REFUSED (BundlePathNotFound)` and names the nearest directory
@ -1160,7 +1161,7 @@ whether the first deployment works. Gated by `tests/test_handover_package_loadbe
- [Kunnskapsbase for én kjøring](docs/kunnskapsbase-for-en-kjoring.md) *(norsk)* — how to compose
the base for ONE specific run: which categories of knowledge follow the project, the domain and
the organisation; a content-type table (owner, delivery form, role in the loop, what happens
when it is missing); and a worked road project from the commission to a base that passes the
when it is missing); and a worked example project from the commission to a base that passes the
dry-run check. Every technical claim is marked verified or assumed.
- [OKF consumption contracts](docs/okf-konsum-kontrakter.md) — the three cross-repo facts this
consumer and the producer are both held to: the falsification threshold, the adjudication
@ -1187,13 +1188,17 @@ uv run ruff check .
### Frozen knowledge bases
The measurements that read a delivered corpus (the v1 gate's rows 6–7, the stress judge, and four
corpus tests) read a **frozen copy** under `~/corpora/po-frosne-bundles/<name>-<short sha256>/`,
outside every repository, pinned by `src/portfolio_optimiser/frozen_bundles.json`. The bundles
themselves are never committed here; only the pin is. A copy that no longer matches its pin fails
loudly (`frosset bundle … avviker fra pin`), and a copy that is absent is `IKKE MÅLT`, never green.
corpus tests) read a **frozen copy** `<name>-<short sha256>/`, pinned by
`src/portfolio_optimiser/frozen_bundles.json`. By default the store is the two synthetic example
bases that ship with the package, `driftskrav-2027` and `prosesskatalog-2027` (an invented
requirements corpus and process catalogue for "Eksempelvirksomheten", under
`src/portfolio_optimiser/data/kunnskapsbaser/`). To measure against your own corpus, point
`PORTFOLIO_FROZEN_BUNDLES` at your own store and pin it the same way. A copy that no longer matches
its pin fails loudly (`frosset bundle … avviker fra pin`), and a copy that is absent is
`IKKE MÅLT`, never green.
**Renewing a copy is a decision, not maintenance: a new copy and a new pin go in the SAME commit.**
Re-copy the base, recompute with `portfolio_optimiser.frozen_bundles.digest_bundle`, rename the
directory to carry the new short digest, and update `frozen_bundles.json` in that same change.
`--bundle-root` (or `PORTFOLIO_VEGNORMAL_ROOT`) stays as an explicit, **unpinned** live mount — the
`--bundle-root` (or `PORTFOLIO_BUNDLE_ROOT`) stays as an explicit, **unpinned** live mount — the
way to look at a fresh corpus before deciding to refreeze.