feat(bib): backfill-comments skips sealed dockets; --force override (refs #615)

This commit is contained in:
kert
2026-09-08 13:02:08 -04:00
parent 0ade9bee41
commit 5ff3a5e5bf
3 changed files with 95 additions and 0 deletions

View File

@@ -396,6 +396,7 @@ def backfill_details(
client: Client, client: Client,
*, *,
docket: str = "", docket: str = "",
force: bool = False,
limit: int | None = None, limit: int | None = None,
log_path: Path | None = None, log_path: Path | None = None,
commit_every: int = 25, commit_every: int = 25,
@@ -417,6 +418,27 @@ def backfill_details(
so we stop hammering it. Commits land every ``commit_every`` items so we stop hammering it. Commits land every ``commit_every`` items
so a crash loses at most that many items of work. so a crash loses at most that many items of work.
""" """
if docket and not force:
d = store.docket_get(docket)
if d is not None and d.sealed:
log.info(
"%s sealed %s (%s); backfill skipped",
docket,
d.sealed_at[:10],
d.seal_reason,
)
print(
f"{docket}: sealed {d.sealed_at[:10]} ({d.seal_reason}); skipped — use --force",
flush=True,
)
return {
"skipped_sealed": 1,
"seen": 0,
"enriched": 0,
"created": 0,
"attached": 0,
"errors": 0,
}
con = store._con() # noqa: SLF001 con = store._con() # noqa: SLF001
# Resume rule: treat ``enriched:ok`` as the completion marker. # Resume rule: treat ``enriched:ok`` as the completion marker.
# Using a tag (not just the abstract) lets us handle "see attached" # Using a tag (not just the abstract) lets us handle "see attached"
@@ -675,6 +697,7 @@ def backfill_from_mirror(
mirror: Mirror, mirror: Mirror,
*, *,
docket: str, docket: str,
force: bool = False,
limit: int | None = None, limit: int | None = None,
log_path: Path | None = None, log_path: Path | None = None,
commit_every: int = 200, commit_every: int = 200,
@@ -688,6 +711,27 @@ def backfill_from_mirror(
S3 fetches fan out over a thread pool; every Store/sqlite call stays S3 fetches fan out over a thread pool; every Store/sqlite call stays
on this thread. Idempotent: ``enriched:ok`` items are skipped. on this thread. Idempotent: ``enriched:ok`` items are skipped.
""" """
if docket and not force:
d = store.docket_get(docket)
if d is not None and d.sealed:
log.info(
"%s sealed %s (%s); backfill skipped",
docket,
d.sealed_at[:10],
d.seal_reason,
)
print(
f"{docket}: sealed {d.sealed_at[:10]} ({d.seal_reason}); skipped — use --force",
flush=True,
)
return {
"skipped_sealed": 1,
"seen": 0,
"enriched": 0,
"created": 0,
"attached": 0,
"errors": 0,
}
con = store._con() # noqa: SLF001 con = store._con() # noqa: SLF001
existing: dict[str, tuple[str, bool]] = {} # cid -> (key, enriched) existing: dict[str, tuple[str, bool]] = {} # cid -> (key, enriched)
for key, url, enriched in con.execute( for key, url, enriched in con.execute(

View File

@@ -388,6 +388,11 @@ def backfill_comments(
"reg.gov API — bulk speed, no rate cap, also ingests comments " "reg.gov API — bulk speed, no rate cap, also ingests comments "
"the original farm never listed. Requires --docket.", "the original farm never listed. Requires --docket.",
), ),
force: bool = typer.Option(
False,
"--force",
help="Run even if the docket is sealed.",
),
) -> None: ) -> None:
"""Enrich every reg-gov comment stub with body + attachments. """Enrich every reg-gov comment stub with body + attachments.
@@ -413,6 +418,7 @@ def backfill_comments(
store, store,
Mirror(), Mirror(),
docket=docket, docket=docket,
force=force,
limit=limit or None, limit=limit or None,
log_path=log_path, log_path=log_path,
) )
@@ -424,6 +430,7 @@ def backfill_comments(
store, store,
api, api,
docket=docket, docket=docket,
force=force,
limit=limit or None, limit=limit or None,
log_path=log_path, log_path=log_path,
) )

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from unittest.mock import MagicMock
from bib.dockets import Docket
from bib.regulations_gov import backfill_details, backfill_from_mirror
from bib.store import Store
D = "CMS-2019-0111"
def _sealed_store() -> Store:
s = Store(":memory:", storage_dir="/tmp/nope")
s.docket_upsert(
Docket(id=D, sealed_at="2020-06-01T00:00:00Z", seal_reason="manual")
)
return s
def test_api_backfill_skips_sealed_without_calls():
s = _sealed_store()
api = MagicMock()
stats = backfill_details(s, api, docket=D)
assert stats["skipped_sealed"] == 1 and stats["seen"] == 0
api.get_comment_detail.assert_not_called()
def test_mirror_backfill_skips_sealed_without_listing():
s = _sealed_store()
m = MagicMock()
stats = backfill_from_mirror(s, m, docket=D)
assert stats["skipped_sealed"] == 1 and stats["seen"] == 0
m.comment_ids.assert_not_called()
m.attachment_keys.assert_not_called()
def test_force_runs_sealed_mirror_backfill():
s = _sealed_store()
m = MagicMock()
m.comment_ids.return_value = []
m.attachment_keys.return_value = {}
stats = backfill_from_mirror(s, m, docket=D, force=True)
assert "skipped_sealed" not in stats
m.comment_ids.assert_called_once_with(D)