Skip to content

feat(extract): ingest selection knob + keep dotted numbers intact - #541

Merged
plind-junior merged 2 commits into
testfrom
feat/ingest-selection
Jul 29, 2026
Merged

feat(extract): ingest selection knob + keep dotted numbers intact#541
plind-junior merged 2 commits into
testfrom
feat/ingest-selection

Conversation

@plind-junior

@plind-junior plind-junior commented Jul 22, 2026

Copy link
Copy Markdown
Member

the compiler pivot's go/no-go is beat-grep: a compiled bundle has to win on
accuracy-per-token, and it can't while ingest restates the whole document.
this lands the ingest-side machinery for that — a selection knob and an
extraction fix — and reports what measuring it on a real corpus actually says.

selection knob. extract.select_spans ranks candidate spans by information
density (sum over distinct content words of 1/document-frequency) and keeps the
best under a claim-count or character budget, threaded through
extract_receipt_claims + ingest_source and surfaced as vouch ingest --max-claims / --budget-chars. it is deterministic and llm-free, and — the
load-bearing property — it only ever returns a subset of the verbatim spans,
never a paraphrase, so every kept claim's receipt still verifies by
construction. selection cannot fabricate; it can only choose which real quotes
to keep. unset, ingest keeps every span exactly as before.

extraction fix. segment_source split on every ., so a version or
decimal (6.8.3, 3.14) was fractured across segment boundaries and its
answer atom fell out of every span — ~11% of the ground-truth facts on a
synthetic lookup corpus, lost before any budget applied. a . between digits
is now kept inside the span; the recall ceiling of the whole pipeline rises
89% → 100% on that corpus. this cap bounded any downstream compiler, not just
this one.

what the measurement says — honest result. an in-repo probe over the d6
corpus (ingest each doc, select, check how many of the 46 answer atoms survive)
shows coverage tracking fraction-kept almost linearly: keep 70% of spans → 72%
of facts, 50% → 52%, 35% → 35%. the density ranking is ~uncorrelated with
answer-relevance — it drops answer spans as readily as filler, because grep
wins by being query-conditioned and query-agnostic compression at ingest
cedes exactly that. so this PR is the receipt-safe plumbing and a real
recall-ceiling fix; the ranking is the lever that actually moves the number,
and choosing it (query/answer-aware selection vs read-time retrieval precision
vs an llm picker) is deliberately a separate step — measure early, don't
over-build. full gate green — pytest, mypy, ruff.

Summary by CodeRabbit

  • New Features

    • Added --max-claims and --budget-chars to vouch ingest for density-based span selection.
    • Ingest now keeps the original order and files only the most information-dense, verbatim, receipt-verifiable passages within the selected limits.
    • If no limits are set, behavior remains unchanged (all spans kept).
  • Bug Fixes

    • Improved segmentation so dotted numbers (e.g., 6.8.3, 3.14) aren’t split.
  • Documentation

    • Updated the unreleased changelog with the new options and behavior.

…hars)

ingest filed every substantive sentence of a source — complete, but a
restatement of the whole document rather than the facts worth a claim, and
that beats nothing on accuracy-per-token, not grep.

add extract.select_spans: rank candidate spans by information density (sum
over distinct content words of 1/document-frequency, so rare specific terms
outweigh stopword-heavy filler) and keep the best under a claim-count or
character budget. deterministic and llm-free, and it only ever returns a
subset of the verbatim spans — never a paraphrase — so every kept claim's
receipt still verifies by construction.

thread max_claims/budget_chars through extract_receipt_claims and
ingest_source, and expose vouch ingest --max-claims / --budget-chars. unset,
ingest keeps every span exactly as before (the unbudgeted baseline stays
untouched); the older positional limit used by session-answer capture is
unchanged.

this is the selection step the compiler pivot needs: fewer, denser claims
are what move the fidelity number against the grep baseline.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Ingest selection

Layer / File(s) Summary
Deterministic span selection
src/vouch/extract.py, tests/test_extract.py
Preserves dotted numeric tokens and selects information-dense spans deterministically under claim-count or character-budget bounds.
Receipt-backed extraction integration
src/vouch/extract.py, tests/test_extract.py
Applies selected spans before filing receipt-backed claims while retaining existing limit and approval behavior.
CLI exposure and validation
src/vouch/cli.py, tests/test_extract.py, CHANGELOG.md
Adds --max-claims and --budget-chars, forwards them into ingest, and documents the updated extraction behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant vouch_ingest
  participant ingest_source
  participant extract_receipt_claims
  participant select_spans
  User->>vouch_ingest: run ingest with selection bounds
  vouch_ingest->>ingest_source: pass max_claims and budget_chars
  ingest_source->>extract_receipt_claims: extract receipt-backed claims
  extract_receipt_claims->>select_spans: rank and bound candidate spans
  select_spans-->>extract_receipt_claims: return selected spans
  extract_receipt_claims-->>ingest_source: return filed claims
Loading

Possibly related PRs

  • vouchdev/vouch#518: Related segmentation changes and tests for preserving dotted numeric tokens.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures both the new ingest selection knob and the dotted-number segmentation fix.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ingest-selection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface tests tests and fixtures size: M 200-499 changed non-doc lines ci: passing ci is green labels Jul 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/vouch/extract.py`:
- Around line 47-49: Update the new comments near the filler-word definition and
the corresponding comment around line 102 to begin with lowercase prose,
preserving their existing wording and meaning.
- Around line 96-117: Reject negative non-None bounds in the shared selector in
src/vouch/extract.py#L81-L107 by raising ValueError before selection; update
src/vouch/cli.py#L1757-L1765 to use click.IntRange(min=0) for both options so
the CLI reports validation errors, and add negative-bound coverage for direct
helper calls and the command in src/vouch/extract.py#L81-L107,
src/vouch/cli.py#L1757-L1765, tests/test_extract.py#L103-L133, and
tests/test_extract.py#L166-L185.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a4480e2-fba8-48e5-a945-847b51a8453f

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9ca7c and e8dd59a.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/vouch/cli.py
  • src/vouch/extract.py
  • tests/test_extract.py

Comment thread src/vouch/extract.py
Comment thread src/vouch/extract.py
Comment on lines +96 to +117
if max_claims is None and budget_chars is None:
return segments
doc_freq: dict[str, int] = {}
for seg in segments:
for word in set(_content_words(seg)):
doc_freq[word] = doc_freq.get(word, 0) + 1
# Rank by score (desc), ties broken by original position for determinism.
ranked = sorted(
range(len(segments)),
key=lambda i: (-_span_score(segments[i], doc_freq), i),
)
kept: list[int] = []
used = 0
for i in ranked:
if max_claims is not None and len(kept) >= max_claims:
break
length = len(segments[i])
if budget_chars is not None and used + length > budget_chars:
continue
kept.append(i)
used += length
return [segments[i] for i in sorted(kept)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does the Click version declared by this repository support click.IntRange(min=0) as a click.option type?

💡 Result:

Yes, the Click library supports click.IntRange(min=0) as an option type [1]. click.IntRange is specifically designed to restrict integer input to a defined range [1][2]. When you use click.IntRange(min=0), you effectively create a parameter that accepts any integer greater than or equal to 0 [1]. If the user provides a value less than 0, Click will raise an error by default, unless the clamp=True option is also enabled, in which case the value would be clamped to the minimum boundary [1][2]. This functionality is a standard part of the Click API and is consistent across documented versions [1][2][3]. Top results: [1][2]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- src/vouch/extract.py (relevant section) ---\n'
sed -n '1,220p' src/vouch/extract.py

printf '\n--- src/vouch/cli.py (relevant option section) ---\n'
sed -n '1720,1785p' src/vouch/cli.py

printf '\n--- tests/test_extract.py (relevant tests) ---\n'
sed -n '1,260p' tests/test_extract.py

printf '\n--- search for select_spans usages ---\n'
rg -n "select_spans\(" -S src tests

Repository: vouchdev/vouch

Length of output: 243


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('src/vouch/extract.py')
print('extract.py lines', sum(1 for _ in p.open()))
p = Path('src/vouch/cli.py')
print('cli.py lines', sum(1 for _ in p.open()))
p = Path('tests/test_extract.py')
print('test_extract.py lines', sum(1 for _ in p.open()))
PY

Repository: vouchdev/vouch

Length of output: 217


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Probe runtime semantics only if click is importable in the sandbox.
python3 - <<'PY'
try:
    import click
except Exception as e:
    print(f'click import failed: {e}')
    raise SystemExit(0)

opt = click.Option(['--n'], type=click.IntRange(min=0), default=None)
ctx = click.Context(click.Command('cmd', params=[opt]))

def parse(args):
    try:
        return opt.consume_value(ctx, opt.process_value(ctx, args))
    except Exception as e:
        return type(e).__name__, str(e)

print('click', click.__version__)
print('type', type(opt.type).__name__)
for value in [None, 0, 3, -1]:
    try:
        rv = opt.type.convert(value, opt, ctx) if value is not None else None
        print(value, '=>', rv)
    except Exception as e:
        print(value, '=>', type(e).__name__, e)
PY

Repository: vouchdev/vouch

Length of output: 433


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- src/vouch/extract.py (relevant section) ---'
sed -n '70,140p' src/vouch/extract.py

echo
echo '--- src/vouch/cli.py (relevant option section) ---'
sed -n '1748,1778p' src/vouch/cli.py

echo
echo '--- tests/test_extract.py (relevant tests) ---'
sed -n '96,190p' tests/test_extract.py

echo
echo '--- direct usage search ---'
rg -n "select_spans\(" src tests -S

Repository: vouchdev/vouch

Length of output: 8672


reject negative selection budgets.

max_claims < 0 or budget_chars < 0 silently produces an empty selection, so an invalid input looks like a valid empty result. reject negative values at the shared selector and surface a cli validation error.

  • src/vouch/extract.py#L81-L107: raise ValueError for negative non-None bounds so direct callers cannot bypass the check.
  • src/vouch/cli.py#L1757-L1765: switch these options to click.IntRange(min=0) so the cli rejects invalid input up front.
  • tests/test_extract.py#L103-L133 and tests/test_extract.py#L166-L185: add negative-bound coverage for the helper and the command.
📍 Affects 3 files
  • src/vouch/extract.py#L96-L117 (this comment)
  • src/vouch/cli.py#L1757-L1765
  • tests/test_extract.py#L103-L133
  • tests/test_extract.py#L166-L185
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/extract.py` around lines 96 - 117, Reject negative non-None bounds
in the shared selector in src/vouch/extract.py#L81-L107 by raising ValueError
before selection; update src/vouch/cli.py#L1757-L1765 to use
click.IntRange(min=0) for both options so the CLI reports validation errors, and
add negative-bound coverage for direct helper calls and the command in
src/vouch/extract.py#L81-L107, src/vouch/cli.py#L1757-L1765,
tests/test_extract.py#L103-L133, and tests/test_extract.py#L166-L185.

segment_source split on every `.`, so a version or decimal — 6.8.3, 3.14 —
was fractured across segment boundaries and the number-valued answer atom fell
out of every quotable span. on a synthetic lookup corpus that dropped ~11% of
the ground-truth facts before any budget was applied: the answer simply was
not present in any claim to retrieve.

a `.` flanked by digits is a decimal/version dot, never a sentence boundary,
so the segment regex now keeps it inside the span (sentence-ending periods are
unaffected). measured recall ceiling on that corpus rises from 89% to 100% of
facts — a cap that bounded every downstream compiler, not just this one.
@plind-junior plind-junior changed the title feat(extract): density-selection knob for ingest (max-claims/budget-chars) feat(extract): ingest selection knob + keep dotted numbers intact Jul 22, 2026
@plind-junior
plind-junior merged commit be6a1d6 into test Jul 29, 2026
20 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: passing ci is green cli command line interface docs documentation, specs, examples, and repo guidance size: M 200-499 changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant