Skip to content

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

Open
plind-junior wants to merge 2 commits into
testfrom
feat/ingest-selection
Open

feat(extract): ingest selection knob + keep dotted numbers intact#541
plind-junior wants to merge 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
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