test(s31): pin the structural-order-preservation contract — RED at the shipped weight

This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 20:01:32 +02:00
commit b05747a3cd

View file

@ -26,6 +26,7 @@ system's own echo of the query can no longer outrank genuine expert prose.
from __future__ import annotations
import ast
import itertools
import os
import subprocess
import sys
@ -34,12 +35,14 @@ from pathlib import Path
import numpy as np
from portfolio_optimiser.semretrieval import (
SEMANTIC_WEIGHT_DEFAULT,
FakeEmbedder,
HybridRanker,
StructuralRetriever,
cosine,
)
from portfolio_optimiser.verdicts import (
_W_MAGNITUDE,
ProposalFeatures,
Verdict,
VerdictStore,
@ -439,3 +442,155 @@ def test_sc2_positive_holds_through_the_store_seam() -> None:
assert store.retrieve(_QUERY, k=3)[0].id == _CORRECT_ID
# ...and the untouched default store still cannot see it.
assert VerdictStore(verdicts=_synthetic_base()).retrieve(_QUERY, k=3)[0].id == _DISTRACTOR_ID
# --- The other half of the contract: the hybrid must PRESERVE a real structural gap ------------
#
# SC2 above proves the cosine term can break a structural TIE. Nothing above proves it stops
# there. The two claims are opposites at the margin, and only one of them was ever pinned: an
# arbitrary sha256 term with enough weight does not merely settle ties, it overturns genuine
# orderings. The blend weight is the only thing that bounds how large a gap it can overturn, and
# until this test that bound rested on a comment rather than on a guard.
#
# The fixture family is fully enumerated and RNG-free — the repo's byte-determinism posture rules
# out a flaky property test, so the sweep is exhaustive over a small vocabulary instead.
_ORDER_CODES = ("A10", "A20", "B10", "B20")
_ORDER_MEASURES = ("energy", "material")
# One amount per magnitude bucket boundary that matters: [0, 1e5), [1e5, 5e5), [5e5, 1e6).
_ORDER_AMOUNTS = (5e4, 2e5, 7e5)
# The structural score is a sum of three float terms, so a gap that is mathematically EXACTLY one
# ``_W_MAGNITUDE`` step can subtract to 0.1499999999999999. Comparing strictly would silently drop
# those pairs — and they are real contract violations: admitting them raises the qualifying count
# from 71 820 to 73 980 and the adverse count at ``w=0.5`` from 7 to 9. A guard errs toward
# sensitivity, so the threshold carries a float tolerance rather than pretending the arithmetic is
# exact.
_GAP_TOLERANCE = 1e-9
def _order_features(codes: frozenset[str], measure: str, amount: float) -> ProposalFeatures:
"""``description == measure_type`` — the shape both live minting paths emit."""
return ProposalFeatures(
affected_codes=codes,
measure_type=measure,
claimed_saving_nok=amount,
description=measure,
)
def _order_family() -> list[ProposalFeatures]:
"""4 codes (subsets of size 1-2) x 2 measures x 3 magnitude buckets = 60 feature sets."""
code_sets = [
frozenset(combo) for size in (1, 2) for combo in itertools.combinations(_ORDER_CODES, size)
]
return [
_order_features(codes, measure, amount)
for codes in code_sets
for measure in _ORDER_MEASURES
for amount in _ORDER_AMOUNTS
]
def test_hybrid_preserves_structural_order_across_one_magnitude_step() -> None:
"""CONTRACT — over a structural gap of one ``_W_MAGNITUDE`` step the hybrid must not overturn
the ordering. Asserted at the SHIPPED default weight, so the constant itself is what is pinned.
Two deliberately split assertions:
1. **Named anchor, through the real API.** query ``{A10}/energy/200k``; better
``{A10,B20}/energy/50k`` (structural 0.5500); worse ``{A20}/energy/200k`` (structural
0.4000). The gap is exactly one ``_W_MAGNITUDE`` step, and ``worse`` shares NO cost code
with the query while ``better`` does. Human-readable, instant, and it exercises
``HybridRanker.rank`` rather than a re-implementation of the score.
2. **Exhaustive breadth sweep** over the n=60 family, scoring
``w * cosine + (1 - w) * structural`` directly against one cached embedding per feature
set. ``rank()`` re-embeds every candidate on every call, which measured 6,1 s over this
family versus 1,1 s cached too expensive for a 37 s suite, and the anchor above already
covers the real ranking path.
MEASURED BASIS (this family, this embedder, ``EMBED_DIM=64``): 73 980 qualifying triples;
**9 adverse at ``w=0.5``**, and **0 at every weight from 0.45 down to 0.05**. So this test is
genuinely RED at 0.5 and green at the shipped 0.25 it is not vacuous, and it is not
fixture-tuned to the exact value either, since the whole corridor below 0.45 passes.
THE TRAP, quantified rather than warned about: a 3x2x2 family (n=24) yields **0** adverse
triples and passes SILENTLY at ``w=0.5``. The family size is therefore load-bearing and must
not be shrunk a test that cannot fail proves nothing, which is the exact defect class this
test was written to close.
Detach point: restore ``SEMANTIC_WEIGHT_DEFAULT = 0.5`` -> RED."""
embedder = FakeEmbedder()
# 1. Named anchor, through the shipped ranking path.
query = _order_features(frozenset({"A10"}), "energy", 200_000.0)
better = capture_verdict(
_order_features(frozenset({"A10", "B20"}), "energy", 50_000.0),
"approved",
"shares a cost code with the query",
)
worse = capture_verdict(
_order_features(frozenset({"A20"}), "energy", 200_000.0),
"rejected",
"shares no cost code with the query",
)
# The anchor's structural values are asserted, not assumed — if ``similarity``'s weights ever
# move, this test must fail loudly rather than quietly stop testing a one-step gap.
structural_better = similarity(query, better.proposal_features)
structural_worse = similarity(query, worse.proposal_features)
assert round(structural_better, 4) == 0.5500
assert round(structural_worse, 4) == 0.4000
assert round(structural_better - structural_worse, 9) == round(_W_MAGNITUDE, 9)
top = HybridRanker(embedder, similarity).rank(query, [better, worse], 2)
assert top[0].id == better.id, (
"the hybrid ranked a candidate sharing NO cost code with the query above one that does, "
f"across a full {_W_MAGNITUDE} structural step, on nothing but sha256 cosine noise "
f"(weight={SEMANTIC_WEIGHT_DEFAULT})"
)
# 2. Exhaustive breadth sweep, one cached embedding per feature set.
family = _order_family()
assert len(family) == 60, "the family size is load-bearing — n=24 passes this test vacuously"
vectors = [embedder(features) for features in family]
qualifying = 0
adverse: list[str] = []
for query_index, query_features in enumerate(family):
structural = [similarity(query_features, candidate) for candidate in family]
for i, j in itertools.combinations(range(len(family)), 2):
if query_index in (i, j):
continue
high, low = (i, j) if structural[i] >= structural[j] else (j, i)
if structural[high] - structural[low] < _W_MAGNITUDE - _GAP_TOLERANCE:
continue
qualifying += 1
score_high = (
SEMANTIC_WEIGHT_DEFAULT * cosine(vectors[query_index], vectors[high])
+ (1.0 - SEMANTIC_WEIGHT_DEFAULT) * structural[high]
)
score_low = (
SEMANTIC_WEIGHT_DEFAULT * cosine(vectors[query_index], vectors[low])
+ (1.0 - SEMANTIC_WEIGHT_DEFAULT) * structural[low]
)
# Compared at the ranking key's own tolerance — ``HybridRanker`` orders on
# ``(-round(score, 9), id)``, so anything finer than that is not an inversion the
# ranker can express.
if round(score_low, 9) > round(score_high, 9):
adverse.append(
f"query={sorted(query_features.affected_codes)}/{query_features.measure_type}"
f"/{query_features.claimed_saving_nok:.0f} "
f"better={sorted(family[high].affected_codes)} ({structural[high]:.4f}) "
f"lost to worse={sorted(family[low].affected_codes)} ({structural[low]:.4f})"
)
# Anti-vacuity floor: if a future edit narrows the vocabulary, this fails before the contract
# assertion below can pass for the wrong reason.
assert qualifying > 70_000, (
f"only {qualifying} qualifying triples — the family no longer expresses the contract "
"(measured basis: 73 980)"
)
assert adverse == [], (
f"{len(adverse)} structural orderings overturned by the semantic term at "
f"weight={SEMANTIC_WEIGHT_DEFAULT}; first three: {adverse[:3]}"
)