fix(corpus): re-link orphan citations when a cited ruling is uploaded (G1) #309
@@ -432,3 +432,42 @@ async def get_cited_case_law_ids(source_case_law_ids: list[UUID]) -> dict[str, l
|
|||||||
for r in rows:
|
for r in rows:
|
||||||
out.setdefault(r["source_id"], []).append(r["cited_id"])
|
out.setdefault(r["source_id"], []).append(r["cited_id"])
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
async def relink_orphan_citations(case_law_id: "UUID | None" = None) -> int:
|
||||||
|
"""Back-fill ``cited_case_law_id`` on orphan citation edges that now resolve.
|
||||||
|
|
||||||
|
Citations are resolved to a corpus row only at extraction time (see
|
||||||
|
``extract_and_store``). When a cited ruling is uploaded LATER, its existing
|
||||||
|
orphan edges (``cited_case_law_id IS NULL``) are never re-resolved, so the
|
||||||
|
citation graph under-counts real connectivity. Call on every precedent
|
||||||
|
upload (``case_law_id`` set → link only edges that resolve to that row) or as
|
||||||
|
a one-off sweep (``case_law_id=None`` → re-resolve every orphan). Reuses the
|
||||||
|
canonical resolver ``_resolve_case_law_id`` (no parallel matching logic, G2);
|
||||||
|
idempotent — a second run links nothing new.
|
||||||
|
"""
|
||||||
|
pool = await db.get_pool()
|
||||||
|
async with pool.acquire() as conn:
|
||||||
|
orphans = await conn.fetch(
|
||||||
|
"SELECT DISTINCT cited_case_number FROM precedent_internal_citations "
|
||||||
|
"WHERE cited_case_law_id IS NULL AND coalesce(cited_case_number, '') <> ''"
|
||||||
|
)
|
||||||
|
linked = 0
|
||||||
|
for r in orphans:
|
||||||
|
cited = await _resolve_case_law_id(_normalize_case_number(r["cited_case_number"]))
|
||||||
|
if cited is None:
|
||||||
|
continue
|
||||||
|
if case_law_id is not None and cited != case_law_id:
|
||||||
|
continue
|
||||||
|
async with pool.acquire() as conn:
|
||||||
|
res = await conn.execute(
|
||||||
|
"UPDATE precedent_internal_citations "
|
||||||
|
"SET cited_case_law_id = $1, confidence = GREATEST(confidence, 0.90) "
|
||||||
|
"WHERE cited_case_law_id IS NULL AND cited_case_number = $2",
|
||||||
|
cited, r["cited_case_number"],
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
linked += int(res.split()[-1])
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
return linked
|
||||||
|
|||||||
@@ -233,6 +233,17 @@ async def ingest_document(
|
|||||||
await db.request_metadata_extraction(case_law_id)
|
await db.request_metadata_extraction(case_law_id)
|
||||||
await db.request_halacha_extraction(case_law_id)
|
await db.request_halacha_extraction(case_law_id)
|
||||||
await db.recompute_searchable(case_law_id)
|
await db.recompute_searchable(case_law_id)
|
||||||
|
# Citations resolve to a corpus row only at extraction time. A ruling
|
||||||
|
# uploaded AFTER it was first cited leaves orphan edges (NULL link),
|
||||||
|
# so re-link them to this freshly-ingested row now — the citation
|
||||||
|
# graph self-heals instead of permanently under-counting (G1). Non-fatal.
|
||||||
|
try:
|
||||||
|
from legal_mcp.services import citation_extractor as _ce
|
||||||
|
relinked = await _ce.relink_orphan_citations(case_law_id)
|
||||||
|
if relinked:
|
||||||
|
logger.info("relinked %d orphan citation(s) -> %s", relinked, case_law_id)
|
||||||
|
except Exception as e: # noqa: BLE001 — relink is best-effort
|
||||||
|
logger.warning("citation relink failed (non-fatal): %s", e)
|
||||||
|
|
||||||
await progress("completed", 100,
|
await progress("completed", 100,
|
||||||
f"נקלט: {stored_chunks} chunks. חילוץ הלכות ומטא-דאטה ממתינים בתור.")
|
f"נקלט: {stored_chunks} chunks. חילוץ הלכות ומטא-דאטה ממתינים בתור.")
|
||||||
|
|||||||
Reference in New Issue
Block a user