diff --git a/scripts/SCRIPTS.md b/scripts/SCRIPTS.md index ed3a48f..b6a80e0 100644 --- a/scripts/SCRIPTS.md +++ b/scripts/SCRIPTS.md @@ -30,6 +30,7 @@ | `backfill_chunk_pages.py` | python | Backfill `page_number` ב-`document_chunks` קיימים. legacy chunker לא tracked עמודים → `page_number=NULL` חוסם boost של multimodal hybrid (text+image join על אותו עמוד). re-extracts כל PDF (re-OCR אם צריך, ~$0.0015/page), מחשב page_offsets, ומעדכן chunks. idempotent | ידני per-case (`python backfill_chunk_pages.py 8174-24 8137-24`) | | `backfill_legal_arguments.py` | python | Backfill `legal_arguments` לתיקים עם `claims` קיימים (TaskMaster #36). מקבץ פרופוזיציות גולמיות לטיעונים משפטיים מובחנים (~6-12 לכל צד) דרך `argument_aggregator.aggregate_claims_to_arguments` (Claude CLI). תומך `--dry-run`/`--apply`/`--force`/`--case ...`. **חייב לרוץ מהמכונה המקומית** (לא קונטיינר) — `claude_session` דורש Claude CLI | ידני per-case (`python scripts/backfill_legal_arguments.py --apply --case 1017-03-26`) | | `upload_blam_decisions.py` | python | חד-פעמי (2026-05-26) — העלאת 2 החלטות בל"מ ל-`case_law` (8126/24 סופר נוח, 8047/23 הרנון) דרך `ingest_internal_decision` ישיר, עוקף MCP server שטרם נטען מחדש אחרי הוספת `proceeding_type`. **לא להריץ שוב** | חד-פעמי — להעביר ל-`.archive/` בהזדמנות | +| `process_pending_blam.py` | python | חד-פעמי (2026-05-26) — הרצת metadata + halacha extraction על 2 החלטות בל"מ שעלו ב-`upload_blam_decisions.py`. עוקף MCP (אותו טעם). **לא להריץ שוב** | חד-פעמי — להעביר ל-`.archive/` בהזדמנות | ## תיקיית `.archive/` — סקריפטים שהושלמו diff --git a/scripts/process_pending_blam.py b/scripts/process_pending_blam.py new file mode 100644 index 0000000..d61c972 --- /dev/null +++ b/scripts/process_pending_blam.py @@ -0,0 +1,53 @@ +"""One-shot: run pending metadata + halacha extraction on the 2 בל"מ +decisions uploaded today (8126/24 + 8047/23). Bypasses MCP because the +running MCP server has stale code; calls the services directly with the +updated local copy. + +Run from /home/chaim/legal-ai with the venv: + POSTGRES_URL=... .venv/bin/python scripts/process_pending_blam.py +""" + +import asyncio +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "mcp-server", "src")) + +from legal_mcp.services import db +from legal_mcp.services import precedent_library + + +async def main(): + # Queue metadata extraction too (ingest_internal_decision only queues + # halacha; metadata fills headnote/summary/key_quote and now also + # confirms proceeding_type via the new prompt field). + pool = await db.get_pool() + async with pool.acquire() as conn: + rows = await conn.fetch( + "SELECT id, case_number FROM case_law " + "WHERE case_number IN ('8126/24','8047/23') " + " AND source_kind = 'internal_committee'" + ) + for r in rows: + await conn.execute( + "UPDATE case_law SET metadata_extraction_requested_at = NOW() " + "WHERE id = $1", + r["id"], + ) + print(f"queued metadata for {r['case_number']} ({r['id']})") + + print("\n→ running metadata extraction…") + meta_result = await precedent_library.process_pending_extractions( + kind="metadata", limit=10, + ) + print(meta_result) + + print("\n→ running halacha extraction…") + halacha_result = await precedent_library.process_pending_extractions( + kind="halacha", limit=10, + ) + print(halacha_result) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/web-ui/src/components/cases/case-header.tsx b/web-ui/src/components/cases/case-header.tsx index 439de5b..e54b0b5 100644 --- a/web-ui/src/components/cases/case-header.tsx +++ b/web-ui/src/components/cases/case-header.tsx @@ -63,7 +63,7 @@ export function CaseHeader({ data }: { data?: CaseDetail }) { )} )} - {isBlamSubtype(data?.appeal_subtype) && ( + {(data?.proceeding_type === 'בל"מ' || isBlamSubtype(data?.appeal_subtype)) && ( [] = [ header: "כותרת", cell: ({ row }) => (
- {isBlamSubtype(row.original.appeal_subtype) && ( + {(row.original.proceeding_type === 'בל"מ' || isBlamSubtype(row.original.appeal_subtype)) && ( { const all = cases ?? []; - if (blamFilter === "blam") return all.filter((c) => isBlamSubtype(c.appeal_subtype)); - if (blamFilter === "regular") return all.filter((c) => !isBlamSubtype(c.appeal_subtype)); + const isBlam = (c: Case) => + c.proceeding_type === 'בל"מ' || isBlamSubtype(c.appeal_subtype); + if (blamFilter === "blam") return all.filter(isBlam); + if (blamFilter === "regular") return all.filter((c) => !isBlam(c)); return all; }, [cases, blamFilter]); diff --git a/web-ui/src/lib/api/types.ts b/web-ui/src/lib/api/types.ts index 1f27496..965a03e 100644 --- a/web-ui/src/lib/api/types.ts +++ b/web-ui/src/lib/api/types.ts @@ -777,6 +777,48 @@ export interface paths { patch?: never; trace?: never; }; + "/api/cases/{case_number}/aggregate-arguments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Api Aggregate Arguments + * @description Aggregate raw claims into distinct legal arguments via Claude. + * + * Runs as a BackgroundTask because the LLM pass can take 30-90 seconds. + */ + post: operations["api_aggregate_arguments_api_cases__case_number__aggregate_arguments_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/cases/{case_number}/legal-arguments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Api Get Legal Arguments + * @description Return aggregated legal arguments for a case, grouped by party. + */ + get: operations["api_get_legal_arguments_api_cases__case_number__legal_arguments_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/cases/{case_number}/direction": { parameters: { query?: never; @@ -2255,6 +2297,75 @@ export interface paths { patch: operations["halacha_update_api_halachot__halacha_id__patch"]; trace?: never; }; + "/api/missing-precedents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Missing Precedents List + * @description List missing precedents, optionally filtered by status / case. + */ + get: operations["missing_precedents_list_api_missing_precedents_get"]; + put?: never; + /** + * Missing Precedent Create + * @description Log a new missing precedent (status='open'). Dedupes by + * (citation, cited_in_case_id) — duplicate POST returns the existing row. + */ + post: operations["missing_precedent_create_api_missing_precedents_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/missing-precedents/{mp_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Missing Precedent Get */ + get: operations["missing_precedent_get_api_missing_precedents__mp_id__get"]; + put?: never; + post?: never; + /** Missing Precedent Delete */ + delete: operations["missing_precedent_delete_api_missing_precedents__mp_id__delete"]; + options?: never; + head?: never; + /** Missing Precedent Update */ + patch: operations["missing_precedent_update_api_missing_precedents__mp_id__patch"]; + trace?: never; + }; + "/api/missing-precedents/{mp_id}/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Missing Precedent Upload + * @description Upload the decision file behind a missing-precedent and link it. + * + * Routes to ingest_internal_decision if the citation looks like a + * committee decision (ערר / בל"מ prefix), otherwise to ingest_precedent. + * Once the case_law row is created, the missing_precedents row is marked + * status='closed' with linked_case_law_id pointing to the new row. + */ + post: operations["missing_precedent_upload_api_missing_precedents__mp_id__upload_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { @@ -2388,6 +2499,81 @@ export interface components { */ summary: string; }; + /** Body_missing_precedent_upload_api_missing_precedents__mp_id__upload_post */ + Body_missing_precedent_upload_api_missing_precedents__mp_id__upload_post: { + /** File */ + file: string; + /** + * Case Number + * @default + */ + case_number: string; + /** + * Chair Name + * @default + */ + chair_name: string; + /** + * District + * @default + */ + district: string; + /** + * Case Name + * @default + */ + case_name: string; + /** + * Court + * @default + */ + court: string; + /** + * Decision Date + * @default + */ + decision_date: string; + /** + * Practice Area + * @default + */ + practice_area: string; + /** + * Appeal Subtype + * @default + */ + appeal_subtype: string; + /** + * Subject Tags + * @default [] + */ + subject_tags: string; + /** + * Is Binding + * @default true + */ + is_binding: boolean; + /** + * Headnote + * @default + */ + headnote: string; + /** + * Summary + * @default + */ + summary: string; + /** + * Precedent Level + * @default + */ + precedent_level: string; + /** + * Source Type + * @default + */ + source_type: string; + }; /** Body_precedent_library_upload_api_precedent_library_upload_post */ Body_precedent_library_upload_api_precedent_library_upload_post: { /** File */ @@ -2507,7 +2693,7 @@ export interface components { expected_outcome: string; /** * Practice Area - * @default appeals_committee + * @default */ practice_area: string; /** @@ -2515,6 +2701,11 @@ export interface components { * @default */ appeal_subtype: string; + /** + * Proceeding Type + * @default + */ + proceeding_type: string; }; /** CaseUpdateRequest */ CaseUpdateRequest: { @@ -2555,6 +2746,25 @@ export interface components { * @default */ expected_outcome: string; + /** Appellants */ + appellants?: string[] | null; + /** Respondents */ + respondents?: string[] | null; + /** + * Property Address + * @default + */ + property_address: string; + /** + * Permit Number + * @default + */ + permit_number: string; + /** + * Proceeding Type + * @default + */ + proceeding_type: string; }; /** ChairPositionRequest */ ChairPositionRequest: { @@ -2681,6 +2891,57 @@ export interface components { /** Value */ value: unknown; }; + /** MissingPrecedentCreate */ + MissingPrecedentCreate: { + /** Citation */ + citation: string; + /** + * Case Number + * @default + */ + case_number: string; + /** Cited In Document Id */ + cited_in_document_id?: string | null; + /** + * Cited By Party + * @default unknown + * @enum {string} + */ + cited_by_party: "appellant" | "respondent" | "committee" | "permit_applicant" | "unknown"; + /** Cited By Party Name */ + cited_by_party_name?: string | null; + /** Legal Topic */ + legal_topic?: string | null; + /** Legal Issue */ + legal_issue?: string | null; + /** Claim Quote */ + claim_quote?: string | null; + /** Case Name */ + case_name?: string | null; + /** Notes */ + notes?: string | null; + }; + /** MissingPrecedentPatch */ + MissingPrecedentPatch: { + /** Legal Topic */ + legal_topic?: string | null; + /** Legal Issue */ + legal_issue?: string | null; + /** Notes */ + notes?: string | null; + /** Cited By Party */ + cited_by_party?: ("appellant" | "respondent" | "committee" | "permit_applicant" | "unknown") | null; + /** Cited By Party Name */ + cited_by_party_name?: string | null; + /** Case Name */ + case_name?: string | null; + /** Status */ + status?: ("open" | "uploaded" | "closed" | "irrelevant") | null; + /** Citation */ + citation?: string | null; + /** Claim Quote */ + claim_quote?: string | null; + }; /** OutcomeRequest */ OutcomeRequest: { /** Outcome */ @@ -2768,6 +3029,10 @@ export interface components { precedent_level?: string | null; /** Is Binding */ is_binding?: boolean | null; + /** District */ + district?: string | null; + /** Chair Name */ + chair_name?: string | null; }; /** ReviseRequest */ ReviseRequest: { @@ -3908,6 +4173,72 @@ export interface operations { }; }; }; + api_aggregate_arguments_api_cases__case_number__aggregate_arguments_post: { + parameters: { + query?: { + force?: boolean; + }; + header?: never; + path: { + case_number: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + api_get_legal_arguments_api_cases__case_number__legal_arguments_get: { + parameters: { + query?: { + party?: string; + }; + header?: never; + path: { + case_number: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; api_set_direction_api_cases__case_number__direction_post: { parameters: { query?: never; @@ -6333,4 +6664,205 @@ export interface operations { }; }; }; + missing_precedents_list_api_missing_precedents_get: { + parameters: { + query?: { + status?: string; + case_id?: string; + case_number?: string; + legal_topic?: string; + limit?: number; + offset?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + missing_precedent_create_api_missing_precedents_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MissingPrecedentCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + missing_precedent_get_api_missing_precedents__mp_id__get: { + parameters: { + query?: never; + header?: never; + path: { + mp_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + missing_precedent_delete_api_missing_precedents__mp_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + mp_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + missing_precedent_update_api_missing_precedents__mp_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + mp_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MissingPrecedentPatch"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + missing_precedent_upload_api_missing_precedents__mp_id__upload_post: { + parameters: { + query?: never; + header?: never; + path: { + mp_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_missing_precedent_upload_api_missing_precedents__mp_id__upload_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; }