feat(consume): cut by exact knapsack with a closing partition
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f4b1f4ef0b
commit
7eb87a04cb
2 changed files with 262 additions and 1 deletions
|
|
@ -438,3 +438,114 @@ def test_the_price_concept_leads_on_the_price_question_in_the_fixture() -> None:
|
||||||
scores = okf_consume.document_scores(FIXTURE, "Hvordan skal prisene fylles ut?")
|
scores = okf_consume.document_scores(FIXTURE, "Hvordan skal prisene fylles ut?")
|
||||||
ranked = okf_consume.concept_scores(concepts, "Hvordan skal prisene fylles ut?", scores)
|
ranked = okf_consume.concept_scores(concepts, "Hvordan skal prisene fylles ut?", scores)
|
||||||
assert ranked[0][0].concept_id == "krav/prissammenstilling"
|
assert ranked[0][0].concept_id == "krav/prissammenstilling"
|
||||||
|
|
||||||
|
|
||||||
|
# --- Step 7: the cut ----------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _cut_fixture(
|
||||||
|
question: str = "Hvordan skal prisene fylles ut?", k: int = 8, limit: int | None = None
|
||||||
|
) -> tuple[list[dict[str, object]], list[tuple[str, str]], int]:
|
||||||
|
concepts = _fixture_concepts()
|
||||||
|
scores = okf_consume.document_scores(FIXTURE, question)
|
||||||
|
ranked = okf_consume.concept_scores(concepts, question, scores)
|
||||||
|
delivered, withheld = okf_consume.cut(
|
||||||
|
ranked, k=k, limit=okf_consume.DEFAULT_LIMIT if limit is None else limit
|
||||||
|
)
|
||||||
|
return list(delivered), list(withheld), len(ranked)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_fixture_has_both_a_verdict_concept_and_a_delivered_one() -> None:
|
||||||
|
# The control on every count below: neither zero may come from an empty
|
||||||
|
# fixture.
|
||||||
|
delivered, withheld, considered = _cut_fixture()
|
||||||
|
assert delivered, "nothing was delivered, so 'excluded' would measure nothing"
|
||||||
|
assert withheld, "nothing was withheld, so the rules would measure nothing"
|
||||||
|
assert considered == len(okf_consume.enumerate_concepts(FIXTURE))
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_verdict_concept_is_withheld_by_rule_and_reaches_no_excerpt() -> None:
|
||||||
|
delivered, withheld, _ = _cut_fixture()
|
||||||
|
rules = dict(withheld)
|
||||||
|
assert rules["dyp/nivaa/alminnelig-notat"] == "verdict_layer_excluded"
|
||||||
|
assert all(excerpt["concept_id"] != "dyp/nivaa/alminnelig-notat" for excerpt in delivered)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_verdict_exclusion_is_a_type_check_and_never_a_path_filter() -> None:
|
||||||
|
# A `type: reference` file NAMED `verdict-lookalike` is delivered; a
|
||||||
|
# `type: verdict` file under an ordinary name at depth 3 is withheld. A path
|
||||||
|
# filter gets both backwards.
|
||||||
|
delivered, withheld, _ = _cut_fixture()
|
||||||
|
delivered_ids = {excerpt["concept_id"] for excerpt in delivered}
|
||||||
|
rules = dict(withheld)
|
||||||
|
assert "krav/verdict-lookalike" in delivered_ids
|
||||||
|
assert "dyp/nivaa/alminnelig-notat" not in delivered_ids
|
||||||
|
assert rules["dyp/nivaa/alminnelig-notat"] == "verdict_layer_excluded"
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_capital_l_log_type_does_not_crash_the_reader() -> None:
|
||||||
|
# `type: Log` really occurs in the K2 corpus. Case handling is a test here
|
||||||
|
# rather than an accident.
|
||||||
|
delivered, withheld, _ = _cut_fixture()
|
||||||
|
seen = {excerpt["concept_id"] for excerpt in delivered} | {cid for cid, _ in withheld}
|
||||||
|
assert "krav/loggnotat" in seen
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_concept_whose_verified_cannot_be_read_is_withheld_by_name() -> None:
|
||||||
|
# SS 6.2 requires a tier on every excerpt and SS 6.4 forbids reading absence
|
||||||
|
# as negation. Emitting `unverified` for an unreadable value asserts a fact
|
||||||
|
# nobody measured.
|
||||||
|
_, withheld, _ = _cut_fixture()
|
||||||
|
assert dict(withheld)["dyp/nivaa/blokkform-verifisert"] == "verified_unreadable"
|
||||||
|
|
||||||
|
|
||||||
|
def test_delivered_and_withheld_partition_the_considered_set() -> None:
|
||||||
|
delivered, withheld, considered = _cut_fixture()
|
||||||
|
delivered_ids = {excerpt["concept_id"] for excerpt in delivered}
|
||||||
|
withheld_ids = {concept_id for concept_id, _ in withheld}
|
||||||
|
assert delivered_ids & withheld_ids == set()
|
||||||
|
assert len(delivered_ids) + len(withheld_ids) == considered
|
||||||
|
assert delivered_ids | withheld_ids == set(okf_consume.enumerate_concepts(FIXTURE))
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_withheld_entry_names_a_rule_from_the_closed_set() -> None:
|
||||||
|
_, withheld, _ = _cut_fixture()
|
||||||
|
assert {rule for _, rule in withheld} <= set(okf_consume.WITHHOLDING_RULES)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_concept_larger_than_the_limit_is_excluded_by_name_before_the_dp() -> None:
|
||||||
|
# Named as a RULE rather than left as a packing artefact: "it did not fit"
|
||||||
|
# and "it could never fit" are different facts about the cut.
|
||||||
|
_, withheld, _ = _cut_fixture(limit=200)
|
||||||
|
rules = {rule for _, rule in withheld}
|
||||||
|
assert "over_budget_alone" in rules
|
||||||
|
assert "over_budget_after_knapsack" not in rules
|
||||||
|
|
||||||
|
|
||||||
|
def test_concepts_ranked_beyond_k_are_withheld_as_below_k() -> None:
|
||||||
|
_, withheld, _ = _cut_fixture(k=1)
|
||||||
|
assert "below_k" in {rule for _, rule in withheld}
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_exact_knapsack_beats_greedy_by_density() -> None:
|
||||||
|
# Greedy takes the densest item first and is then unable to fit either of
|
||||||
|
# the two that together are worth more. Greedy-by-density has an unbounded
|
||||||
|
# approximation factor; an exact DP over at most `k` items is microseconds.
|
||||||
|
items = ((10.0, 6), (7.0, 5), (7.0, 5))
|
||||||
|
chosen = okf_consume.knapsack(items, capacity=10)
|
||||||
|
assert sorted(chosen) == [1, 2]
|
||||||
|
assert sum(items[index][0] for index in chosen) == 14.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_knapsack_is_deterministic_over_equal_value_subsets() -> None:
|
||||||
|
items = ((5.0, 5), (5.0, 5), (5.0, 5))
|
||||||
|
assert okf_consume.knapsack(items, capacity=10) == okf_consume.knapsack(items, capacity=10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_excerpts_come_back_in_rank_order_and_carry_that_rank() -> None:
|
||||||
|
# An id-sorted payload would turn "position in the payload" into a
|
||||||
|
# different number from "position in the ranking", and hit@k reads the
|
||||||
|
# second one.
|
||||||
|
delivered, _, _ = _cut_fixture()
|
||||||
|
assert [excerpt["rank"] for excerpt in delivered] == list(range(1, len(delivered) + 1))
|
||||||
|
assert delivered[0]["concept_id"] == "krav/prissammenstilling"
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,11 @@ from llm_ingestion_okf.inbox import ( # noqa: E402
|
||||||
ADJUDICATION_STATES,
|
ADJUDICATION_STATES,
|
||||||
)
|
)
|
||||||
from llm_ingestion_okf.materialize import parse_frontmatter # noqa: E402
|
from llm_ingestion_okf.materialize import parse_frontmatter # noqa: E402
|
||||||
from llm_ingestion_okf.profiles import SEGMENTED_OKF_V0_2, BundleProfile # noqa: E402
|
from llm_ingestion_okf.profiles import ( # noqa: E402
|
||||||
|
RESERVED_OKF_TYPE,
|
||||||
|
SEGMENTED_OKF_V0_2,
|
||||||
|
BundleProfile,
|
||||||
|
)
|
||||||
|
|
||||||
#: The profile whose index policy reads a faceted, per-directory index -- the
|
#: The profile whose index policy reads a faceted, per-directory index -- the
|
||||||
#: shape both the in-repo golden bundle and the K2 corpus carry. Named as this
|
#: shape both the in-repo golden bundle and the K2 corpus carry. Named as this
|
||||||
|
|
@ -638,3 +642,149 @@ def concept_scores(
|
||||||
by_id = {concept.concept_id: concept for concept in concepts}
|
by_id = {concept.concept_id: concept for concept in concepts}
|
||||||
ranked_ids = sorted(fused, key=lambda key: (-fused[key], key))
|
ranked_ids = sorted(fused, key=lambda key: (-fused[key], key))
|
||||||
return [(by_id[concept_id], fused[concept_id]) for concept_id in ranked_ids]
|
return [(by_id[concept_id], fused[concept_id]) for concept_id in ranked_ids]
|
||||||
|
|
||||||
|
|
||||||
|
# --- The cut (SS 5, SS 7, SS 9.1) ---------------------------------------------
|
||||||
|
|
||||||
|
#: Every rule this instrument may drop a concept under, spelled once. CLOSED:
|
||||||
|
#: a drop with no rule is the silent cut SS 5.3 exists to forbid, and a rule
|
||||||
|
#: invented at the drop site is a vocabulary no consumer can be held to.
|
||||||
|
WITHHOLDING_RULES = (
|
||||||
|
"verdict_layer_excluded",
|
||||||
|
"verified_unreadable",
|
||||||
|
"over_budget_alone",
|
||||||
|
"below_k",
|
||||||
|
"over_budget_after_knapsack",
|
||||||
|
)
|
||||||
|
|
||||||
|
#: The knapsack's weight granularity, in bytes. Bucketing keeps the DP table
|
||||||
|
#: small; bucketing UP the item and DOWN the capacity keeps the error one-sided,
|
||||||
|
#: so the pack may under-deliver by a bucket and can never over-spend.
|
||||||
|
WEIGHT_BUCKET = 500
|
||||||
|
|
||||||
|
|
||||||
|
def excerpt_for(concept: Concept) -> dict[str, object] | None:
|
||||||
|
"""One concept as a payload excerpt, or `None` when it cannot be tiered.
|
||||||
|
|
||||||
|
Carries the SS 8 members plus three the contract permits and this profile
|
||||||
|
needs:
|
||||||
|
|
||||||
|
- **`text`** -- the concept body. SS 8 names no content member, but SS 1
|
||||||
|
defines an excerpt as "one delivered unit of bundle CONTENT", and without
|
||||||
|
a body the budget gate would measure a two-kilobyte skeleton while the
|
||||||
|
skill went to the bundle itself, breaking SS 2.2.
|
||||||
|
- **`text_sha256`** -- the digest of the delivered bytes. `sha256` is the
|
||||||
|
digest of the WHOLE concept file (SS 3.2), so without this second digest a
|
||||||
|
consumer holds an identity that cannot verify what it was handed.
|
||||||
|
- **`bundle_id_inherited`** -- whether the first half of the SS 3.1 identity
|
||||||
|
tuple came from the concept or from the root index.
|
||||||
|
|
||||||
|
Trailing whitespace is stripped per line: a spreadsheet render is padded to
|
||||||
|
hundreds of trailing spaces per line, and unstripped, most of a budget goes
|
||||||
|
on padding.
|
||||||
|
"""
|
||||||
|
tier = trust_tier(concept.frontmatter.get("verified"))
|
||||||
|
if tier is None:
|
||||||
|
return None
|
||||||
|
text = "\n".join(
|
||||||
|
line.rstrip() for line in unicodedata.normalize("NFC", concept.body).split("\n")
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"bundle_id": concept.bundle_id,
|
||||||
|
"concept_id": concept.concept_id,
|
||||||
|
"sha256": concept.sha256,
|
||||||
|
"adjudication": concept.adjudication,
|
||||||
|
"trust_tier": tier,
|
||||||
|
"bundle_id_inherited": concept.bundle_id_inherited,
|
||||||
|
"text_sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),
|
||||||
|
"text": text,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def excerpt_weight(excerpt: Mapping[str, object]) -> int:
|
||||||
|
"""What this excerpt costs by the gate's own instrument.
|
||||||
|
|
||||||
|
The ENCODED JSON object, never `stat().st_size`: measured, the two differ by
|
||||||
|
7.1 % over the K2 corpus, and a cut computed as fitting would then be
|
||||||
|
refused by a gate measuring different bytes.
|
||||||
|
"""
|
||||||
|
return len(json.dumps(excerpt, ensure_ascii=False).encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def knapsack(items: Sequence[tuple[float, int]], *, capacity: int) -> tuple[int, ...]:
|
||||||
|
"""The exact 0/1 knapsack: indices of the highest-value subset that fits.
|
||||||
|
|
||||||
|
Exact rather than greedy-by-density, which has an unbounded approximation
|
||||||
|
factor -- and over at most `k` items the DP is microseconds, so the
|
||||||
|
approximation buys nothing. Deterministic: a subset replaces the incumbent
|
||||||
|
only on a STRICT improvement, so equal-value subsets resolve to the one
|
||||||
|
built from earlier items, and the caller sorts the pool by `concept_id`
|
||||||
|
first.
|
||||||
|
"""
|
||||||
|
best: list[tuple[float, tuple[int, ...]]] = [(0.0, ()) for _ in range(capacity + 1)]
|
||||||
|
for index, (value, weight) in enumerate(items):
|
||||||
|
if weight > capacity:
|
||||||
|
continue
|
||||||
|
for room in range(capacity, weight - 1, -1):
|
||||||
|
candidate_value = best[room - weight][0] + value
|
||||||
|
if candidate_value > best[room][0]:
|
||||||
|
best[room] = (candidate_value, (*best[room - weight][1], index))
|
||||||
|
return max(best, key=lambda entry: entry[0])[1]
|
||||||
|
|
||||||
|
|
||||||
|
def cut(
|
||||||
|
ranked: Sequence[tuple[Concept, float]], *, k: int, limit: int
|
||||||
|
) -> tuple[tuple[dict[str, object], ...], tuple[tuple[str, str], ...]]:
|
||||||
|
"""The ranked concepts split into delivered excerpts and named drops.
|
||||||
|
|
||||||
|
**The partition is the invariant, not a consequence.** Every considered
|
||||||
|
concept lands in exactly one of the two, so `considered == withheld +
|
||||||
|
delivered` closes by construction rather than by a count computed twice.
|
||||||
|
|
||||||
|
Exclusions run before the pack, each naming its rule, because "it did not
|
||||||
|
fit" and "it could never be delivered" are different facts about the cut.
|
||||||
|
"""
|
||||||
|
withheld: list[tuple[str, str]] = []
|
||||||
|
candidates: list[tuple[Concept, float, dict[str, object], int]] = []
|
||||||
|
for concept, score in ranked:
|
||||||
|
# SS 9.1: a TYPE check at every level, never a path filter. Case-folded
|
||||||
|
# against the profile's own constant, because `type: Log` (capital L)
|
||||||
|
# really occurs in the corpus.
|
||||||
|
if concept.okf_type.casefold() == RESERVED_OKF_TYPE:
|
||||||
|
withheld.append((concept.concept_id, "verdict_layer_excluded"))
|
||||||
|
continue
|
||||||
|
excerpt = excerpt_for(concept)
|
||||||
|
if excerpt is None:
|
||||||
|
withheld.append((concept.concept_id, "verified_unreadable"))
|
||||||
|
continue
|
||||||
|
weight = excerpt_weight(excerpt)
|
||||||
|
if weight > limit:
|
||||||
|
withheld.append((concept.concept_id, "over_budget_alone"))
|
||||||
|
continue
|
||||||
|
candidates.append((concept, score, excerpt, weight))
|
||||||
|
for concept, _, _, _ in candidates[k:]:
|
||||||
|
withheld.append((concept.concept_id, "below_k"))
|
||||||
|
shortlist = candidates[:k]
|
||||||
|
# The DP POOL is sorted by `concept_id`, so which of two equal-value subsets
|
||||||
|
# wins is a property of the input set rather than of the order the ranker
|
||||||
|
# happened to emit. The OUTPUT is not: excerpts come back in fused-rank
|
||||||
|
# order, because the rank is what a hit@k measurement reads, and an id-sorted
|
||||||
|
# list would silently turn "position in the payload" into a different number
|
||||||
|
# from "position in the ranking".
|
||||||
|
pool = sorted(shortlist, key=lambda entry: entry[0].concept_id)
|
||||||
|
capacity = limit // WEIGHT_BUCKET
|
||||||
|
packed = {
|
||||||
|
id(pool[index][0])
|
||||||
|
for index in knapsack(
|
||||||
|
tuple((score, -(-weight // WEIGHT_BUCKET)) for _, score, _, weight in pool),
|
||||||
|
capacity=capacity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
delivered: list[dict[str, object]] = []
|
||||||
|
for concept, _, excerpt, _ in shortlist:
|
||||||
|
if id(concept) in packed:
|
||||||
|
delivered.append({**excerpt, "rank": len(delivered) + 1})
|
||||||
|
else:
|
||||||
|
withheld.append((concept.concept_id, "over_budget_after_knapsack"))
|
||||||
|
withheld.sort()
|
||||||
|
return tuple(delivered), tuple(withheld)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue