diff --git a/src/controllers/case.controller.ts b/src/controllers/case.controller.ts index 6d75886..8481bbf 100644 --- a/src/controllers/case.controller.ts +++ b/src/controllers/case.controller.ts @@ -1,7 +1,7 @@ import { Response } from 'express'; import { supabase } from '../lib/supabase'; import { AuthenticatedRequest } from '../middleware/auth'; -import { runDocumentAnalysis, runIddianameAnalysis } from './document.controller'; +import { runIddianameAnalysis, runCaseNarrativeAnalysis } from './document.controller'; import { findOrCreateCaseByTitle } from '../lib/caseLookup'; export const createCase = async (req: AuthenticatedRequest, res: Response) => { @@ -34,15 +34,15 @@ function normalizeTr(s: string): string { return s.replace(/İ/g, 'i').replace(/I/g, 'i').toLowerCase(); } -// Dava dosyasındaki TÜM belgelerin çıkarılmış metnini birleştirip tek bir AI -// özeti üretir (Dosya Ayrıntı ekranındaki "Özet Hazırla" butonu). uploadDocument'ın -// aksine tek bir belgeye değil, dosyanın o anki tüm belgelerine bakıyor. +// Dava dosyasındaki TÜM belgelerin çıkarılmış metnini birleştirip kapsamlı, anlatı +// biçiminde bir "Dosya Özeti" raporu üretir (Dosya Ayrıntı ekranındaki "Özet Hazırla" +// butonu). uploadDocument'ın aksine tek bir belgeye değil, dosyanın o anki tüm +// belgelerine bakıyor. // // Dosyada bir iddianame varsa önce SADECE onun üzerinden dar kapsamlı bir ön -// çıkarım yapılıyor (isnat edilen suç, iddia özeti, kritik tarihler); genel -// değerlendirme bu ön çıkarımı bağlam olarak alıp tüm belgelerle birlikte -// üretiliyor — böylece iddianamedeki temel olgular genel değerlendirmenin -// başlangıç noktası oluyor, kaybolmuyor. +// çıkarım yapılıyor (isnat edilen suç, iddia özeti, kritik tarihler); anlatı +// raporu bu ön çıkarımı bağlam olarak alıp tüm belgelerle birlikte üretiliyor — +// böylece iddianamedeki temel olgular raporun başlangıç noktası oluyor. // // Not: "Bu dosyadaki tüm evraklar işlenene kadar bekle" kontrolü burada DEĞİL, // frontend'de (CaseDetail) yapılıyor — çünkü eklentiden indirilip henüz OCR/kayıt @@ -78,26 +78,22 @@ export const summarizeCase = async (req: AuthenticatedRequest, res: Response) => const iddianameResult = await runIddianameAnalysis(String(iddianameDoc.extracted_text)); iddianameJson = iddianameResult.summaryJson; } catch (e) { - console.error('İddianame ön analizi başarısız, genel değerlendirmeye iddianamesiz devam ediliyor:', e); + console.error('İddianame ön analizi başarısız, rapora iddianamesiz devam ediliyor:', e); } } - // Tüm belgeleri modele gönderiyoruz; runDocumentAnalysis toplamı ~15000 karaktere - // kırpıyor, o yüzden belge sayısına göre kabaca dengeli bir pay ayırıyoruz. + // Tüm belgeleri modele gönderiyoruz; ~15000 karaktere kırpılıyor, o yüzden belge + // sayısına göre kabaca dengeli bir pay ayırıyoruz. const perDocLimit = Math.max(1000, Math.floor(15000 / withText.length)); const combinedText = withText .map((d) => `--- ${d.filename} ---\n${String(d.extracted_text).slice(0, perDocLimit)}`) .join('\n\n'); const analysisInput = iddianameJson - ? `İDDİANAME ÖN ANALİZİ (genel değerlendirmeyi buna dayandır):\n${JSON.stringify(iddianameJson)}\n\n---\n\nDOSYADAKİ TÜM BELGELER:\n${combinedText}` + ? `İDDİANAME ÖN ANALİZİ (rapora bunu öncelikli bağlam olarak yansıt):\n${JSON.stringify(iddianameJson)}\n\n---\n\nDOSYADAKİ TÜM BELGELER:\n${combinedText}` : combinedText; - const { summaryJson, aiModel } = await runDocumentAnalysis(analysisInput); - if (iddianameJson) { - summaryJson.isnatEdilenSuc = iddianameJson.isnatEdilenSuc || null; - summaryJson.iddianameOzeti = iddianameJson.iddiaOzeti || null; - } + const { summaryJson, aiModel } = await runCaseNarrativeAnalysis(analysisInput); const { data: analysisRecord, error: analysisError } = await supabase .from('analyses') diff --git a/src/controllers/document.controller.ts b/src/controllers/document.controller.ts index cd1129a..492c247 100644 --- a/src/controllers/document.controller.ts +++ b/src/controllers/document.controller.ts @@ -31,7 +31,44 @@ const IDDIANAME_SYSTEM_PROMPT = ` Sadece ve sadece JSON döndür, başka hiçbir açıklama ekleme. `; +// Dosya Ayrıntı ekranındaki "Dosya Özeti" kartı için — kısa alan-alan JSON yerine +// verilen örnekteki gibi kapsamlı, bölümlere ayrılmış bir anlatı raporu istiyoruz. +// Serbest metin JSON içine gömülüp modelin onu doğru kaçışlaması (escape) riskli +// olduğundan, bunun çıktısını JSON olarak PARSE ETMİYORUZ — ham metni alıp +// { ozet: } olarak biz sarıyoruz (bkz. callAiModelRaw / runCaseNarrativeAnalysis). +const CASE_NARRATIVE_SYSTEM_PROMPT = ` + Sen uzman bir Türk avukatısın. Sana bir dava dosyasındaki TÜM belgelerin metni + verilecek (varsa önce bir iddianame ön analizi de eklenmiş olabilir, buna öncelik ver). + Bu dosya hakkında kapsamlı, iyi yapılandırılmış bir "Dosya Özeti" raporu yaz. + + Rapor Markdown formatında olsun ve şu yapıyı izlesin: + - Kısa bir giriş paragrafı: mahkeme/daire, esas no'su ve konusu. + - "1. Olayın/Uyuşmazlığın Özeti" — ne olduğunu anlat. + - "2. Yargılama Süreci ve Kararlar" — ilk derece, istinaf/temyiz, bozma vb. aşamaları madde madde anlat. + - "3. Tarafların İddia ve Savunmaları" — davacı/davalı ya da sanık/müştekinin ne dediğini özetle. + - "4. Önemli Bulgular ve Belgeler" — bilirkişi raporu, banka kayıtları, HTS/baz istasyonu, tanık ifadeleri gibi somut delilleri madde madde listele. + - Kısa bir "Sonuç" paragrafı: dosyanın şu anki durumu ve beklenen sonraki adım. + + Dava türüne göre terminolojiyi uyarla (ceza davasında "sanık/müşteki", hukuk davasında + "davacı/davalı", icra dosyasında "alacaklı/borçlu" gibi). SADECE dosyada gerçekten + geçen bilgileri kullan, uydurma bilgi ekleme; bir bölüm için yeterli bilgi yoksa o + bölümü kısa geç veya atla. SADECE Markdown metni döndür — JSON, kod bloğu işareti + veya başka bir açıklama ekleme. + `; + async function callAiModel(systemPrompt: string, userText: string): Promise<{ resultJson: any; aiModel: string }> { + const { aiContent, aiModel } = await callAiModelRaw(systemPrompt, userText); + let resultJson: any = {}; + try { + resultJson = JSON.parse(aiContent || '{}'); + } catch (e) { + console.error('Failed to parse AI response as JSON:', aiContent); + resultJson = { error: 'AI did not return valid JSON', raw: aiContent }; + } + return { resultJson, aiModel }; +} + +async function callAiModelRaw(systemPrompt: string, userText: string): Promise<{ aiContent: string; aiModel: string }> { const { runpodUrl, aiModel } = getAiConfig(); const response = await fetch(`${runpodUrl}/v1/chat/completions`, { @@ -53,19 +90,11 @@ async function callAiModel(systemPrompt: string, userText: string): Promise<{ re } const data = await response.json(); - const aiContent = data.message?.content || (data.choices && data.choices[0]?.message?.content); - let resultJson: any = {}; - try { - resultJson = JSON.parse(aiContent || '{}'); - } catch (e) { - console.error('Failed to parse AI response as JSON:', aiContent); - resultJson = { error: 'AI did not return valid JSON', raw: aiContent }; - } - return { resultJson, aiModel }; + const aiContent = data.message?.content || (data.choices && data.choices[0]?.message?.content) || ''; + return { aiContent: String(aiContent).trim(), aiModel }; } -// uploadDocument, registerLocalDocument ve summarizeCase'in (case.controller.ts) -// ortak kullandığı genel analiz adımı. +// uploadDocument, registerLocalDocument'in kullandığı genel (kısa, alan-alan) analiz adımı. export async function runDocumentAnalysis(extractedText: string): Promise<{ summaryJson: any; aiModel: string }> { const { resultJson, aiModel } = await callAiModel(ANALYSIS_SYSTEM_PROMPT, extractedText); return { summaryJson: resultJson, aiModel }; @@ -77,6 +106,13 @@ export async function runIddianameAnalysis(extractedText: string): Promise<{ sum return { summaryJson: resultJson, aiModel }; } +// summarizeCase'in Dosya Özeti raporu için kullandığı adım — çıktı serbest Markdown +// metni, JSON'a çevrilmeye çalışılmıyor. +export async function runCaseNarrativeAnalysis(combinedText: string): Promise<{ summaryJson: any; aiModel: string }> { + const { aiContent, aiModel } = await callAiModelRaw(CASE_NARRATIVE_SYSTEM_PROMPT, combinedText); + return { summaryJson: { ozet: aiContent }, aiModel }; +} + // UYAP Evrak İndirici eklentisinden gelen, Electron ana sürecinde YERELDE metni // çıkarılmış belgeleri kaydeder. Ham dosya baytları bu isteğe hiç dahil değil — // sadece çıkarılan metin (JSON) geliyor, Supabase Storage'a hiçbir yükleme yapılmıyor.