Files
stack/src/llm/links.py
kert 531fca9655
Some checks failed
CI / lint (push) Successful in 39s
CI / notebooks-smoke (push) Successful in 1m42s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
CI / test (push) Failing after 2m30s
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / llm (push) Has been skipped
Deploy / mc (push) Has been skipped
Infra CI / zotero (push) Successful in 15s
Infra CI / notebooks (push) Successful in 52s
Deploy / report (push) Has been cancelled
Infra CI / api (push) Has been cancelled
Infra CI / llm (push) Has been cancelled
Infra CI / mc (push) Has been cancelled
Infra CI / docs (push) Has started running
feat(llm): rule discussions ↔ manual sections — cited IOM sections join the chat as companion sources; search links them; rules theme-stamped
A retrieved FR paragraph that cites a CMS manual ('Pub. 100-04,
Chapter 12, Section 30.6.4') now carries those citations on its source
(as_source iom_refs, parsed by pfs.guidance.iom_refs over the full
chunk text). llm.iomlink resolves each cited section to its indexed
manual chunk (exact section, else the first finer section under it,
else the chapter's first section) and the chat merges up to
iom_companion_max (4) of them in after the CPT manual block, labelled
'IOM 100-04 ch.12 §30.6.4', linked to the chapter PDF page, marked
'cited by [91 FR … ¶n]' in the prompt and the sources drawer; most-cited
sections first, nothing already among the sources repeated, no query
when no rule cites a manual. The search page shows 'cites IOM …' links
on rule hits that open the cited section under the manual filters, and
gains manual / chapter / section / theme fields. Rule paragraphs are
theme-stamped as well (stack llm stamp-themes --collection rules
--doctype rule: 67,340 chunks).
2026-09-24 20:25:33 -04:00

170 lines
6.2 KiB
Python

"""Evidence deep links: one (url, label) per retrieved chunk (pure).
- comment → regulations.gov comment page, or the attachment on the
downloads CDN with ``#page=N`` for PDFs (page located at index time,
see ``llm.pages``).
- rule → the FR paragraph anchor ``#p-N`` plus a ``:~:text=``
scroll-to-text fragment so the browser highlights the passage;
degrades to ``#page-N``, then the document URL.
- corpus → the item's own URL (``#page=N`` when it is a PDF).
Labels double as the citation tokens the model is told to emit
(``[91 FR 43949 ¶4]``, ``[CMS-2026-2377-3438 p.4]``).
"""
from __future__ import annotations
from bib.frlink import text_fragment
_COMMENT = "https://www.regulations.gov/comment/{cid}"
_DOWNLOAD = "https://downloads.regulations.gov/{cid}/{name}"
_TITLE_MAX = 60
_SNIPPET_CHARS = 500
def _short(title: str) -> str:
title = " ".join(title.split())
if len(title) <= _TITLE_MAX:
return title
return title[:_TITLE_MAX].rsplit(" ", 1)[0] + "…"
def _comment(md: dict[str, str]) -> tuple[str, str]:
cid = md.get("comment_id", "")
if not cid:
return "", md.get("item_key", "")
name, page = md.get("attachment", ""), md.get("page", "")
if name:
url = _DOWNLOAD.format(cid=cid, name=name)
if page and name.lower().endswith(".pdf"):
return f"{url}#page={page}", f"{cid} p.{page}"
return url, cid
return _COMMENT.format(cid=cid), cid
def _rule(md: dict[str, str], snippet: str) -> tuple[str, str]:
base = md.get("html_url", "") or md.get("url", "")
page, p_id, ordinal, vol = (
md.get("page", ""),
md.get("p_id", ""),
md.get("ordinal", ""),
md.get("fr_volume", ""),
)
if p_id and page:
url = f"{base}#p-{p_id}"
frag = text_fragment(snippet)
if frag:
url = f"{url}:~:text={frag}"
label = f"{vol} FR {page}{ordinal}" if vol and ordinal else f"p-{p_id}"
return url, label
if page:
label = f"{vol} FR {page}" if vol else f"page {page}"
return f"{base}#page-{page}", label
return base, _short(md.get("title", "")) or md.get("item_key", "")
def _corpus(md: dict[str, str]) -> tuple[str, str]:
url, page = md.get("url", ""), md.get("page", "")
if url and page and url.lower().endswith(".pdf"):
url = f"{url}#page={page}"
if md.get("doctype") == "manual":
# IOM chapter section: "IOM 100-04 ch.18 §10.1.2", linked to the
# chapter PDF page the section was located on
from llm.manuals import label as manual_label
label = manual_label(md)
if label:
return url, label
title = _short(md.get("title", ""))
year = md.get("year", "") or md.get("date", "")[:4]
if title and year:
label = f"{title} ({year})"
else:
label = title or md.get("item_key", "")
return url, label
def for_source(md: dict[str, str], snippet: str) -> tuple[str, str]:
"""``(url, label)`` for a chunk's metadata. Never raises; degrades
to whatever link the metadata supports."""
kind = md.get("kind", "") or ("comment" if md.get("comment_id") else "corpus")
if kind == "comment":
return _comment(md)
if kind == "rule":
return _rule(md, snippet)
return _corpus(md)
def iom_refs_of(text: str) -> list[dict[str, str]]:
"""``[{"pub", "chapter", "section", "label"}]`` for every CMS manual
citation in *text* ("Pub. 100-04, Chapter 12, Section 30.6.4"),
de-duplicated in order of appearance."""
from llm.manuals import label as manual_label
from pfs.guidance import iom_refs
out: list[dict[str, str]] = []
seen: set[tuple[str, str, str]] = set()
for pub, chapter, section in iom_refs(text or ""):
key = (pub, chapter, section)
if key in seen:
continue
seen.add(key)
out.append(
{
"pub": pub,
"chapter": chapter,
"section": section,
"label": manual_label(
{"manual": pub, "chapter": chapter, "iom_section": section}
),
}
)
return out
def as_source(md: dict[str, str], text: str, score: float) -> dict:
"""The source dict the chat sends to the prompt and the UI.
``item_key``, ``p_id`` (rules only; ``""`` otherwise), ``seq``
(non-rule kinds only; ``""`` for rules) and ``section`` are carried
straight from *md* so the golden evaluation can match a source back
to its chunk anchor: ``(item_key, p_id)`` for a rule paragraph,
``(item_key, seq)`` for a comment/corpus chunk.
"""
snippet = text[:_SNIPPET_CHARS].strip()
url, label = for_source(md, snippet)
kind = md.get("kind", "")
return {
"id": label,
"label": label,
"kind": kind,
# proposed / final / correction for a rule (the UI badge and the
# prompt's source line show it; the citation token stays the FR cite)
"rule_kind": md.get("rule_kind", "") if kind == "rule" else "",
"doctype": md.get("doctype", ""),
# IOM chapter coordinates for manual sections (llm.manuals)
"manual": md.get("manual", ""),
"chapter": md.get("chapter", ""),
"iom_section": md.get("iom_section", ""),
# theme slugs stamped by llm.themes (comma-joined), for filters and badges
"themes": md.get("themes", ""),
# IOM sections a rule paragraph cites (llm.iomlink pulls them in as
# companion sources; the search page links them)
"iom_refs": iom_refs_of(text) if kind == "rule" else [],
"url": url,
"title": md.get("title", ""),
"date": md.get("date", ""),
"docket": md.get("docket", ""),
"comment_id": md.get("comment_id", ""),
"snippet": snippet,
"score": round(score, 4),
"item_key": md.get("item_key", ""),
"p_id": md.get("p_id", "") if kind == "rule" else "",
"seq": md.get("seq", "") if kind != "rule" else "",
"section": md.get("section", ""),
# the PDF the chunk came from and the page it was located on
# (``llm.pages.enrich_pdf_pages``) — the UI links them to the viewer
"attachment": md.get("attachment", ""),
"page": md.get("page", ""),
}