צד-המטא-דאטה (precedent_metadata_extractor) קרא רק GEMINI_API_KEY בעוד בסביבה קיים GOOGLE_GEMINI_API_KEY — תוקן ב-PR #255 (fallback). הבאג המשני שנותר: כש- extract_and_apply החזיר 'no_metadata' (כשל-Gemini), מסלול-הדריינר process_pending_extractions התיישב ל-metadata_status='completed' ללא-תנאי, כך שהרשומה ננטשה בשקט עם מטא ריק והדריינר לא חזר אליה (נצפה: da2d9ccb '4491-02-21', 5fabdac5 '14306-09-23' — completed אך court/date/summary ריקים). תיקון (G1 — אבחנת-מקור): - extract_and_apply מבדיל תוצאה-ריקה: יש full_text → 'extraction_failed' (חולף, בר-retry); אין full_text → 'no_metadata' (אין מה לחלץ). - process_pending_extractions (metadata): 'extraction_failed' → חוזר ל-'pending' (משמר את חותם-התור) במקום להתיישב 'completed'. retry-loop הקיים מנסה שוב, ואחרי-מיצוי הרשומה נשארת בתור. מסלול reextract_metadata כבר עקבי (חוזר pending על כל מה שאינו completed/no_changes). תיקון-נתון (בוצע ידנית דרך כלי-MCP precedent_extract_metadata): da2d9ccb + 5fabdac5 חולצו-מחדש בהצלחה (court/date/summary/headnote/tags מלאים). 0 נותרו external 'completed-but-empty'. הערה: מפתח-Gemini אינו נדרש ב-Coolify — המחלץ רץ רק מקומית (precedent_library → extract_and_apply, host ~/.env עם GOOGLE_GEMINI_API_KEY); app.py מייבא רק את הקבוע PLACEHOLDER_PENDING_EXTRACTION, לא את פונקציית-החילוץ. בדיקות: test_metadata_extract_failure_status (transient/permanent/missing). כל 335 בדיקות mcp עוברות. guards נקיים. Invariants: G1 (אבחנת-מקור, לא התיישבות-בקריאה), INV-G3/X16 (עמידות — בר-retry), G12 (leak-guard נקי). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
1.9 KiB
Python
64 lines
1.9 KiB
Python
"""Regression test for #138 — metadata extraction must distinguish a transient
|
|
failure (Gemini hiccup despite the row having text) from a permanent empty
|
|
(no text to extract). Conflating them as 'no_metadata' let the drain settle the
|
|
row to 'completed' and silently strand it with empty metadata.
|
|
|
|
``extract_and_apply`` returns:
|
|
* ``extraction_failed`` when ``extract_metadata`` yields nothing BUT the row
|
|
has full_text → retryable.
|
|
* ``no_metadata`` when the row has no text → genuinely nothing to do.
|
|
|
|
Runs fully OFFLINE — monkeypatches ``extract_metadata`` and ``db.get_case_law``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from legal_mcp.services import db, precedent_metadata_extractor as pme
|
|
|
|
|
|
def _run(coro):
|
|
loop = asyncio.new_event_loop()
|
|
try:
|
|
return loop.run_until_complete(coro)
|
|
finally:
|
|
loop.close()
|
|
|
|
|
|
@pytest.fixture()
|
|
def empty_extract(monkeypatch: pytest.MonkeyPatch):
|
|
async def _empty(_cid):
|
|
return {}
|
|
monkeypatch.setattr(pme, "extract_metadata", _empty)
|
|
|
|
|
|
def test_empty_result_with_text_is_transient_failure(empty_extract, monkeypatch):
|
|
async def _rec(_cid):
|
|
return {"full_text": "פסק דין ארוך עם תוכן ממשי לחילוץ"}
|
|
monkeypatch.setattr(db, "get_case_law", _rec)
|
|
|
|
out = _run(pme.extract_and_apply(uuid4()))
|
|
assert out["status"] == "extraction_failed", out
|
|
|
|
|
|
def test_empty_result_without_text_is_no_metadata(empty_extract, monkeypatch):
|
|
async def _rec(_cid):
|
|
return {"full_text": ""}
|
|
monkeypatch.setattr(db, "get_case_law", _rec)
|
|
|
|
out = _run(pme.extract_and_apply(uuid4()))
|
|
assert out["status"] == "no_metadata", out
|
|
|
|
|
|
def test_missing_record_is_no_metadata(empty_extract, monkeypatch):
|
|
async def _none(_cid):
|
|
return None
|
|
monkeypatch.setattr(db, "get_case_law", _none)
|
|
|
|
out = _run(pme.extract_and_apply(uuid4()))
|
|
assert out["status"] == "no_metadata", out
|