import { Response } from 'express'; import { supabase } from '../lib/supabase'; import { AuthenticatedRequest } from '../middleware/auth'; import { runIddianameAnalysis, runCaseNarrativeAnalysis, runStatementAnalysis } from './document.controller'; import { findOrCreateCaseByTitle } from '../lib/caseLookup'; export const createCase = async (req: AuthenticatedRequest, res: Response) => { try { const userId = req.user?.id; const title = String(req.body?.title || 'Yeni Sohbet').slice(0, 200); // kind ayrımı: 'case' = Dava Dosyaları'nda listelenen gerçek dosyalar, // 'chat' = Sohbet ekranındaki "Yeni Sohbet" ile açılan, dosya olmayan konuşmalar. // Dava Dosyaları listesi sadece kind='case' olanları gösteriyor. const kind = req.body?.kind === 'chat' ? 'chat' : 'case'; // requireAuth doğrudan kullanıcının kendi id'sini set ediyor; başkasının hesabına // dosya oluşturulamaz. Yazma frontend'in anon client'ı yerine burada (service_role // ile) yapılıyor, cases için de RLS engeli yaşanmasın diye. const { data, error } = await supabase .from('cases') .insert([{ title, user_id: userId, kind }]) .select('id, title') .single(); if (error) throw error; res.json({ case: data }); } catch (error: any) { console.error('Create Case Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; // Türkçe İ/I harflerinin regex /i bayrağıyla güvenilir eşleşmemesi (Unicode // case-folding sorunu) yüzünden manuel normalize ediyoruz. 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 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); 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 // kuyruğunda bekleyen evraklar sadece Electron'un yerel durumunda görünüyor, // backend'in bundan haberi yok. export const summarizeCase = async (req: AuthenticatedRequest, res: Response) => { try { const { caseId } = req.params; if (!caseId) { return res.status(400).json({ error: 'Geçersiz caseId' }); } const { data: docs, error: docsError } = await supabase .from('documents') .select('filename, extracted_text') .eq('case_id', caseId) .order('uploaded_at', { ascending: true }); if (docsError) throw docsError; const withText = (docs || []).filter((d) => d.extracted_text && String(d.extracted_text).trim()); if (withText.length === 0) { return res.status(400).json({ error: 'Bu dosyada metni çıkarılmış belge yok, özet hazırlanamıyor.' }); } const iddianameDoc = withText.find((d) => normalizeTr(d.filename).includes('iddianame') || normalizeTr(String(d.extracted_text)).slice(0, 500).includes('iddianame') ); let iddianameJson: any = null; if (iddianameDoc) { try { const iddianameResult = await runIddianameAnalysis(String(iddianameDoc.extracted_text)); iddianameJson = iddianameResult.summaryJson; } catch (e) { console.error('İddianame ön analizi başarısız, rapora iddianamesiz devam ediliyor:', e); } } // 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İ (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 runCaseNarrativeAnalysis(analysisInput); const { data: analysisRecord, error: analysisError } = await supabase .from('analyses') .insert([{ case_id: caseId, document_id: null, summary_json: summaryJson, model_used: aiModel }]) .select() .single(); if (analysisError) throw analysisError; res.json({ analysis: analysisRecord }); } catch (error: any) { console.error('Summarize Case Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; // Çelişki Avcısı: İfade ve sorgu evraklarını alıp analiz eder. export const analyzeStatements = async (req: AuthenticatedRequest, res: Response) => { try { const { caseId } = req.params; if (!caseId) { return res.status(400).json({ error: 'Geçersiz caseId' }); } const { data: docs, error: docsError } = await supabase .from('documents') .select('filename, extracted_text') .eq('case_id', caseId) .order('uploaded_at', { ascending: true }); if (docsError) throw docsError; // Sadece ifade, sorgu, beyan, tutanak, duruşma içeren belgeleri filtrele const keywords = ['ifade', 'sorgu', 'beyan', 'tutanak', 'duruşma']; const statementDocs = (docs || []).filter((d) => { if (!d.extracted_text || !String(d.extracted_text).trim()) return false; const fn = normalizeTr(d.filename); return keywords.some(kw => fn.includes(kw)); }); if (statementDocs.length === 0) { return res.status(400).json({ error: 'Bu dosyada ifade/beyan evrakı bulunamadı.' }); } const perDocLimit = Math.max(1000, Math.floor(15000 / statementDocs.length)); const combinedText = statementDocs .map((d) => `--- ${d.filename} ---\n${String(d.extracted_text).slice(0, perDocLimit)}`) .join('\n\n'); const analysisInput = `Aşağıdaki belgeler sadece ifade ve sorgu evraklarıdır:\n\n${combinedText}`; const { summaryJson, aiModel } = await runStatementAnalysis(analysisInput); // Çelişki analizi sonucunu 'analyses' tablosuna kaydet const { data: analysisRecord, error: analysisError } = await supabase .from('analyses') .insert([{ case_id: caseId, document_id: null, summary_json: { statementAnalysis: summaryJson }, model_used: aiModel }]) .select() .single(); if (analysisError) throw analysisError; res.json({ analysis: analysisRecord }); } catch (error: any) { console.error('Analyze Statements Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; // UYAP Evrak İndirici eklentisinin "Taraf Bilgileri" sekmesinden yakaladığı // davacı/davalı/sanık + vekil bilgisini kaydeder. AI'nin OCR metninden tahmin // ettiği "Taraflar" alanından çok daha güvenilir — doğrudan UYAP'ın yapılandırılmış // verisi. case_title ile eşleştirme registerLocalDocument ile aynı mantık. export const updateCaseParties = async (req: AuthenticatedRequest, res: Response) => { try { const userId = req.user?.id; const { case_title, parties } = req.body || {}; if (!case_title || !Array.isArray(parties) || parties.length === 0) { return res.status(400).json({ error: 'case_title ve parties zorunludur.' }); } const caseId = await findOrCreateCaseByTitle(userId as string, case_title); const { data, error } = await supabase .from('cases') .update({ parties }) .eq('id', caseId) .select('id, title, parties') .single(); if (error) throw error; res.json({ case: data }); } catch (error: any) { console.error('Update Case Parties Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; export const deleteCase = async (req: AuthenticatedRequest, res: Response) => { try { const { caseId } = req.params; if (!caseId) { return res.status(400).json({ error: 'Geçersiz caseId' }); } // requireAuth zaten bu caseId'nin isteği yapan kullanıcıya ait olduğunu doğruladı. // documents/analyses/chat_messages tabloları case_id üzerinden ON DELETE CASCADE // ile tanımlı (bkz. sql/1.sql), yani cases satırını silmek hepsini birlikte siliyor. const { error } = await supabase.from('cases').delete().eq('id', caseId); if (error) throw error; res.json({ ok: true }); } catch (error: any) { console.error('Delete Case Error:', error); res.status(500).json({ error: error.message || 'Internal Server Error' }); } };