Skip to content

feat(kb): document-level learning-goals fact extraction (RCA-11)#7

Merged
ramirocantu merged 2 commits into
mainfrom
ramirocanturamirez/rca-11-atomic-fact-extractor-over-extracts-examplelecture-context
Jun 1, 2026
Merged

feat(kb): document-level learning-goals fact extraction (RCA-11)#7
ramirocantu merged 2 commits into
mainfrom
ramirocanturamirez/rca-11-atomic-fact-extractor-over-extracts-examplelecture-context

Conversation

@ramirocantu

@ramirocantu ramirocantu commented Jun 1, 2026

Copy link
Copy Markdown
Owner

What

Reworks the PDF atomic-fact extractor (pdf-vision-v1pdf-vision-v2) to stop over-extracting lecture-context noise. Closes RCA-11.

The pipeline was mechanically sound (RCA-10) but per-page extraction faithfully restated every sentence as a "fact" — no learning-goals framing, no example/meta exclusion. A 6-page physics PDF produced 124 facts, ~33 of them lecture-context noise (avg tag confidence 0.28, 81/108 manual_review).

Changes

  • Vision transcription enriched — emits verbatim text plus brief semantic descriptions of diagrams/figures/graphs, so visual facts survive the transcription bottleneck. Vision (mini) still single-tasks per page.
  • Document-level, learning-goals-driven extraction — page transcriptions are concatenated (chunked on page boundaries above _EXTRACT_MAX_DOC_CHARS) and extracted in one pass that reasons about the document's learning goals and may synthesize standalone claims. Nano single-tasks.
  • Discernment gate — each fact is classified kind ∈ {concept, context}; context (worked-example / figure / problem-restatement prose) is dropped at persist and counted in IngestReport.dropped_context_facts. No DB migration.
  • Eval harness — revives app/services/eval/ with extract_metrics.py (keyword-noise fraction, yield, before/after delta) + scripts/run_rca11_extract_eval.py (capture / replay modes) + a reference-transcription fixture for cheap deterministic re-runs.
  • EXTRACTOR_VERSIONpdf-vision-v2.

Measurement (V-L2, Phys McatKing.pdf)

metric v1 v2
total facts 124 51
keyword-noise fraction ~0.27 0.065
context dropped n/a 5

Kept facts read as durable physics knowledge. Manual review surfaced a few confidently wrong facts (e.g. electron-shell KE) and garbled formulas — a distinct failure mode (false signal, not noise) tracked in RCA-13.

Tests

mise run check green — 666 passed / 1 skipped, ruff + pyright clean. New: kind-carries, malformed-kind-defaults, doc-level-single-call, chunking, context-drop, eval metrics; reworked token-accounting + version assertions.

Notes / follow-ups

  • Dedup × version bump: content_hash excludes extractor_version and PdfSource.sha256 short-circuits re-ingest before extraction — bumping to v2 does not re-extract existing PDFs (delete + re-ingest to refresh).
  • app.services.eval removed from the deleted-surfaces fence guard (revived intentionally).
  • Spun off: RCA-12 (vision-only routing for diagram-heavy pages), RCA-13 (fact-correctness verification), RCA-14 epic (lecture view + coarser node Notion pages).
  • SPEC.md seeded (issue-scoped); §B B1 logged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added extraction-quality metrics and an evaluation/reporting harness for extraction performance
    • Document-level extraction replaces page-by-page processing; facts are classified as learning concepts (kept) or context (dropped)
    • CLI tool to capture/replay document extraction runs and emit summarized reports
    • PDF ingestion reports now include dropped-context counts and extractor version
  • Tests

    • New tests validating extraction metrics and noise heuristics
    • Updated ingest tests to verify document-level extraction and kind-based filtering
  • Chores

    • .gitignore updated to ignore SPEC.md

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e66154d2-1617-4b32-b6a5-28a4867a287f

📥 Commits

Reviewing files that changed from the base of the PR and between b3ce0d9 and b7d1089.

📒 Files selected for processing (5)
  • .gitignore
  • app/services/eval/metrics.py
  • scripts/run_rca11_extract_eval.py
  • tests/test_kb_inbox.py
  • tests/test_pdf_ingest_api.py

📝 Walkthrough

Walkthrough

This pull request implements RCA-11, a document-level PDF fact extraction redesign with quality metrics infrastructure. It replaces per-page atomic fact extraction with document-level chunked extraction, adds concept/context fact discrimination, introduces extraction and tagging quality measurement modules, and provides an evaluation CLI harness.

Changes

RCA-11 Extraction Refactor with Quality Metrics

Layer / File(s) Summary
Extraction-quality metric primitives
app/services/eval/extract_metrics.py, tests/test_eval_extract_metrics.py
Noise detection via regex patterns, fact yield counting, noise-fraction computation, run summarization splitting concept/context facts, and before-after delta comparison for extraction quality measurement.
Tagging-quality evaluation metrics
app/services/eval/metrics.py
Jaccard similarity computation, evaluation case aggregation, baseline-vs-candidate regression detection with tolerance thresholds, and JSON artifact persistence for baselines and reports.
Evaluation package initialization
app/services/eval/__init__.py
Package re-exports extraction and tagging quality utilities via __all__, establishing public API surface with grouped tagging-quality and extraction-quality items.
PDF ingestion document-level extraction
app/services/kb/pdf_ingest.py
Refactors ingestion from per-page to document-level extraction: bumps extractor version, adds ParsedFact type with text/kind tuples, extends JSON schema with kind discriminator, implements per-page vision transcription followed by document-level chunked extraction, applies kind-based persistence gating to drop context facts, and tracks dropped-context counts.
PDF ingest test updates
tests/test_kb_pdf_ingest.py
Test suite updated to mock kind field in structured extraction, validate kind parsing and defaulting, verify context-fact dropping with dropped-context counting, and add coverage for large-document page-boundary chunking.
RCA-11 evaluation CLI harness
scripts/run_rca11_extract_eval.py, tests/fixtures/rca11_reference_transcriptions.json
Standalone CLI tool supporting capture (render/transcribe/cache) and replay modes, performs document-level chunked extraction, summarizes metrics, prints kept facts for human review, and optionally emits JSON reports with separated fact lists.
Version bump and test alignment
tests/test_kb_inbox.py, tests/test_pdf_ingest_api.py, tests/test_fence_guards.py
Test expectations updated to reflect extractor_version bump from pdf-vision-v1 to pdf-vision-v2; legacy surface tracking updated to indicate app.services.eval package revival.

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(kb): document-level learning-goals fact extraction (RCA-11)' directly and accurately summarizes the main change: upgrading PDF fact extraction to document-level processing with learning-goals framing and the RCA-11 issue reference.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 ramirocanturamirez/rca-11-atomic-fact-extractor-over-extracts-examplelecture-context

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

@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: 3

🤖 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 `@app/services/eval/metrics.py`:
- Around line 121-155: In regression_blocks_pivot, guard against incomparable
runs by checking baseline.n_cases vs candidate.n_cases before computing deltas;
if they differ, return an EvalReport with blocks_pivot=True and a clear reason
like "n_cases mismatch: baseline=X candidate=Y" (populate baseline and
candidate, set mean_jaccard_delta and set_equality_delta to 0.0 or a neutral
value, and avoid applying the tolerance logic), otherwise proceed with the
existing delta/tolerance checks.

In `@scripts/run_rca11_extract_eval.py`:
- Around line 58-60: The _load_transcriptions function returns transcriptions in
fixture order which may be unstable; modify it to parse the raw list into
(int(page), str(text)) tuples and then sort that list by the integer page before
returning. Locate _load_transcriptions and ensure the returned list is sorted
(e.g., using sorted(..., key=lambda t: t[0]) or list.sort()) so downstream
chunking operates on page-ordered transcriptions.

In `@SPEC.md`:
- Around line 28-45: In SPEC.md update the fenced code block that lists files
and symbols so it includes a language identifier (e.g., change ``` to ```text)
to satisfy MD040; locate the block containing entries like
app/services/kb/pdf_ingest.py, EXTRACTOR_VERSION, ingest_pdf,
FactExtraction.facts and add the language tag at the opening fence.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a8ddb19-7c76-44fa-a1d2-22a2ecc280f3

📥 Commits

Reviewing files that changed from the base of the PR and between 13b6a76 and 7d0bc15.

📒 Files selected for processing (12)
  • SPEC.md
  • app/services/eval/__init__.py
  • app/services/eval/extract_metrics.py
  • app/services/eval/metrics.py
  • app/services/kb/pdf_ingest.py
  • scripts/run_rca11_extract_eval.py
  • tests/fixtures/rca11_reference_transcriptions.json
  • tests/test_eval_extract_metrics.py
  • tests/test_fence_guards.py
  • tests/test_kb_inbox.py
  • tests/test_kb_pdf_ingest.py
  • tests/test_pdf_ingest_api.py

Comment thread app/services/eval/metrics.py
Comment thread scripts/run_rca11_extract_eval.py Outdated
Comment thread SPEC.md Outdated
Rework the PDF atomic-fact extractor (pdf-vision-v1 -> v2) to stop
over-extracting lecture-context noise.

- Vision transcription enriched to describe diagrams/figures/graphs so
  visual facts survive the transcription bottleneck.
- Extraction is now document-level + learning-goals-driven synthesis
  with a concept/context discernment gate; context facts are dropped at
  persist and counted (IngestReport.dropped_context_facts).
- Add app/services/eval extraction-quality harness + extract_metrics +
  scripts/run_rca11_extract_eval.py; reference transcription fixture.
- Revive app/services/eval (removed from the deleted-surfaces fence).

Measured v1->v2 on Phys McatKing.pdf: 124 -> 51 facts, keyword-noise
fraction 0.27 -> 0.065. mise run check green (666 passed).

SPEC.md seeded (issue-scoped, B1 logged). Spun off RCA-12/13/14.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ramirocantu
ramirocantu force-pushed the ramirocanturamirez/rca-11-atomic-fact-extractor-over-extracts-examplelecture-context branch from 7d0bc15 to b3ce0d9 Compare June 1, 2026 03:23

@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: 3

♻️ Duplicate comments (1)
app/services/eval/metrics.py (1)

121-156: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against non-comparable runs before applying tolerance gate.

Lines 133-134 compute deltas even when baseline.n_cases and candidate.n_cases differ, which can produce incorrect blocks_pivot decisions from incomparable samples. Add a fail-closed mismatch check first.

🛡️ Proposed fix to add n_cases guard
 def regression_blocks_pivot(
     baseline: EvalRunResult,
     candidate: EvalRunResult,
     *,
     mean_jaccard_tolerance: float = MEAN_JACCARD_TOLERANCE,
     set_equality_tolerance: float = SET_EQUALITY_TOLERANCE,
 ) -> EvalReport:
     """V-L2: candidate must not drop more than the tolerance on either metric.
 
     Returns an `EvalReport` with `blocks_pivot=True` and a human-readable
     `reason` when either delta exceeds the tolerance.
     """
     mean_delta = candidate.mean_jaccard - baseline.mean_jaccard
     set_delta = candidate.set_equality_rate - baseline.set_equality_rate
+
+    if baseline.n_cases != candidate.n_cases:
+        return EvalReport(
+            baseline=baseline,
+            candidate=candidate,
+            mean_jaccard_delta=mean_delta,
+            set_equality_delta=set_delta,
+            blocks_pivot=True,
+            reason=(
+                f"n_cases mismatch: baseline={baseline.n_cases}, "
+                f"candidate={candidate.n_cases}"
+            ),
+        )
 
     reasons: list[str] = []
🤖 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 `@app/services/eval/metrics.py` around lines 121 - 156, In
regression_blocks_pivot, guard against comparing runs with different sample
sizes by checking baseline.n_cases vs candidate.n_cases before computing
mean_jaccard_delta and set_equality_delta; if they differ, return an EvalReport
(using the existing EvalReport constructor) with baseline and candidate,
blocks_pivot=True and a clear reason like "n_cases mismatch: baseline X vs
candidate Y" (you can set the delta fields to 0.0 or None consistently with
other callers), otherwise proceed with the existing delta/tolerance logic.
🤖 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 `@app/services/eval/metrics.py`:
- Around line 87-96: The function jaccard contains an unreachable check after
computing union = a | b; remove the dead check "if not union: return 1.0" (and
its comment) because the prior guard "if not a and not b: return 1.0" guarantees
union cannot be empty; update the function to compute and return len(a & b) /
len(union) after the initial empty-both guard (keep the docstring and signature
for jaccard(a: frozenset[str], b: frozenset[str]) -> float).

In `@tests/test_kb_inbox.py`:
- Line 72: The test currently asserts a hard-coded extractor version string via
assert facts[0].extractor_version == "pdf-vision-v2"; replace the literal with
the shared EXTRACTOR_VERSION constant so tests follow the single source of
truth—locate the assertion in tests/test_kb_inbox.py and change it to compare
against EXTRACTOR_VERSION (ensure the test imports or references the
EXTRACTOR_VERSION constant from the module where it’s defined).

In `@tests/test_pdf_ingest_api.py`:
- Line 67: Replace the hard-coded "pdf-vision-v2" in the assertion that checks
body["extractor_version"] with the shared extractor version constant (e.g.,
EXTRACTOR_VERSION) so tests follow version bumps automatically; import that
constant into tests/test_pdf_ingest_api.py from the module that defines it (the
shared extractor constants module used by the production code) and use
EXTRACTOR_VERSION in the assert instead of the literal string.

---

Duplicate comments:
In `@app/services/eval/metrics.py`:
- Around line 121-156: In regression_blocks_pivot, guard against comparing runs
with different sample sizes by checking baseline.n_cases vs candidate.n_cases
before computing mean_jaccard_delta and set_equality_delta; if they differ,
return an EvalReport (using the existing EvalReport constructor) with baseline
and candidate, blocks_pivot=True and a clear reason like "n_cases mismatch:
baseline X vs candidate Y" (you can set the delta fields to 0.0 or None
consistently with other callers), otherwise proceed with the existing
delta/tolerance logic.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c9e160eb-b7d7-4090-8f3e-18d1b24e0faa

📥 Commits

Reviewing files that changed from the base of the PR and between 7d0bc15 and b3ce0d9.

📒 Files selected for processing (11)
  • app/services/eval/__init__.py
  • app/services/eval/extract_metrics.py
  • app/services/eval/metrics.py
  • app/services/kb/pdf_ingest.py
  • scripts/run_rca11_extract_eval.py
  • tests/fixtures/rca11_reference_transcriptions.json
  • tests/test_eval_extract_metrics.py
  • tests/test_fence_guards.py
  • tests/test_kb_inbox.py
  • tests/test_kb_pdf_ingest.py
  • tests/test_pdf_ingest_api.py

Comment thread app/services/eval/metrics.py
Comment thread tests/test_kb_inbox.py Outdated
Comment thread tests/test_pdf_ingest_api.py Outdated
- metrics.py: fail-closed n_cases mismatch guard in regression_blocks_pivot
- metrics.py: drop unreachable empty-union check in jaccard
- run_rca11_extract_eval.py: sort transcriptions by page in loader
- tests: use shared EXTRACTOR_VERSION constant instead of literal
- SPEC.md: add language tag to §I fenced block (MD040)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ramirocantu
ramirocantu merged commit 4378dfe into main Jun 1, 2026
26 checks passed
@ramirocantu
ramirocantu deleted the ramirocanturamirez/rca-11-atomic-fact-extractor-over-extracts-examplelecture-context branch June 1, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant