fix(consume): match an identifier by equality, deliver the concept a question names

A question naming a requirement number now delivers that requirement at rank 1
on all three vegnormal bundles (was 96, 9, 35 of 446, 1 133, 270). Two
mechanisms, both measured, both default because no published figure moves.

The matcher: `tokens_match` compared four leading characters, so the unique
identifier `3.3.1-13` read as 135 of 446 common and the rarity weight ranked a
common adjective above the number naming the document. An identifier now
matches by equality alone; df falls to 1/1/1. Words keep the prefix rule, which
was measured for Norwegian compounds. Equality has no floor either, so a
three-character identifier stops matching nothing at all -- measured, `9.2`
reached 0 concepts while sitting verbatim in one title.

The lookup: a question carrying an identifier that sits verbatim in a concept's
title or id is answered by a partition over the fusion's output, not by a
fourth signal. The form was chosen by measurement -- a fourth RRF signal was
simulated first and put the gold at rank 26 / 15 / 19, none of them delivered,
because RRF consumes ranks only and one signal contributes at most 1/(RRF_K+1).
No frontmatter key list is declared: of 1 846 concepts carrying `req_number`,
1 846 also carry that identifier in the title.

The matcher alone is NOT a monotone win (N200 9 -> 26, because that gold's body
cross-references a neighbouring number that the prefix rule counted as a hit on
the question's). Only the partition delivers; the table is in the record.

Consumer corpus: every named control byte-identical against a frozen
`git archive` copy of 116d3e1 -- four payload digests, eight candidate ranks,
six hit@8 rows, both known-negatives. One document that was withheld at
position 621 of 621 is now delivered at rank 1, on a corpus with no
requirement-number grammar at all.

13 tests (12 red before the rules existed), 7 mutations, 7 red. 1 320 passed.
Consumption-side only; no bundle ref moves.
Record: docs/2026-09-08-eksakt-oppslag.md

Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-08 13:27:59 +02:00
commit a37d5ced38
3 changed files with 638 additions and 4 deletions

View file

@ -1669,9 +1669,12 @@ def test_counting_leaves_the_identifier_worth_no_more_than_the_common_word() ->
# The known-positive for the weight: without it, a rank of 1 with the
# weight on would prove nothing, because nothing would have been shown to
# hold the gold down in the first place.
# `lookup=False` throughout: this fixture puts the identifier in the
# gold's TITLE, which the lookup partition (Step 14) answers at rank one.
# The claim here is about the FUSION, so the fusion is what is read.
for identifier_in_body in (False, True):
concepts = _rarity_corpus(identifier_in_body=identifier_in_body)
ranked = okf_consume.concept_scores(concepts, RARITY_QUESTION, {})
ranked = okf_consume.concept_scores(concepts, RARITY_QUESTION, {}, lookup=False)
assert _rank_of(ranked, RARITY_GOLD) == 18
lexical = {concept.concept_id: value for concept, _, value in ranked}
assert lexical["krav/c-00"] == 6
@ -1716,7 +1719,9 @@ def test_a_rarity_weight_cannot_move_a_signal_the_gold_already_leads() -> None:
# And the rank it buys is the rank it already had, so the fusion sees none of it.
assert _signal_rank(concepts, RARITY_QUESTION, RARITY_GOLD, None) == 1
assert _signal_rank(concepts, RARITY_QUESTION, RARITY_GOLD, weights) == 1
ranked = okf_consume.concept_scores(concepts, RARITY_QUESTION, {}, weights=weights)
ranked = okf_consume.concept_scores(
concepts, RARITY_QUESTION, {}, weights=weights, lookup=False
)
assert _rank_of(ranked, RARITY_GOLD) == 18
@ -1885,11 +1890,203 @@ def test_the_weight_reaches_the_title_signal_and_reorders_it() -> None:
)
assert _signal_rank(concepts, question, "krav/t-29", None) == 30
assert _signal_rank(concepts, question, "krav/t-29", weights) == 1
plain = okf_consume.concept_scores(concepts, question, {})
weighted = okf_consume.concept_scores(concepts, question, {}, weights=weights)
# `lookup=False`: the gold's title bears the identifier, so the lookup
# partition answers this question at rank one. The fusion is the claim.
plain = okf_consume.concept_scores(concepts, question, {}, lookup=False)
weighted = okf_consume.concept_scores(concepts, question, {}, weights=weights, lookup=False)
# Fused: 30 -> 18. The title signal is reordered from last to first and the
# fused rank moves by twelve, not to one -- the other two signals still
# cannot see the identifier. That gap IS the finding of this session, and
# the numbers are here so a change to either half shows up as a diff.
assert _rank_of(plain, "krav/t-29") == 30
assert _rank_of(weighted, "krav/t-29") == 18
# --- Step 14: exact identifier matching and the lookup signal (O2c) -----------
#: The lookup fixture: 300 concepts that all carry the word a standards corpus
#: repeats on every page, one of which also carries the identifier. Synthetic
#: rather than borrowed, and 300 rather than 30 so a rank of one is a claim
#: about the rule and not about a small corpus.
LOOKUP_QUESTION = "Hva krever Krav 3.3.1-13? Gjengi det sentrale vilkåret."
LOOKUP_GOLD = "krav/c-299"
LOOKUP_SECOND = "krav/z-000"
def _lookup_corpus(*, second_holder: bool = False) -> list[okf_consume.Concept]:
"""299 neighbours numbered `3.3.1-<n>`, plus the concept the question names.
The neighbours share the identifier's first four characters, which is
exactly what made a unique requirement number read as 135-of-446 common on
a real bundle. The gold's `concept_id` sorts LAST, so nothing but the rule
can lift it.
"""
body = "Kravet gjengir det sentrale vilkåret for anlegget."
concepts = [
_synthetic(f"krav/c-{index:03d}", f"Krav 3.3.1-{index + 20} Alminnelig krav", body)
for index in range(299)
]
concepts.append(_synthetic(LOOKUP_GOLD, "Krav 3.3.1-13 Tekniske bygg", body))
if second_holder:
# The second holder's id sorts AFTER the gold's and its title answers
# four question tokens more, so the fusion ranks it FIRST while byte
# order ranks it second. The two orders disagree on purpose: a lookup
# that re-sorted its hits by id instead of keeping the fused order
# would otherwise be indistinguishable from one that keeps it.
concepts.append(
_synthetic(LOOKUP_SECOND, "Krav 3.3.1-13 Gjengi det sentrale vilkåret", body)
)
return concepts
def test_an_identifier_matches_its_own_spelling_and_no_neighbouring_number() -> None:
# The defect, at the case it costs most: `3.3.1-13` and `3.3.1-14` share
# four leading characters, so the prefix rule called them a match and every
# requirement number beginning `3.3.` counted as a hit.
assert okf_consume.tokens_match("3.3.1-13", "3.3.1-13") is True
assert okf_consume.tokens_match("3.3.1-13", "3.3.1-14") is False
assert okf_consume.tokens_match("3.3.1-13", "3.3.2-13") is False
# Symmetric, like the rule it replaces.
assert okf_consume.tokens_match("3.3.1-14", "3.3.1-13") is False
def test_a_norwegian_compound_still_matches_on_the_shared_prefix() -> None:
# The known-positive. `MIN_SHARED_PREFIX` exists because Norwegian
# compounds do not match token-exactly, and an identifier rule that also
# narrowed words would buy one lookup by losing every compound.
assert okf_consume.tokens_match("brannsikring", "brannvern") is True
assert okf_consume.tokens_match("prisene", "prissammenstilling") is True
assert okf_consume.tokens_match("varene", "varemottak") is True
assert okf_consume.tokens_match("brann", "bygg") is False
def test_a_short_identifier_becomes_reachable_because_equality_has_no_floor() -> None:
# `MIN_SHARED_PREFIX` made a three-character identifier match NOTHING, not
# even itself: measured on a 629-concept bundle, `9.2` reached 0 concepts
# under the matcher while sitting verbatim in one title.
assert okf_consume.tokens_match("9.2", "9.2") is True
assert okf_consume.tokens_match("9.2", "9.3") is False
# The floor still stands for words, which is what it was measured for.
assert okf_consume.tokens_match("veg", "veg") is False
def test_the_identifier_rule_makes_a_unique_number_unique_in_the_document_frequency() -> None:
# The df is what the rarity weight reads, and 135-of-446 was the reason a
# unique requirement number weighed less than a common adjective.
concepts = _lookup_corpus()
corpus = okf_consume.searchable_text(concepts)
identifier = "3.3.1-13"
under_matcher = sum(
1
for text in corpus
if any(okf_consume.tokens_match(identifier, other) for other in okf_consume.normalise(text))
)
assert under_matcher == 1
def test_is_identifier_names_numbers_and_not_words() -> None:
assert okf_consume.is_identifier("3.3.1-13") is True
assert okf_consume.is_identifier("10.2-2") is True
assert okf_consume.is_identifier("r610.4") is True
assert okf_consume.is_identifier("brannsikring") is False
assert okf_consume.is_identifier("krav") is False
# A bare number is not an identifier: it has no separator, and every page
# number in a corpus would become one.
assert okf_consume.is_identifier("2023") is False
# THE WHOLE TOKEN, never a part of one. No token `normalise` emits can tell
# a whole-token rule from a substring rule -- the generic split keeps
# neither `.` nor `-`, so a split token can never contain an identifier --
# which is why the commitment is asserted on the function rather than on an
# output it happens to leave unchanged.
assert okf_consume.is_identifier("bilag3.3-1x") is False
def test_a_question_naming_an_identifier_delivers_the_concept_that_bears_it() -> None:
# The lookup: 300 concepts all answer `krav`, one is NAMED. Rank one, not
# rank eight -- a question that names a concept is not a search.
concepts = _lookup_corpus()
ranked = okf_consume.concept_scores(concepts, LOOKUP_QUESTION, {})
assert _rank_of(ranked, LOOKUP_GOLD) == 1
def test_two_concepts_bearing_the_same_identifier_both_reach_the_top() -> None:
concepts = _lookup_corpus(second_holder=True)
ranked = okf_consume.concept_scores(concepts, LOOKUP_QUESTION, {})
top = [concept.concept_id for concept, _, _ in ranked[:2]]
assert sorted(top) == sorted([LOOKUP_GOLD, LOOKUP_SECOND])
def test_the_lookup_is_invisible_to_a_question_that_names_no_identifier() -> None:
# The KNOWN-NEGATIVE. Every published control question on the consumer
# corpus carries zero identifiers, so this is the property that lets the
# rule ship on by default.
concepts = _lookup_corpus()
question = "Hvordan skal det sentrale vilkåret oppfylles?"
assert okf_consume.lookup_hits(concepts, question) == ()
ranked = okf_consume.concept_scores(concepts, question, {})
order = [concept.concept_id for concept, _, _ in ranked]
assert order == sorted(order)
def test_a_question_without_an_identifier_never_reads_the_corpus(
monkeypatch: pytest.MonkeyPatch,
) -> None:
# The early return is a COST commitment, not a semantic one: an empty
# identifier set intersects to nothing anyway, so no ranking distinguishes
# the guard from its absence. What it does buy is the corpus never being
# tokenised for a question that cannot be a lookup -- measured, the lookup
# pass costs 0.024 s over 1 133 concepts when it does run.
concepts = _lookup_corpus()
calls: list[str] = []
original = okf_consume.normalise
monkeypatch.setattr(
okf_consume, "normalise", lambda text: (calls.append(text), original(text))[1]
)
assert okf_consume.lookup_hits(concepts, "Hvordan oppfylles vilkåret?") == ()
assert calls == ["Hvordan oppfylles vilkåret?"]
def test_an_identifier_no_concept_bears_changes_nothing_and_empties_nothing() -> None:
concepts = _lookup_corpus()
question = "Hva krever Krav 9.9.9-99? Gjengi det sentrale vilkåret."
assert okf_consume.lookup_hits(concepts, question) == ()
ranked = okf_consume.concept_scores(concepts, question, {})
assert len(ranked) == len(concepts)
order = [concept.concept_id for concept, _, _ in ranked]
assert order == sorted(order)
def test_the_three_spellings_of_one_identifier_are_one_lookup() -> None:
concepts = _lookup_corpus()
for dash in ("-", "", ""):
question = f"Hva krever Krav 3.3.1{dash}13? Gjengi det sentrale vilkåret."
assert okf_consume.lookup_hits(concepts, question) == (LOOKUP_GOLD,)
def test_the_lookup_reads_the_text_the_title_signal_reads() -> None:
# Measured on three real bundles: `req_number` carries an identifier that
# is ALSO in the title on 1 846 of 1 846 concepts that have the key, so a
# frontmatter key list buys nothing and is not declared.
identified = _synthetic("krav/only-in-id", "Alminnelig krav", "Kravet gjelder anlegget.")
concepts = [*_lookup_corpus()[:10], identified]
assert okf_consume.lookup_hits(concepts, "Hva krever Krav 3.3.1-13?") == ()
in_id = _synthetic("krav/3.3.1-13", "Alminnelig krav", "Kravet gjelder anlegget.")
assert okf_consume.lookup_hits([*concepts, in_id], "Hva krever Krav 3.3.1-13?") == (
"krav/3.3.1-13",
)
def test_the_lookup_keeps_the_fused_order_among_the_concepts_it_lifts() -> None:
# Determinism with several hits: the lifted concepts keep the order the
# fusion gave them, which is itself declared down to the id tie-break.
concepts = _lookup_corpus(second_holder=True)
ranked = okf_consume.concept_scores(concepts, LOOKUP_QUESTION, {})
hits = okf_consume.lookup_hits(concepts, LOOKUP_QUESTION)
# `lookup_hits` reports in BYTE order, and the two orders disagree here on
# purpose: the second holder's id sorts second and its fusion rank is better.
assert hits == (LOOKUP_GOLD, LOOKUP_SECOND)
lifted = [concept.concept_id for concept, _, _ in ranked[: len(hits)]]
assert lifted == [LOOKUP_SECOND, LOOKUP_GOLD]
assert [concept.concept_id for concept, _, _ in ranked[len(hits) :]] == sorted(
concept.concept_id for concept in concepts if concept.concept_id not in hits
)