diff --git a/mcp-server/src/legal_mcp/services/precedent_library.py b/mcp-server/src/legal_mcp/services/precedent_library.py index 8de10b2..0cab8d8 100644 --- a/mcp-server/src/legal_mcp/services/precedent_library.py +++ b/mcp-server/src/legal_mcp/services/precedent_library.py @@ -383,7 +383,10 @@ async def reextract_metadata( appeal_subtype, and case_name when it equals the citation). User 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 diff --git a/web/app.py b/web/app.py index a1b141a..1d2a4c6 100644 --- a/web/app.py +++ b/web/app.py @@ -3643,9 +3643,11 @@ async def _enroll_final_in_library( out["error"] = "no final text extracted" return out - # Deterministic metadata from the case record — the Gemini metadata extractor is - # tuned for EXTERNAL rulings and returns no_metadata for internal decisions, so we - # populate proceeding_type / date / tags / summary / citation ourselves (no LLM). + # Deterministic seeds from the case record first — proceeding_type / date / + # citation are identity, not inference, so we set them ourselves (no LLM) and the + # 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 = "ירושלים" proceeding_type = (case.get("proceeding_type") or "ערר").strip() 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: 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. try: await cit_tools.extract_internal_citations(case_law_id=case_law_id, limit=0)