feat(consume): the ranking can read a body without the door's link line
One parameter, `link_in_signal`, default `True` -- so no payload moves. `False` scores the body with `inbox._link_enclosing`'s line removed, and removes it from the stem vocabulary too, because `searchable_text` counts `df` over the text a hit is scored on: taking the line out of one and leaving it in the other would measure two different texts. The recognition is the door's own constant plus its own place -- last in the body, after a blank line, in the door's link form. The excerpt keeps the line either way, so two readings of ONE bundle differ in ORDER alone, which is what separates a ranking movement from a budget displacement. Not a CLI flag: whether this becomes a default is K3-23's measurement, not an implementation's decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
18b390399d
commit
8e82da4682
1 changed files with 81 additions and 6 deletions
|
|
@ -59,6 +59,7 @@ from .inbox import (
|
||||||
ADJUDICATION_ADJUDICATED,
|
ADJUDICATION_ADJUDICATED,
|
||||||
ADJUDICATION_PROPOSED,
|
ADJUDICATION_PROPOSED,
|
||||||
ADJUDICATION_STATES,
|
ADJUDICATION_STATES,
|
||||||
|
ENCLOSING_SECTION,
|
||||||
)
|
)
|
||||||
from .materialize import parse_frontmatter
|
from .materialize import parse_frontmatter
|
||||||
from .profiles import (
|
from .profiles import (
|
||||||
|
|
@ -934,16 +935,50 @@ def question_uses_cost_vocabulary(question: str) -> bool:
|
||||||
return any(in_cost_vocabulary(token) for token in normalise(question))
|
return any(in_cost_vocabulary(token) for token in normalise(question))
|
||||||
|
|
||||||
|
|
||||||
def searchable_text(concepts: Sequence["Concept"]) -> list[str]:
|
#: The DOOR's line, exactly as `inbox._link_enclosing` writes it: the two SS 6.1
|
||||||
|
#: words, a markdown link, and a bundle-absolute target. Built from the door's
|
||||||
|
#: own constant, never from prose, so a rename there breaks the recognition
|
||||||
|
#: instead of quietly loosening it.
|
||||||
|
_LINK_LINE = re.compile(rf"^{re.escape(ENCLOSING_SECTION)}: \[[^\]]*\]\(/[^)]*\)$")
|
||||||
|
|
||||||
|
|
||||||
|
def body_without_link_line(body: str) -> str:
|
||||||
|
"""`body` minus the one line the door appends to a heading-only body.
|
||||||
|
|
||||||
|
THE FILE KEEPS THE LINE AND SO DOES THE READER. This is what a caller uses
|
||||||
|
to score a body the way it read before `--shell-parent` existed, on the same
|
||||||
|
bytes, in the same process -- never a second build compared against a first.
|
||||||
|
|
||||||
|
Three conditions, all the door's own, and each one is a known-negative that
|
||||||
|
would otherwise be stripped: the line is LAST, a blank line stands before
|
||||||
|
it, and it matches the door's form (`_LINK_LINE`). A human sentence opening
|
||||||
|
with the same two words has no link and survives; the door's exact form with
|
||||||
|
prose under it is not last and survives. What cannot be told apart is a
|
||||||
|
human line that is byte-for-byte the door's own, last in a body -- an
|
||||||
|
ambiguity of the form itself, stated rather than hidden.
|
||||||
|
"""
|
||||||
|
lines = body.rstrip("\n").split("\n")
|
||||||
|
if len(lines) < 3 or lines[-2].strip() or not _LINK_LINE.match(lines[-1]):
|
||||||
|
return body
|
||||||
|
return "\n".join(lines[:-2]) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def searchable_text(concepts: Sequence["Concept"], *, link_in_signal: bool = True) -> list[str]:
|
||||||
"""The text a concept is scored against, one string per concept.
|
"""The text a concept is scored against, one string per concept.
|
||||||
|
|
||||||
The same two fields the ranker's two lexical signals read -- title plus
|
The same two fields the ranker's two lexical signals read -- title plus
|
||||||
id, and body -- joined, so a `df` counted here is a `df` over exactly what
|
id, and body -- joined, so a `df` counted here is a `df` over exactly what
|
||||||
a hit can be scored on. Counting rarity over one field and matching on
|
a hit can be scored on. Counting rarity over one field and matching on
|
||||||
another would weight a token by how rare it is somewhere it is not read.
|
another would weight a token by how rare it is somewhere it is not read.
|
||||||
|
|
||||||
|
`link_in_signal=False` reads the bodies without the door's link line, for
|
||||||
|
the same reason: the stem vocabulary and the rarity `df` are counted over
|
||||||
|
what a hit is scored on, so an instrument that took the line out of the
|
||||||
|
signal and left it in the vocabulary would measure two different texts.
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
f"{concept.title} {concept.concept_id.replace('/', ' ')} {concept.body}"
|
f"{concept.title} {concept.concept_id.replace('/', ' ')} "
|
||||||
|
f"{concept.body if link_in_signal else body_without_link_line(concept.body)}"
|
||||||
for concept in concepts
|
for concept in concepts
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1368,6 +1403,7 @@ def concept_scores(
|
||||||
tie_shared_rank: bool = DEFAULT_TIE_SHARED_RANK,
|
tie_shared_rank: bool = DEFAULT_TIE_SHARED_RANK,
|
||||||
title_covered: bool = DEFAULT_TITLE_COVERED,
|
title_covered: bool = DEFAULT_TITLE_COVERED,
|
||||||
stems: frozenset[str] | None = None,
|
stems: frozenset[str] | None = None,
|
||||||
|
link_in_signal: bool = True,
|
||||||
) -> list[tuple[Concept, float, int]]:
|
) -> list[tuple[Concept, float, int]]:
|
||||||
"""Every concept, ordered best first, fused from three signals by RRF.
|
"""Every concept, ordered best first, fused from three signals by RRF.
|
||||||
|
|
||||||
|
|
@ -1412,6 +1448,17 @@ def concept_scores(
|
||||||
only `tunnel` and `vann` whose ids sorted earlier. With shared ranks it
|
only `tunnel` and `vann` whose ids sorted earlier. With shared ranks it
|
||||||
fuses to rank 3.
|
fuses to rank 3.
|
||||||
|
|
||||||
|
**`link_in_signal=False` is an INSTRUMENT, not a flag and not a default.**
|
||||||
|
The body this reads is the file's, and `inbox._link_enclosing` puts one line
|
||||||
|
in it -- `Enclosing section: [<title>](/<bundle-absolute path>)` -- whose
|
||||||
|
path repeats the document's own directory in every linked body. SS 6.1's
|
||||||
|
argument for the absolute form is about the FILE; nothing decided it should
|
||||||
|
be SCORED. Passing `False` scores the body without that line, on the same
|
||||||
|
bytes and in the same process, so the two readings can be measured against
|
||||||
|
each other rather than two builds. The excerpt a reader gets is untouched
|
||||||
|
either way; no caller in the run path passes it and the CLI does not expose
|
||||||
|
it.
|
||||||
|
|
||||||
The rule takes the FIRST position of a score group rather than its middle.
|
The rule takes the FIRST position of a score group rather than its middle.
|
||||||
Both were measured on the same four cases; the middle put the same concept
|
Both were measured on the same four cases; the middle put the same concept
|
||||||
at rank 5 where the first puts it at 3, and neither changed the three
|
at rank 5 where the first puts it at 3, and neither changed the three
|
||||||
|
|
@ -1425,6 +1472,12 @@ def concept_scores(
|
||||||
# The id BELOW the directories every concept shares: a segment every
|
# The id BELOW the directories every concept shares: a segment every
|
||||||
# concept carries separates nothing. See `shared_id_prefix`.
|
# concept carries separates nothing. See `shared_id_prefix`.
|
||||||
shared = shared_id_prefix([concept.concept_id for concept in concepts])
|
shared = shared_id_prefix([concept.concept_id for concept in concepts])
|
||||||
|
bodies = {
|
||||||
|
concept.concept_id: (
|
||||||
|
concept.body if link_in_signal else body_without_link_line(concept.body)
|
||||||
|
)
|
||||||
|
for concept in concepts
|
||||||
|
}
|
||||||
titles = {
|
titles = {
|
||||||
concept.concept_id: f"{concept.title} {' '.join(concept.concept_id.split('/')[shared:])}"
|
concept.concept_id: f"{concept.title} {' '.join(concept.concept_id.split('/')[shared:])}"
|
||||||
for concept in concepts
|
for concept in concepts
|
||||||
|
|
@ -1446,7 +1499,7 @@ def concept_scores(
|
||||||
concept.concept_id: float(
|
concept.concept_id: float(
|
||||||
_overlap(
|
_overlap(
|
||||||
question_tokens,
|
question_tokens,
|
||||||
concept.body,
|
bodies[concept.concept_id],
|
||||||
cost_vocabulary=bridge,
|
cost_vocabulary=bridge,
|
||||||
weights=weights,
|
weights=weights,
|
||||||
stems=stems,
|
stems=stems,
|
||||||
|
|
@ -1498,7 +1551,12 @@ def concept_scores(
|
||||||
_overlap(
|
_overlap(
|
||||||
question_tokens, titles[concept.concept_id], cost_vocabulary=bridge, stems=stems
|
question_tokens, titles[concept.concept_id], cost_vocabulary=bridge, stems=stems
|
||||||
)
|
)
|
||||||
+ _overlap(question_tokens, concept.body, cost_vocabulary=bridge, stems=stems)
|
+ _overlap(
|
||||||
|
question_tokens,
|
||||||
|
bodies[concept.concept_id],
|
||||||
|
cost_vocabulary=bridge,
|
||||||
|
stems=stems,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
for concept in concepts
|
for concept in concepts
|
||||||
}
|
}
|
||||||
|
|
@ -1966,6 +2024,7 @@ def build_payload(
|
||||||
stem_prefix: bool = DEFAULT_STEM_PREFIX,
|
stem_prefix: bool = DEFAULT_STEM_PREFIX,
|
||||||
source_quota: int | None = DEFAULT_SOURCE_QUOTA,
|
source_quota: int | None = DEFAULT_SOURCE_QUOTA,
|
||||||
follow_parent: bool = DEFAULT_FOLLOW_PARENT,
|
follow_parent: bool = DEFAULT_FOLLOW_PARENT,
|
||||||
|
link_in_signal: bool = True,
|
||||||
) -> dict[str, object]:
|
) -> dict[str, object]:
|
||||||
"""One bundle plus one question, cut to one contract-conformant payload.
|
"""One bundle plus one question, cut to one contract-conformant payload.
|
||||||
|
|
||||||
|
|
@ -1989,6 +2048,13 @@ def build_payload(
|
||||||
would move every consumer's bytes for a field none of them asked for.
|
would move every consumer's bytes for a field none of them asked for.
|
||||||
Whether the naming is worth the bookkeeping is the caller's call, and the
|
Whether the naming is worth the bookkeeping is the caller's call, and the
|
||||||
flag is how it stays one.
|
flag is how it stays one.
|
||||||
|
|
||||||
|
**`link_in_signal` is an instrument and carries no CLI flag** -- see
|
||||||
|
:func:`concept_scores`. `False` keeps the door's `Enclosing section:` line
|
||||||
|
out of the ranking and out of the stem vocabulary; the excerpt still carries
|
||||||
|
it, so a payload built either way holds the same bytes per excerpt and only
|
||||||
|
the ORDER can differ. That is what separates a ranking movement from a
|
||||||
|
budget displacement on one bundle.
|
||||||
"""
|
"""
|
||||||
case, expected, measured = known_positive()
|
case, expected, measured = known_positive()
|
||||||
if expected != measured:
|
if expected != measured:
|
||||||
|
|
@ -2019,12 +2085,20 @@ def build_payload(
|
||||||
# separates a Norwegian compound from four coincidental characters. One
|
# separates a Norwegian compound from four coincidental characters. One
|
||||||
# pass, over the same text the ranking reads.
|
# pass, over the same text the ranking reads.
|
||||||
stems = (
|
stems = (
|
||||||
frozenset(token for text in searchable_text(concepts) for token in normalise(text))
|
frozenset(
|
||||||
|
token
|
||||||
|
for text in searchable_text(concepts, link_in_signal=link_in_signal)
|
||||||
|
for token in normalise(text)
|
||||||
|
)
|
||||||
if stem_prefix
|
if stem_prefix
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
weights = (
|
weights = (
|
||||||
rarity_weights(normalise(question), searchable_text(concepts), stems=stems)
|
rarity_weights(
|
||||||
|
normalise(question),
|
||||||
|
searchable_text(concepts, link_in_signal=link_in_signal),
|
||||||
|
stems=stems,
|
||||||
|
)
|
||||||
if rarity_weight
|
if rarity_weight
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
@ -2044,6 +2118,7 @@ def build_payload(
|
||||||
tie_shared_rank=tie_shared_rank,
|
tie_shared_rank=tie_shared_rank,
|
||||||
title_covered=title_covered,
|
title_covered=title_covered,
|
||||||
stems=stems,
|
stems=stems,
|
||||||
|
link_in_signal=link_in_signal,
|
||||||
)
|
)
|
||||||
titles_by_id = {concept.concept_id: concept.title for concept in concepts}
|
titles_by_id = {concept.concept_id: concept.title for concept in concepts}
|
||||||
matched = sum(1 for _, _, lexical in ranked if lexical > 0)
|
matched = sum(1 for _, _, lexical in ranked if lexical > 0)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue