/** * HTTP client for Ezer Mishpati legal-ai REST API. */ export class LegalApi { constructor(private baseUrl: string) {} private async request(path: string, init?: RequestInit): Promise { const res = await fetch(`${this.baseUrl}${path}`, { ...init, headers: { "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { const text = await res.text(); throw new Error(`Legal API ${res.status}: ${text}`); } return res.json() as Promise; } async listCases(): Promise { return this.request("/api/cases"); } async getCase(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/details`); } async createCase(data: CaseCreateInput): Promise { return this.request("/api/cases/create", { method: "POST", body: JSON.stringify(data), }); } async updateCase( caseNumber: string, data: CaseUpdateInput, ): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}`, { method: "PUT", body: JSON.stringify(data), }); } async getCaseStatus(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/status`); } async search( query: string, limit = 10, sectionType = "", ): Promise { const params = new URLSearchParams({ query, limit: String(limit) }); if (sectionType) params.set("section_type", sectionType); return this.request(`/api/search?${params}`); } async searchCase( caseNumber: string, query: string, limit = 10, ): Promise { const params = new URLSearchParams({ query, limit: String(limit) }); return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/search?${params}`, ); } async getTemplate(caseNumber: string): Promise<{ template: string }> { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/template`, ); } async getProcessingStatus(): Promise { return this.request("/api/processing-status"); } async health(): Promise<{ status: string }> { return this.request("/health"); } // ── New methods for expanded workflow ── async listDocuments(caseNumber: string): Promise { const details = await this.getCase(caseNumber); return details.documents || []; } async getDocumentText(docId: string): Promise { return this.request(`/api/documents/${docId}/text`); } async findSimilarCases(caseNumber: string): Promise { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/search?query=similar&limit=5`, ); } async setOutcome( caseNumber: string, outcome: string, reasoning?: string, ): Promise<{ status: string }> { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/outcome`, { method: "POST", body: JSON.stringify({ outcome, reasoning: reasoning || "" }), }, ); } async getClaims(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/claims`); } async setDirection( caseNumber: string, directionDoc: Record, ): Promise<{ status: string }> { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/direction`, { method: "POST", body: JSON.stringify({ direction_doc: directionDoc }), }, ); } async runQA(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/qa`, { method: "POST", }); } async triggerLearning(caseNumber: string): Promise<{ status: string }> { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/learn`, { method: "POST", }); } async getStyleGuide(): Promise { // Return static style guide reference return "ראה skill-legal-decision/SKILL.md — מדריך סגנון מלא של דפנה תמיר"; } /** * Recent conclusions of prior heartbeat runs across a case's issues (#220, * "seance"). Lets a resuming agent read what earlier sessions concluded * instead of re-deriving context from scratch. */ async getPredecessorForCase( caseNumber: string, limit = 5, ): Promise { return this.request( `/api/operations/cases/${encodeURIComponent(caseNumber)}/predecessor?limit=${limit}`, ); } } // Types export interface CaseSummary { case_number: string; title: string; status: string; } export interface CaseDetails { id: string; case_number: string; title: string; status: string; appellants: string[]; respondents: string[]; subject: string; property_address: string; expected_outcome: string; documents?: DocumentInfo[]; [key: string]: unknown; } export interface CaseCreateInput { case_number: string; title: string; appellants?: string[]; respondents?: string[]; subject?: string; property_address?: string; permit_number?: string; committee_type?: string; hearing_date?: string; notes?: string; expected_outcome?: string; } export interface CaseUpdateInput { status?: string; title?: string; subject?: string; notes?: string; hearing_date?: string; decision_date?: string; tags?: string[]; expected_outcome?: string; } export interface DocumentInfo { id: string; title: string; doc_type: string; extraction_status: string; page_count?: number; } export interface WorkflowStatus { case_number: string; title: string; status: string; documents: Array<{ title: string; type: string; extraction: string; chunks: number; pages?: number; }>; total_documents: number; total_chunks: number; has_draft: boolean; draft_size_bytes: number; next_steps: string[]; } export interface SearchResult { score: number; case_number?: string; document: string; section: string; page: number; content: string; } export interface ProcessingStatus { cases: number; documents: number; pending_processing: number; chunks: number; style_corpus_entries: number; style_patterns: number; } export interface ClaimsResponse { case_number: string; claims: Record< string, Array<{ party_role: string; claim_text: string; claim_index: number }> >; total: number; } export interface QAResponse { passed: boolean; checks: Array<{ check_name: string; passed: boolean; severity: string; details: string; }>; status: string; } export interface PredecessorRun { run_id: string; status: string; started_at: string | null; finished_at: string | null; summary: string | null; error_code: string | null; session_id: string | null; agent_name: string | null; identifier: string | null; } export interface PredecessorResponse { ok: boolean; case_number: string; runs: PredecessorRun[]; }