fix(comments): title=comment_id; route by docket into Rules/Comments/<docket>

Two related fixes for how regulations.gov comment items show up in
Zotero.

Title was "Comment on <comment_on_id>" — but ``comment_on_id`` is the
*parent rule's* document id, so every comment under a single rule
appeared with an identical title in the Zotero list view. Use
``comment.id`` instead (the comment's own CMS-DOCKET-XXXX-NNNN id),
optionally prefixed with the byline when the commenter populated
firstName/lastName/organization.

Collection routing put every comment into a flat Rules/Comments
folder. With ~164k items that's unusable; route to
Rules/Comments/<docket_id> instead. The docket comes from the
``reg-docket:`` tag set by ``cli/bib.py`` when fetching PFS comments
(via ``api.resolve_docket``) — not the ``docket:`` tag, which is
sourced from regulations.gov's comment-detail attrs.docketId field
that's unpopulated for the bulk of submissions.

Companion to a one-shot SQL migration that retitled 164,214 existing
items in bib + Zotero and moved them out of the flat Rules/Comments
into per-docket sub-collections (CMS-2017-0092 … CMS-2025-0304).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kert
2026-04-28 13:12:48 -04:00
parent fafe6618bc
commit c53571c086
2 changed files with 21 additions and 6 deletions

View File

@@ -540,10 +540,14 @@ def upsert_comment(
extra_tags: list[str] | None = None, extra_tags: list[str] | None = None,
) -> str: ) -> str:
"""Upsert the comment as a Source item, return bib key.""" """Upsert the comment as a Source item, return bib key."""
title = comment.title or f"Comment on {comment.comment_on_id}" # Title = the comment's own CMS-DOCKET-XXXX-NNNN id, optionally
# prefixed with the byline (org or first/last name) when the
# commenter filled those fields in. The previous "Comment on
# <comment_on_id>" form named the *parent rule* instead of the
# comment, which made every comment under a single rule look
# identical in the Zotero list.
byline = _byline(comment) byline = _byline(comment)
if byline: title = f"{byline}: {comment.id}" if byline else comment.id
title = f"{byline}: {title[:120]}"
url = f"https://www.regulations.gov/comment/{comment.id}" url = f"https://www.regulations.gov/comment/{comment.id}"
item = Source(title=title, url=url) item = Source(title=title, url=url)

View File

@@ -391,9 +391,20 @@ def _zotero_collection_path(item: Item) -> list[str]:
manual = (ej.get("manual_name") or "").strip() or "General" manual = (ej.get("manual_name") or "").strip() or "General"
return ["Healthcare Data Platform", "Manuals", manual] return ["Healthcare Data Platform", "Manuals", manual]
if "source:regulations-gov" in item.tags: if "source:regulations-gov" in item.tags:
# Every rulemaking comment lands flat in Rules/Comments. The # Each rulemaking gets its own sub-collection under
# rule:<id> and year:<Y> tags on each item carry the # Rules/Comments/<docket_id> (e.g. CMS-2025-0304). Without this
# disambiguating citation, so a single bucket is readable. # split, ~164k comments pile into a single Rules/Comments
# folder and the Zotero list view becomes unusable.
#
# Read from `reg-docket:` (set by cli/bib.py via api.resolve_docket),
# not `docket:` — the latter is sourced from regulations.gov's
# comment-detail attrs.docketId field which is empty for the
# bulk of comments.
for tag in item.tags:
if tag.startswith("reg-docket:"):
docket = tag.split(":", 1)[1]
if docket:
return ["Rules", "Comments", docket]
return ["Rules", "Comments"] return ["Rules", "Comments"]
if "source:email" in item.tags: if "source:email" in item.tags:
# IMAP-ingested email lands under Inbox/<Mailbox-Title>. # IMAP-ingested email lands under Inbox/<Mailbox-Title>.