test(retrieval-gate): row 5 must read a hold-out set in the schema it declares — red
The registered hold-out set is written in a consumer's own question-set
schema, the one row 8 already reads, and row 5 reads every set with the
gate's synthetic reader: "not a question set this gate can read:
'set_id'", a NO no set in that schema can turn.
Three tests over an invented set in that schema and the synthetic
bundle: 4 of 5 clears 0.8 and falls under 0.9 through the same code
path; an unknown schema is a NO that names the schema; and the row
prints the share and never which hold-out question missed, with the
share itself as the known-positive in the same run. All three fail on
cb0c10b with the GateUsage above.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cb0c10b421
commit
3aff2ca0aa
1 changed files with 127 additions and 0 deletions
|
|
@ -1510,3 +1510,130 @@ def test_a_hold_out_set_with_no_question_clears_no_threshold(tmp_path: Path) ->
|
||||||
"clears the threshold: NO (the hold-out set carries no question" in detail
|
"clears the threshold: NO (the hold-out set carries no question" in detail
|
||||||
for detail in row.details
|
for detail in row.details
|
||||||
), row.details
|
), row.details
|
||||||
|
|
||||||
|
|
||||||
|
# --- row 5 reads a hold-out set in the schema it declares --------------------
|
||||||
|
#
|
||||||
|
# The registered hold-out set is written in a consumer's own question-set
|
||||||
|
# schema, the one row 8 already reads, and row 5 used to read every set with
|
||||||
|
# the gate's synthetic reader: `not a question set this gate can read:
|
||||||
|
# 'set_id'`, a NO no set in that schema could ever turn. Every file below is
|
||||||
|
# written in that shape with INVENTED content over the synthetic bundle; the
|
||||||
|
# real hold-out set is never read by a test.
|
||||||
|
|
||||||
|
#: Distinctive on purpose: a row that printed a question's id or text would
|
||||||
|
#: carry one of these, and nothing else in the gate's output can.
|
||||||
|
_HOLD_OUT_MISS_ID = "HOLDOUT-MISS-7Q"
|
||||||
|
_HOLD_OUT_MISS_TEXT = "Hvilken farge har den fraflyttede fyrlykta paa Utvaer?"
|
||||||
|
_HOLD_OUT_HIT_IDS = ("HOLDOUT-H1", "HOLDOUT-H2", "HOLDOUT-H3", "HOLDOUT-H4")
|
||||||
|
|
||||||
|
|
||||||
|
def _fase_set(path: Path, *, schema: str = "fase-sporsmaal/1") -> Path:
|
||||||
|
"""Four questions the positive bundle answers and one it cannot: a
|
||||||
|
share of 4 of 5, counted here off the file and not off the row."""
|
||||||
|
hits = [
|
||||||
|
("Naar kontrolleres vinterberedskapen?", "haandbok", "innen 1. november"),
|
||||||
|
("Hvordan kvitteres noekkelen ut?", "haandbok", "signatur i noekkelboka"),
|
||||||
|
("Hvor ofte byttes batteriet i roekvarsleren?", "haandbok", "en gang i aaret"),
|
||||||
|
("Hva vedtok aarsmoetet om kontingenten?", "referat", "480 kroner"),
|
||||||
|
]
|
||||||
|
questions = [
|
||||||
|
{"id": qid, "question": text, "fasit": [{"doc": doc, "quote": quote}]}
|
||||||
|
for qid, (text, doc, quote) in zip(_HOLD_OUT_HIT_IDS, hits)
|
||||||
|
]
|
||||||
|
questions.append(
|
||||||
|
{
|
||||||
|
"id": _HOLD_OUT_MISS_ID,
|
||||||
|
"question": _HOLD_OUT_MISS_TEXT,
|
||||||
|
"fasit": [{"doc": "haandbok", "quote": "fyrlykta er malt i gult og fiolett"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema": schema,
|
||||||
|
"hit_rule": "an excerpt whose source_file is <doc>.md AND carries the quote",
|
||||||
|
"questions": questions,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def test_row_five_measures_a_hold_out_set_in_the_wiki_schema(tmp_path: Path) -> None:
|
||||||
|
"""Both directions through the same set and the same bundle: 4 of 5
|
||||||
|
clears 0.8 and falls under 0.9. The share is counted off the file first,
|
||||||
|
so the row is not the only thing that knows it."""
|
||||||
|
held_out = _fase_set(tmp_path / "held-out.json")
|
||||||
|
written = json.loads(held_out.read_text(encoding="utf-8"))
|
||||||
|
assert len(written["questions"]) == 5
|
||||||
|
bundle = _bundles(tmp_path)["positive"]
|
||||||
|
|
||||||
|
clears = gate.row_five(
|
||||||
|
_registration(tmp_path, set_path=held_out, bundle=bundle, threshold=0.8, name="c.json"),
|
||||||
|
provenance=lambda _: _carried_by_git(),
|
||||||
|
)
|
||||||
|
assert any(
|
||||||
|
"the measured hold-out clears the threshold: yes (4 of 5 = 0.8000 against 0.8000)" in detail
|
||||||
|
for detail in clears.details
|
||||||
|
), clears.details
|
||||||
|
assert (clears.k, clears.m, clears.status) == (11, 11, gate.GREEN)
|
||||||
|
|
||||||
|
falls = gate.row_five(
|
||||||
|
_registration(tmp_path, set_path=held_out, bundle=bundle, threshold=0.9, name="f.json"),
|
||||||
|
provenance=lambda _: _carried_by_git(),
|
||||||
|
)
|
||||||
|
assert any(
|
||||||
|
"the measured hold-out clears the threshold: NO (4 of 5 = 0.8000 against 0.9000)" in detail
|
||||||
|
for detail in falls.details
|
||||||
|
), falls.details
|
||||||
|
assert (falls.k, falls.m, falls.status) == (10, 11, gate.RED)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_hold_out_set_in_an_unknown_schema_is_a_named_no(tmp_path: Path) -> None:
|
||||||
|
"""Fail closed: a set read in a shape it was not written in measures
|
||||||
|
nothing, so an unknown schema is a NO that NAMES the schema -- never a
|
||||||
|
guess at a reader and never an exception."""
|
||||||
|
held_out = _fase_set(tmp_path / "held-out.json", schema="noe-annet/9")
|
||||||
|
row = gate.row_five(
|
||||||
|
_registration(
|
||||||
|
tmp_path, set_path=held_out, bundle=_bundles(tmp_path)["positive"], threshold=0.8
|
||||||
|
),
|
||||||
|
provenance=lambda _: _carried_by_git(),
|
||||||
|
)
|
||||||
|
verdict = [d for d in row.details if "the measured hold-out clears the threshold" in d]
|
||||||
|
assert len(verdict) == 1, row.details
|
||||||
|
assert ": NO (" in verdict[0]
|
||||||
|
assert "schema `noe-annet/9`" in verdict[0], verdict[0]
|
||||||
|
assert "fase-sporsmaal/1" in verdict[0], verdict[0]
|
||||||
|
assert row.fails
|
||||||
|
|
||||||
|
|
||||||
|
def test_row_five_prints_the_share_and_never_which_hold_out_question_missed(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""A hold-out set whose misses are printed is a tuning set by the next
|
||||||
|
session. The known-positive in the same run: the row DID measure, one
|
||||||
|
question missed, and the share is printed -- so the absence of the ids
|
||||||
|
and the text below is the row's silence and not a row that never ran."""
|
||||||
|
held_out = _fase_set(tmp_path / "held-out.json")
|
||||||
|
for threshold in (0.8, 0.9):
|
||||||
|
row = gate.row_five(
|
||||||
|
_registration(
|
||||||
|
tmp_path,
|
||||||
|
set_path=held_out,
|
||||||
|
bundle=_bundles(tmp_path)["positive"],
|
||||||
|
threshold=threshold,
|
||||||
|
name=f"r{threshold}.json",
|
||||||
|
),
|
||||||
|
provenance=lambda _: _carried_by_git(),
|
||||||
|
)
|
||||||
|
text = gate.render([row]) + json.dumps(row.to_json(), ensure_ascii=False)
|
||||||
|
assert "4 of 5 = 0.8000" in text, text
|
||||||
|
assert _HOLD_OUT_MISS_ID not in text
|
||||||
|
assert _HOLD_OUT_MISS_TEXT not in text
|
||||||
|
assert "fyrlykta" not in text
|
||||||
|
for hit in _HOLD_OUT_HIT_IDS:
|
||||||
|
assert hit not in text
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue