Merge pull request 'fix(learning): extract precedent metadata inline on final-decision enroll' (#335) from worktree-enroll-inline-metadata into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 9s

This commit was merged in pull request #335.
This commit is contained in:
2026-06-28 10:49:23 +00:00
2 changed files with 29 additions and 4 deletions

View File

@@ -383,7 +383,10 @@ async def reextract_metadata(
appeal_subtype, and case_name when it equals the citation). User appeal_subtype, and case_name when it equals the citation). User
values are preserved. values are preserved.
**MCP-tool-only path** — same constraint as :func:`reextract_halachot`. **Container-safe** — unlike :func:`reextract_halachot` (claude CLI, host-only),
metadata extraction runs on Gemini Flash over REST (GOOGLE_GEMINI_API_KEY), so
this path is callable from the FastAPI container too. The final-decision
enrollment loop (``_enroll_final_in_library``) calls it inline on upload.
""" """
from legal_mcp.services import precedent_metadata_extractor from legal_mcp.services import precedent_metadata_extractor

View File

@@ -3643,9 +3643,11 @@ async def _enroll_final_in_library(
out["error"] = "no final text extracted" out["error"] = "no final text extracted"
return out return out
# Deterministic metadata from the case record — the Gemini metadata extractor is # Deterministic seeds from the case record first — proceeding_type / date /
# tuned for EXTERNAL rulings and returns no_metadata for internal decisions, so we # citation are identity, not inference, so we set them ourselves (no LLM) and the
# populate proceeding_type / date / tags / summary / citation ourselves (no LLM). # later Gemini pass (reextract_metadata, below) is fills-empty-only and won't
# touch them. subject_tags/summary seeded here from the case (chair-curated
# subject_categories win when present); when empty, Gemini fills them inline.
district = "ירושלים" district = "ירושלים"
proceeding_type = (case.get("proceeding_type") or "ערר").strip() proceeding_type = (case.get("proceeding_type") or "ערר").strip()
decision_date = case.get("decision_date") or case.get("hearing_date") decision_date = case.get("decision_date") or case.get("hearing_date")
@@ -3685,6 +3687,26 @@ async def _enroll_final_in_library(
except Exception as e: except Exception as e:
logger.warning("citation build failed for %s: %s", case_number, e) logger.warning("citation build failed for %s: %s", case_number, e)
# Fill the LLM-derived metadata (subject_tags, summary, headnote, key_quote)
# inline via Gemini (REST — container-safe; GOOGLE_GEMINI_API_KEY in Coolify),
# reusing the same reextract_metadata path the UI/drain use (G2 — one path, full
# status lifecycle). apply_to_record fills ONLY empty fields, so the deterministic
# seeds above (proceeding_type / date / citation, plus any chair-curated
# subject_categories) are preserved. Without this the row sat at
# metadata_extraction_status='pending' with empty tags until a drain happened to
# pick it up, so a freshly-enrolled final showed no subject tags in the edit UI.
# Best-effort — surfaced in the return value, never silently swallowed.
try:
from legal_mcp.services import precedent_library as plib_service
meta = await plib_service.reextract_metadata(UUID(case_law_id))
out["metadata"] = {
"status": meta.get("status"),
"fields": meta.get("fields") or [],
}
except Exception as e:
logger.warning("inline metadata extraction failed for %s: %s", case_number, e)
out["metadata_error"] = str(e)
# The precedents this decision cites → link to the library; flag the ones not found. # The precedents this decision cites → link to the library; flag the ones not found.
try: try:
await cit_tools.extract_internal_citations(case_law_id=case_law_id, limit=0) await cit_tools.extract_internal_citations(case_law_id=case_law_id, limit=0)