{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": [
     "field-notebook-cover",
     "field-notebook-style"
    ]
   },
   "source": [
    "<style>\n",
    "/*\n",
    " * The Evidence Path\n",
    " * A restrained, dependency-free reading layer for Jupyter and the web.\n",
    " */\n",
    "\n",
    ":root {\n",
    "  --fn-paper: #ffffff;\n",
    "  --fn-ink: #111111;\n",
    "  --fn-ink-soft: #666666;\n",
    "  --fn-intro: #555555;\n",
    "  --fn-rule: #dddddd;\n",
    "  --fn-rule-strong: #111111;\n",
    "  --fn-code-paper: #f7f7f7;\n",
    "  --fn-code-ink: #191919;\n",
    "  --fn-code-leading: 1.5;\n",
    "  --fn-line-number: #888888;\n",
    "  --fn-line-number-width: 2.5rem;\n",
    "  --fn-code-gutter: 3.5rem;\n",
    "  --fn-syntax-name: #191919;\n",
    "  --fn-syntax-keyword: #5a3b73;\n",
    "  --fn-syntax-builtin: #315f75;\n",
    "  --fn-syntax-string: #2f6b43;\n",
    "  --fn-syntax-number: #8a4d20;\n",
    "  --fn-syntax-comment: #777777;\n",
    "  --fn-syntax-operator: #555555;\n",
    "  --fn-syntax-decorator: #7a4e20;\n",
    "  --fn-page-width: 42rem;\n",
    "  --fn-wide-width: 64rem;\n",
    "  --fn-measure: 38rem;\n",
    "  --fn-cell-top: 0;\n",
    "  --fn-cell-bottom: 0;\n",
    "  --fn-ui: Arial, Helvetica, sans-serif;\n",
    "  --fn-prose: Georgia, \"Times New Roman\", serif;\n",
    "  --fn-mono: ui-monospace, \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo,\n",
    "    monospace;\n",
    "}\n",
    "\n",
    "html {\n",
    "  color: var(--fn-ink);\n",
    "  background: var(--fn-paper);\n",
    "}\n",
    "\n",
    ".jp-Notebook,\n",
    ".jp-NotebookPanel-notebook,\n",
    ".notebook_app .container,\n",
    ".notebook-container {\n",
    "  color: var(--fn-ink) !important;\n",
    "  background: var(--fn-paper) !important;\n",
    "}\n",
    "\n",
    ".jp-Notebook .jp-Cell,\n",
    ".cell {\n",
    "  box-sizing: border-box;\n",
    "  position: relative;\n",
    "  max-width: var(--fn-page-width);\n",
    "  margin: 0 auto !important;\n",
    "  border: 0 !important;\n",
    "  box-shadow: none !important;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon,\n",
    ".text_cell_render {\n",
    "  box-sizing: border-box;\n",
    "  color: var(--fn-ink) !important;\n",
    "  background: var(--fn-paper) !important;\n",
    "  border: 0 !important;\n",
    "  font-family: var(--fn-prose) !important;\n",
    "  font-size: 1.08rem;\n",
    "  line-height: 1.64;\n",
    "  padding: var(--fn-cell-top) 0 var(--fn-cell-bottom) !important;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon p,\n",
    ".text_cell_render p {\n",
    "  max-width: var(--fn-measure);\n",
    "  margin: 0 0 1.35rem;\n",
    "  text-wrap: pretty;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon h1,\n",
    ".jp-RenderedHTMLCommon h2,\n",
    ".jp-RenderedHTMLCommon h3,\n",
    ".jp-RenderedHTMLCommon h4,\n",
    ".text_cell_render h1,\n",
    ".text_cell_render h2,\n",
    ".text_cell_render h3,\n",
    ".text_cell_render h4 {\n",
    "  color: var(--fn-ink) !important;\n",
    "  border: 0 !important;\n",
    "  font-family: var(--fn-ui) !important;\n",
    "  font-weight: 700;\n",
    "  text-wrap: balance;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon h1,\n",
    ".text_cell_render h1 {\n",
    "  font-size: clamp(2rem, 7vw, 3.5rem);\n",
    "  letter-spacing: -0.04em;\n",
    "  line-height: 1.08;\n",
    "  margin: 0 0 1rem;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon h2,\n",
    ".text_cell_render h2 {\n",
    "  font-size: 1.65rem;\n",
    "  letter-spacing: -0.025em;\n",
    "  line-height: 1.2;\n",
    "  margin: 3.25rem 0 1rem;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon h3,\n",
    ".text_cell_render h3 {\n",
    "  font-size: 1.2rem;\n",
    "  letter-spacing: -0.015em;\n",
    "  line-height: 1.3;\n",
    "  margin: 2.25rem 0 0.75rem;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon h4,\n",
    ".text_cell_render h4 {\n",
    "  font-size: 1rem;\n",
    "  letter-spacing: 0;\n",
    "  line-height: 1.35;\n",
    "  margin: 1.75rem 0 0.65rem;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon > :first-child,\n",
    ".text_cell_render > :first-child {\n",
    "  margin-top: 0;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon a,\n",
    ".text_cell_render a {\n",
    "  color: inherit !important;\n",
    "  text-decoration: underline;\n",
    "  text-decoration-thickness: 1px;\n",
    "  text-underline-offset: 0.18em;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon a:hover,\n",
    ".text_cell_render a:hover {\n",
    "  text-decoration-thickness: 2px;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon a:focus-visible,\n",
    ".text_cell_render a:focus-visible {\n",
    "  outline: 2px solid currentColor;\n",
    "  outline-offset: 3px;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon strong,\n",
    ".text_cell_render strong {\n",
    "  color: var(--fn-ink);\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon blockquote,\n",
    ".text_cell_render blockquote {\n",
    "  max-width: 36rem;\n",
    "  margin: 1.75rem 0;\n",
    "  padding: 0 0 0 1.25rem;\n",
    "  border-left: 2px solid var(--fn-rule-strong) !important;\n",
    "  color: #444444 !important;\n",
    "  font-style: italic;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon blockquote p:last-child,\n",
    ".text_cell_render blockquote p:last-child {\n",
    "  margin-bottom: 0;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon ul,\n",
    ".jp-RenderedHTMLCommon ol,\n",
    ".text_cell_render ul,\n",
    ".text_cell_render ol {\n",
    "  max-width: var(--fn-measure);\n",
    "  margin: 0 0 1.5rem;\n",
    "  padding-left: 1.5rem;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon li,\n",
    ".text_cell_render li {\n",
    "  margin: 0.25rem 0;\n",
    "  padding-left: 0.15rem;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon table,\n",
    ".text_cell_render table {\n",
    "  display: table;\n",
    "  width: 100%;\n",
    "  margin: 1.75rem 0 2rem;\n",
    "  border-collapse: collapse;\n",
    "  border-spacing: 0;\n",
    "  color: var(--fn-ink);\n",
    "  background: transparent;\n",
    "  font-family: var(--fn-ui);\n",
    "  font-size: 0.88rem;\n",
    "  line-height: 1.45;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon th,\n",
    ".jp-RenderedHTMLCommon td,\n",
    ".text_cell_render th,\n",
    ".text_cell_render td {\n",
    "  padding: 0.65rem 0.5rem !important;\n",
    "  border: 0 !important;\n",
    "  border-bottom: 1px solid var(--fn-rule) !important;\n",
    "  text-align: left !important;\n",
    "  vertical-align: top !important;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon th,\n",
    ".text_cell_render th {\n",
    "  border-bottom-color: var(--fn-rule-strong) !important;\n",
    "  font-weight: 700 !important;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon code,\n",
    ".text_cell_render code {\n",
    "  color: var(--fn-code-ink) !important;\n",
    "  background: transparent !important;\n",
    "  border: 0;\n",
    "  font-family: var(--fn-mono) !important;\n",
    "  font-size: 0.85em;\n",
    "  padding: 0;\n",
    "}\n",
    "\n",
    ".jp-RenderedHTMLCommon pre,\n",
    ".text_cell_render pre {\n",
    "  box-sizing: border-box;\n",
    "  max-width: 100%;\n",
    "  margin: 1.5rem 0 2rem;\n",
    "  overflow-x: auto;\n",
    "  color: var(--fn-code-ink);\n",
    "  background: var(--fn-code-paper);\n",
    "  border: 1px solid var(--fn-rule);\n",
    "  font-family: var(--fn-mono);\n",
    "  font-size: 0.8rem;\n",
    "  line-height: var(--fn-code-leading);\n",
    "  padding: 1rem;\n",
    "  tab-size: 4;\n",
    "  white-space: pre;\n",
    "}\n",
    "\n",
    ".jp-CodeCell,\n",
    ".code_cell,\n",
    ".workbench-note,\n",
    ".bench-insert {\n",
    "  box-sizing: border-box;\n",
    "  width: 100%;\n",
    "  max-width: var(--fn-page-width) !important;\n",
    "  margin: 1.75rem auto 2rem !important;\n",
    "  padding: 0 !important;\n",
    "  transform: none;\n",
    "  background: var(--fn-paper) !important;\n",
    "  border: 0 !important;\n",
    "}\n",
    "\n",
    ".jp-CodeCell .jp-InputArea,\n",
    ".code_cell .input_area {\n",
    "  box-sizing: border-box;\n",
    "  max-width: 100%;\n",
    "  overflow-x: auto;\n",
    "  background: var(--fn-code-paper) !important;\n",
    "  border: 1px solid var(--fn-rule) !important;\n",
    "}\n",
    "\n",
    ".jp-CodeCell .jp-InputArea pre,\n",
    ".code_cell .input_area pre,\n",
    ".jp-CodeCell .jp-InputArea code,\n",
    ".code_cell .input_area code {\n",
    "  margin: 0 !important;\n",
    "  color: var(--fn-code-ink) !important;\n",
    "  background: transparent !important;\n",
    "  border: 0 !important;\n",
    "  font-family: var(--fn-mono) !important;\n",
    "  font-size: 0.8rem !important;\n",
    "  line-height: var(--fn-code-leading) !important;\n",
    "  padding: 1rem !important;\n",
    "  white-space: pre !important;\n",
    "}\n",
    "\n",
    ".jp-OutputArea,\n",
    ".output_area,\n",
    ".output-slip {\n",
    "  box-sizing: border-box;\n",
    "  max-width: 100%;\n",
    "  margin: 0.85rem 0 0 !important;\n",
    "  padding: 0.15rem 0 0.15rem 1rem !important;\n",
    "  overflow-x: auto;\n",
    "  color: var(--fn-code-ink) !important;\n",
    "  background: transparent !important;\n",
    "  border: 0 !important;\n",
    "  border-left: 2px solid var(--fn-rule) !important;\n",
    "}\n",
    "\n",
    ".jp-OutputArea pre,\n",
    ".output_area pre,\n",
    ".output-slip pre {\n",
    "  margin: 0.45rem 0 0 !important;\n",
    "  overflow-x: auto;\n",
    "  color: var(--fn-code-ink) !important;\n",
    "  background: transparent !important;\n",
    "  border: 0 !important;\n",
    "  font-family: var(--fn-mono) !important;\n",
    "  font-size: 0.8rem !important;\n",
    "  line-height: 1.5 !important;\n",
    "  padding: 0 !important;\n",
    "  white-space: pre-wrap;\n",
    "}\n",
    "\n",
    ".field-cover {\n",
    "  margin: 0 0 2rem;\n",
    "  padding: 2rem 0 1.75rem;\n",
    "  border-bottom: 1px solid var(--fn-rule-strong);\n",
    "}\n",
    "\n",
    ".cover-kicker,\n",
    ".folio-kicker {\n",
    "  color: var(--fn-ink-soft);\n",
    "  font-family: var(--fn-ui);\n",
    "  font-size: 0.82rem;\n",
    "  letter-spacing: 0;\n",
    "  line-height: 1.4;\n",
    "}\n",
    "\n",
    ".cover-kicker {\n",
    "  display: none;\n",
    "}\n",
    "\n",
    ".cover-subtitle {\n",
    "  max-width: 36rem;\n",
    "  margin: 0 0 1rem;\n",
    "  color: var(--fn-intro);\n",
    "  font-family: var(--fn-prose);\n",
    "  font-size: 1.25rem;\n",
    "  line-height: 1.5;\n",
    "}\n",
    "\n",
    ".cover-scribble {\n",
    "  display: none;\n",
    "}\n",
    "\n",
    ".folio-opener,\n",
    ".experiment-opener {\n",
    "  margin: 3.75rem 0 1.75rem;\n",
    "  padding: 1.5rem 0 0;\n",
    "  border-top: 2px solid var(--fn-rule-strong);\n",
    "}\n",
    "\n",
    ".folio-opener .folio-kicker,\n",
    ".experiment-opener .folio-kicker {\n",
    "  display: none;\n",
    "}\n",
    "\n",
    ".folio-opener h1,\n",
    ".experiment-opener h1 {\n",
    "  font-size: clamp(2rem, 6vw, 3rem) !important;\n",
    "  margin-bottom: 0.65rem !important;\n",
    "}\n",
    "\n",
    ".folio-opener > p,\n",
    ".experiment-opener > p {\n",
    "  color: var(--fn-intro);\n",
    "  font-size: 1.12rem;\n",
    "  margin-bottom: 1rem;\n",
    "}\n",
    "\n",
    ".opening-question,\n",
    ".field-question {\n",
    "  max-width: 36rem;\n",
    "  margin: 1.25rem 0 1.75rem;\n",
    "  color: #444444;\n",
    "  font-family: var(--fn-prose);\n",
    "  font-size: 1rem;\n",
    "  font-style: italic;\n",
    "  line-height: 1.6;\n",
    "}\n",
    "\n",
    ".leaf-download,\n",
    ".source-links {\n",
    "  font-family: var(--fn-ui);\n",
    "  font-size: 0.85rem;\n",
    "}\n",
    "\n",
    ".reading-ribbon,\n",
    ".insert-legend {\n",
    "  display: none;\n",
    "}\n",
    "\n",
    ".two-page-spread,\n",
    ".notebook-grid {\n",
    "  display: grid;\n",
    "  grid-template-columns: repeat(2, minmax(0, 1fr));\n",
    "  gap: 2rem;\n",
    "  margin: 2rem 0 2.5rem;\n",
    "  padding-top: 1.25rem;\n",
    "  border-top: 1px solid var(--fn-rule);\n",
    "}\n",
    "\n",
    ".two-page-spread h3,\n",
    ".notebook-grid h3 {\n",
    "  margin-top: 0 !important;\n",
    "}\n",
    "\n",
    ".margin-note,\n",
    ".research-note,\n",
    ".warning-note,\n",
    ".provenance-note {\n",
    "  max-width: 36rem;\n",
    "  margin: 1.75rem 0;\n",
    "  padding-left: 1.25rem;\n",
    "  border-left: 2px solid var(--fn-rule);\n",
    "  color: #444444;\n",
    "  font-size: 0.95rem;\n",
    "}\n",
    "\n",
    ".margin-note p:last-child,\n",
    ".research-note p:last-child,\n",
    ".warning-note p:last-child,\n",
    ".provenance-note p:last-child {\n",
    "  margin-bottom: 0;\n",
    "}\n",
    "\n",
    ".observation,\n",
    ".experiment,\n",
    ".definition-card,\n",
    ".execution-note {\n",
    "  margin: 2rem 0;\n",
    "  padding: 1.25rem 0;\n",
    "  border-top: 1px solid var(--fn-rule);\n",
    "  border-bottom: 1px solid var(--fn-rule);\n",
    "}\n",
    "\n",
    ".observation > :last-child,\n",
    ".experiment > :last-child,\n",
    ".definition-card > :last-child,\n",
    ".execution-note > :last-child {\n",
    "  margin-bottom: 0;\n",
    "}\n",
    "\n",
    ".sketch,\n",
    ".two-page-spread,\n",
    ".evidence-strip {\n",
    "  max-width: 100%;\n",
    "}\n",
    "\n",
    ".sketch {\n",
    "  margin: 2rem 0;\n",
    "}\n",
    "\n",
    ".sketch svg,\n",
    ".sketch img {\n",
    "  display: block;\n",
    "  width: 100%;\n",
    "  height: auto;\n",
    "}\n",
    "\n",
    ".sketch svg text {\n",
    "  fill: var(--fn-ink) !important;\n",
    "  font-family: var(--fn-ui) !important;\n",
    "}\n",
    "\n",
    ".sketch svg [stroke] {\n",
    "  stroke: var(--fn-ink) !important;\n",
    "}\n",
    "\n",
    ".sketch svg [filter] {\n",
    "  filter: none !important;\n",
    "}\n",
    "\n",
    ".sketch-caption {\n",
    "  margin-top: 0.65rem;\n",
    "  color: var(--fn-ink-soft);\n",
    "  font-family: var(--fn-ui);\n",
    "  font-size: 0.8rem;\n",
    "}\n",
    "\n",
    ".chapter-bridge {\n",
    "  margin: 2.5rem 0;\n",
    "  padding-top: 1.25rem;\n",
    "  border-top: 1px solid var(--fn-rule);\n",
    "  color: #444444;\n",
    "  font-style: italic;\n",
    "}\n",
    "\n",
    ".atlas-source,\n",
    ".evidence-leaf {\n",
    "  display: block;\n",
    "  margin: 3rem 0 1rem;\n",
    "  padding-top: 1rem;\n",
    "  border-top: 1px solid var(--fn-rule-strong);\n",
    "  color: var(--fn-ink-soft);\n",
    "  font-family: var(--fn-ui);\n",
    "  font-size: 0.78rem;\n",
    "  line-height: 1.45;\n",
    "}\n",
    "\n",
    ".atlas-source + h2,\n",
    ".evidence-leaf + h2 {\n",
    "  margin-top: 1rem !important;\n",
    "}\n",
    "\n",
    ".binding-placeholder,\n",
    ".binding-note {\n",
    "  display: block;\n",
    "  margin: 2.5rem 0;\n",
    "  padding: 1.25rem 0;\n",
    "  border-top: 1px solid var(--fn-rule-strong);\n",
    "  border-bottom: 1px solid var(--fn-rule);\n",
    "}\n",
    "\n",
    ".binding-placeholder strong,\n",
    ".binding-note h3 {\n",
    "  font-family: var(--fn-ui);\n",
    "}\n",
    "\n",
    ".binding-placeholder p,\n",
    ".binding-note p {\n",
    "  margin: 0.5rem 0 0.75rem;\n",
    "}\n",
    "\n",
    ".source-stamp,\n",
    ".tape-label,\n",
    ".binding-path,\n",
    ".source-leaf-path,\n",
    ".page-number {\n",
    "  color: var(--fn-ink-soft);\n",
    "  background: transparent;\n",
    "  font-family: var(--fn-mono);\n",
    "  font-size: 0.75rem;\n",
    "  font-weight: 400;\n",
    "}\n",
    "\n",
    ".run-note,\n",
    ".run-command {\n",
    "  margin-top: 1rem;\n",
    "  font-family: var(--fn-ui);\n",
    "  font-size: 0.82rem;\n",
    "}\n",
    "\n",
    ".equation-note,\n",
    ".math-display {\n",
    "  box-sizing: border-box;\n",
    "  width: 100%;\n",
    "  margin: 1.5rem 0 1.75rem;\n",
    "  overflow-x: auto;\n",
    "  text-align: center;\n",
    "}\n",
    "\n",
    ".math-inline {\n",
    "  white-space: normal;\n",
    "}\n",
    "\n",
    ".handwritten,\n",
    ".underlined-red {\n",
    "  color: inherit;\n",
    "  font-family: inherit;\n",
    "  text-decoration: none;\n",
    "}\n",
    "\n",
    ".ink-sheet,\n",
    ".source-sheet,\n",
    ".source-leaf-body,\n",
    ".binding-note-body {\n",
    "  background: transparent;\n",
    "  border: 0;\n",
    "}\n",
    "\n",
    ".anchor-alias {\n",
    "  position: absolute;\n",
    "  width: 1px;\n",
    "  height: 1px;\n",
    "  overflow: hidden;\n",
    "}\n",
    "\n",
    "@media (max-width: 32rem) {\n",
    "  .jp-RenderedHTMLCommon,\n",
    "  .text_cell_render {\n",
    "    font-size: 1rem;\n",
    "  }\n",
    "\n",
    "  .field-cover {\n",
    "    padding-top: 2rem;\n",
    "  }\n",
    "\n",
    "  .folio-opener,\n",
    "  .experiment-opener {\n",
    "    margin-top: 3rem;\n",
    "  }\n",
    "\n",
    "  .two-page-spread,\n",
    "  .notebook-grid {\n",
    "    grid-template-columns: 1fr;\n",
    "    gap: 0;\n",
    "  }\n",
    "\n",
    "  .jp-RenderedHTMLCommon table,\n",
    "  .text_cell_render table {\n",
    "    display: block;\n",
    "    overflow-x: auto;\n",
    "    white-space: nowrap;\n",
    "  }\n",
    "\n",
    "}\n",
    "\n",
    "@media print {\n",
    "  :root {\n",
    "    --fn-page-width: 100%;\n",
    "    --fn-wide-width: 100%;\n",
    "  }\n",
    "\n",
    "  .jp-CodeCell,\n",
    "  .code_cell,\n",
    "  .workbench-note,\n",
    "  .bench-insert {\n",
    "    width: 100%;\n",
    "  }\n",
    "\n",
    "  .leaf-download,\n",
    "  .source-links {\n",
    "    display: none;\n",
    "  }\n",
    "\n",
    "  a {\n",
    "    color: inherit !important;\n",
    "  }\n",
    "}\n",
    "\n",
    "</style>\n",
    "\n",
    "<div class=\"field-cover\">\n",
    "  <div class=\"cover-kicker\">LAB 03 · DIAGNOSIS</div>\n",
    "  <h1>Measuring the Invisible</h1>\n",
    "  <div class=\"cover-subtitle\">Tracing retrieval loss, context loss, and generation loss without hiding the cause</div>\n",
    "  <div class=\"cover-scribble\">an average cannot tell us where the evidence vanished</div>\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The working page\n",
    "\n",
    "This notebook evaluates retrieval, answer overlap, citation validity, lexical support, abstention, tagged slices, and uncertainty separately. It compares a sparse retrieve-then-generate baseline with the advanced modular pipeline.\n",
    "\n",
    "The small teaching set demonstrates mechanics, not statistical claims about paper systems. A product evaluation needs hundreds or thousands of stratified, human-labeled queries and immutable corpus/model traces.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Evaluating 8 labeled examples over 14 documents\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "import sys\n",
    "\n",
    "ROOT = Path.cwd()\n",
    "if ROOT.name == 'notebooks':\n",
    "    ROOT = ROOT.parent\n",
    "sys.path.insert(0, str(ROOT / 'src'))\n",
    "\n",
    "from rag_evolution.demo_data import demo_documents, demo_questions\n",
    "from rag_evolution.evaluation import (\n",
    "    aggregate_metrics, evaluate_pipeline, evaluate_retriever, metrics_by_tag,\n",
    "    paired_bootstrap_delta, precision_at_k, recall_at_k, reciprocal_rank, ndcg_at_k,\n",
    ")\n",
    "from rag_evolution.pipeline import build_advanced_pipeline, build_baseline_pipeline\n",
    "\n",
    "documents = demo_documents()\n",
    "questions = demo_questions()\n",
    "baseline = build_baseline_pipeline(documents)\n",
    "advanced = build_advanced_pipeline(documents)\n",
    "print(f'Evaluating {len(questions)} labeled examples over {len(documents)} documents')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Verify metric behavior before trusting a harness\n",
    "\n",
    "Metric unit tests catch denominator and cutoff mistakes. Retrieval precision, recall, MRR, and nDCG answer different questions and still do not prove that the generator used the evidence."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision@2 = 0.500\n",
      "recall@2    = 0.500\n",
      "MRR         = 0.500\n",
      "nDCG@3      = 0.693\n"
     ]
    }
   ],
   "source": [
    "ranking = ['distractor', 'gold-a', 'gold-b']\n",
    "gold = {'gold-a', 'gold-b'}\n",
    "print(f'precision@2 = {precision_at_k(ranking, gold, 2):.3f}')\n",
    "print(f'recall@2    = {recall_at_k(ranking, gold, 2):.3f}')\n",
    "print(f'MRR         = {reciprocal_rank(ranking, gold):.3f}')\n",
    "print(f'nDCG@3      = {ndcg_at_k(ranking, gold, 3):.3f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Retrieval layer: compare under one corpus and cutoff\n",
    "\n",
    "The comparison holds documents, chunks, labels, and cutoff fixed. In a real study also hold ANN search depth, source filters, time, and reranker candidate budget fixed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "baseline  recall@3=0.938 MRR=1.000 nDCG@3=0.952\n",
      "advanced  recall@3=1.000 MRR=0.938 nDCG@3=0.954\n",
      "\n",
      "Per-question recall deltas:\n",
      "q-dpr            baseline=1.00 advanced=1.00 delta=+0.00\n",
      "q-fid            baseline=1.00 advanced=1.00 delta=+0.00\n",
      "q-rag-dpr        baseline=0.50 advanced=1.00 delta=+0.50\n",
      "q-self-rag       baseline=1.00 advanced=1.00 delta=+0.00\n",
      "q-raptor         baseline=1.00 advanced=1.00 delta=+0.00\n",
      "q-visual         baseline=1.00 advanced=1.00 delta=+0.00\n",
      "q-long-context   baseline=1.00 advanced=1.00 delta=+0.00\n",
      "q-grip           baseline=1.00 advanced=1.00 delta=+0.00\n"
     ]
    }
   ],
   "source": [
    "baseline_retrieval = evaluate_retriever(baseline, questions, k=3)\n",
    "advanced_retrieval = evaluate_retriever(advanced, questions, k=3)\n",
    "for name, rows in [('baseline', baseline_retrieval), ('advanced', advanced_retrieval)]:\n",
    "    mean = aggregate_metrics(rows)\n",
    "    print(f\"{name:9s} recall@3={mean['recall@3']:.3f} MRR={mean['mrr']:.3f} nDCG@3={mean['ndcg@3']:.3f}\")\n",
    "print('\\nPer-question recall deltas:')\n",
    "for left, right in zip(baseline_retrieval, advanced_retrieval):\n",
    "    delta = right['recall@3'] - left['recall@3']\n",
    "    print(f\"{left['id']:16s} baseline={left['recall@3']:.2f} advanced={right['recall@3']:.2f} delta={delta:+.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. End-to-end layer: answers, citations, support, and abstention\n",
    "\n",
    "`answer_f1` measures overlap with a short reference and will undervalue a correct long extract. Citation precision/recall checks document labels. `lexical_faithfulness` is only a transparent overlap diagnostic—not semantic entailment. Human or calibrated claim-level judges are still required."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "metric                    baseline  advanced\n",
      "------------------------------------------------\n",
      "recall@5                     1.000     1.000\n",
      "mrr                          1.000     0.938\n",
      "answer_f1                    0.275     0.275\n",
      "citation_precision           0.875     0.875\n",
      "citation_recall              1.000     1.000\n",
      "lexical_faithfulness         1.000     1.000\n",
      "abstained                    0.000     0.000\n"
     ]
    }
   ],
   "source": [
    "baseline_rows = evaluate_pipeline(baseline, questions)\n",
    "advanced_rows = evaluate_pipeline(advanced, questions)\n",
    "keys = ['recall@5', 'mrr', 'answer_f1', 'citation_precision', 'citation_recall', 'lexical_faithfulness', 'abstained']\n",
    "print('metric                    baseline  advanced')\n",
    "print('-' * 48)\n",
    "base_mean = aggregate_metrics(baseline_rows)\n",
    "advanced_mean = aggregate_metrics(advanced_rows)\n",
    "for key in keys:\n",
    "    print(f'{key:25s} {base_mean[key]:8.3f}  {advanced_mean[key]:8.3f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Slice before averaging\n",
    "\n",
    "An overall mean can hide that a technique helps comparisons but harms identifier lookup. Tags should reflect product risks: multi-hop, current, long-tail, unanswerable, multilingual, visual, conflict, and adversarial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "adaptive       recall=1.000 citation_recall=1.000 answer_f1=0.424\n",
      "agentic        recall=1.000 citation_recall=1.000 answer_f1=0.049\n",
      "architecture   recall=1.000 citation_recall=1.000 answer_f1=0.391\n",
      "comparison     recall=1.000 citation_recall=1.000 answer_f1=0.286\n",
      "hierarchical   recall=1.000 citation_recall=1.000 answer_f1=0.323\n",
      "lexical        recall=1.000 citation_recall=1.000 answer_f1=0.485\n",
      "multi-hop      recall=1.000 citation_recall=1.000 answer_f1=0.286\n",
      "multimodal     recall=1.000 citation_recall=1.000 answer_f1=0.080\n",
      "routing        recall=1.000 citation_recall=1.000 answer_f1=0.162\n",
      "single-hop     recall=1.000 citation_recall=1.000 answer_f1=0.273\n"
     ]
    }
   ],
   "source": [
    "slices = metrics_by_tag(advanced_rows)\n",
    "for tag in sorted(slices):\n",
    "    metrics = slices[tag]\n",
    "    print(f\"{tag:14s} recall={metrics['recall@5']:.3f} citation_recall={metrics['citation_recall']:.3f} answer_f1={metrics['answer_f1']:.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Pair systems and report uncertainty\n",
    "\n",
    "Independent means throw away the pairing: both systems answer the same query. The paired bootstrap resamples query indices and estimates the mean delta interval. With only eight teaching questions the interval is illustrative and cannot support a serious conclusion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "advanced − baseline recall@3 = +0.062 (illustrative paired 95% interval +0.000, +0.188)\n"
     ]
    }
   ],
   "source": [
    "advanced_recall = [row['recall@3'] for row in advanced_retrieval]\n",
    "baseline_recall = [row['recall@3'] for row in baseline_retrieval]\n",
    "delta, lower, upper = paired_bootstrap_delta(advanced_recall, baseline_recall, iterations=2000, seed=7)\n",
    "print(f'advanced − baseline recall@3 = {delta:+.3f} (illustrative paired 95% interval {lower:+.3f}, {upper:+.3f})')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Inspect a multi-hop case causally\n",
    "\n",
    "A final correct answer can hide an incomplete reasoning chain. Inspect candidates, selected contexts, answer claims, and citations for every failure—not only the final score."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "QUESTION: What is the relationship and difference between DPR and the original RAG model?\n",
      "GOLD DOCUMENTS: ('dpr-2020', 'rag-2020')\n",
      "PACKED DOCUMENTS: ('rag-2020', 'dpr-2020', 'search-r1-2025', 'lara-2025', 'atlas-2022', 'self-rag-2023')\n",
      "CITED DOCUMENTS: ('rag-2020', 'dpr-2020')\n",
      "ANSWER: Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks coupled a DPR question encoder with a frozen passage index and a BART generator. [rag-2020::c000] Dense Passage Retrieval (DPR) for Open-Domain Question Answering introduced a simple dual-encoder retriever trained with positive passages, in-batch negatives, and a hard BM25 negative. [dpr-2020::c000]\n"
     ]
    }
   ],
   "source": [
    "example = next(item for item in questions if item.id == 'q-rag-dpr')\n",
    "answer = advanced.ask(example.question)\n",
    "print('QUESTION:', example.question)\n",
    "print('GOLD DOCUMENTS:', example.relevant_document_ids)\n",
    "print('PACKED DOCUMENTS:', tuple(result.chunk.document_id for result in answer.contexts))\n",
    "print('CITED DOCUMENTS:', tuple(citation.document_id for citation in answer.citations))\n",
    "print('ANSWER:', answer.text)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Test absence explicitly\n",
    "\n",
    "NoMIRACL and sufficient-context work show that hallucination and miss rates trade off. Report both false answers when evidence is absent and unnecessary abstention when it is present."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "abstained=True  confidence=0.000 | How are volcanic zircons dated with uranium lead ratios?\n",
      "abstained=False confidence=0.443 | What dosage cures an imaginary disease called RAG fever?\n"
     ]
    }
   ],
   "source": [
    "absence_queries = [\n",
    "    'How are volcanic zircons dated with uranium lead ratios?',\n",
    "    'What dosage cures an imaginary disease called RAG fever?',\n",
    "]\n",
    "for query in absence_queries:\n",
    "    result = advanced.ask(query)\n",
    "    print(f'abstained={result.abstained!s:5s} confidence={result.confidence:.3f} | {query}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Production evaluation checklist\n",
    "\n",
    "1. Build 300–1,000+ stratified product queries with answerability, claims, exact evidence spans, time, authority, and permissions.\n",
    "2. Evaluate retrieval, oracle-context generation, and end-to-end behavior separately.\n",
    "3. Add conflict, counterfactual, noise, ordering, poison, indirect-instruction, deletion, and cross-tenant tests.\n",
    "4. Freeze corpus/query time, chunks, indexes, prompts, model/judge versions, seeds, and every retrieved text/score.\n",
    "5. Double-label and adjudicate a sample; audit automatic-judge disagreements and high-risk outputs.\n",
    "6. Apply hard safety/permission/freshness gates, then compare quality, p95 latency, index memory, tokens, tool calls, and dollars per supported answer on a Pareto frontier."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.9"
  },
  "rag_evolution": {
   "presentation": "plain-field-notebook",
   "visual_version": 2
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
