diff --git a/src/controllers/case.controller.ts b/src/controllers/case.controller.ts index afbd063..2e05795 100644 --- a/src/controllers/case.controller.ts +++ b/src/controllers/case.controller.ts @@ -17,6 +17,28 @@ function toTraceableLegislation(matches: LegislationMatch[]) { return matches.map(({ maddeNo, baslik, kanun }) => ({ maddeNo, baslik, kanun })); } +interface RequestDocument { filename: string; extracted_text: string | null; } + +// Faz 2 — documents artık kalıcı olarak Postgres'te tutulmuyor (Faz 3'te +// yazma da kaldırılacak); istek gövdesinde gönderilen belgeler (Electron'un +// yerel SQLite'tan okuyup gönderdiği) ÖNCELİKLİ kaynak. Gövdede belge yoksa +// (eski istemci sürümü, web vb.) mevcut Postgres sorgusuna düşülür — geçiş +// süresince iki kaynak da destekleniyor. +async function resolveCaseDocuments(caseId: string, bodyDocuments: unknown): Promise { + if (Array.isArray(bodyDocuments) && bodyDocuments.length > 0) { + return bodyDocuments + .map((d: any) => ({ filename: String(d?.filename ?? d?.name ?? ''), extracted_text: d?.extracted_text ?? null })) + .filter((d) => d.filename); + } + const { data, error } = await supabase + .from('documents') + .select('filename, extracted_text') + .eq('case_id', caseId) + .order('uploaded_at', { ascending: true }); + if (error) throw error; + return data || []; +} + export const createCase = async (req: AuthenticatedRequest, res: Response) => { try { const userId = req.user?.id; @@ -68,14 +90,9 @@ export const summarizeCase = async (req: AuthenticatedRequest, res: Response) => 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 docs = await resolveCaseDocuments(String(caseId), req.body?.documents); - const withText = (docs || []).filter((d) => d.extracted_text && String(d.extracted_text).trim()); + 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.' }); } @@ -132,19 +149,14 @@ export const analyzeStatements = async (req: AuthenticatedRequest, res: Response 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 docs = await resolveCaseDocuments(String(caseId), req.body?.documents); // Sadece ifade, sorgu, beyan, tutanak, duruşma içeren belgeleri filtrele. // "tutanak" tek başına çok geniş — "Tebligat Tutanağı" gibi ifade/beyan // İÇERMEYEN evrakları da yakalıyordu, çelişki analizine gürültü katıyordu. const keywords = ['ifade', 'sorgu', 'beyan', 'tutanak', 'duruşma']; const excludeKeywords = ['tebligat', 'tebliğ']; - const statementDocs = (docs || []).filter((d) => { + const statementDocs = docs.filter((d) => { if (!d.extracted_text || !String(d.extracted_text).trim()) return false; const fn = normalizeTr(d.filename); if (excludeKeywords.some(kw => fn.includes(kw))) return false; @@ -187,16 +199,11 @@ export const analyzeDeficiencies = async (req: AuthenticatedRequest, res: Respon 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 docs = await resolveCaseDocuments(String(caseId), req.body?.documents); // Çöp/makbuz niteliğindeki teknik zarf belgeleri hariç metni olan tüm evrakları dahil et const trashKeywords = ['makbuz', 'pul', 'zarf', 'masraf', 'havale', 'barkod', 'alındı', 'görüldü', 'ilan']; - const targetDocs = (docs || []).filter((d) => { + const targetDocs = docs.filter((d) => { if (!d.extracted_text || !String(d.extracted_text).trim()) return false; const fn = normalizeTr(d.filename); return !trashKeywords.some((kw) => fn.includes(kw)); @@ -245,16 +252,11 @@ export const analyzeStrategy = async (req: AuthenticatedRequest, res: Response) return res.status(400).json({ error: 'clientName (Müvekkil Adı) zorunludur' }); } - 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 docs = await resolveCaseDocuments(String(caseId), req.body?.documents); // Gereksiz makbuz, pul vb. hariç tüm evrakları strateji için kullan const trashKeywords = ['tebligat', 'makbuz', 'pul', 'zarf', 'masraf', 'havale', 'barkod', 'alındı', 'görüldü', 'ilan', 'talimat', 'nüfus', 'sicil', 'sabıka']; - const targetDocs = (docs || []).filter(d => { + const targetDocs = docs.filter(d => { if (!d.extracted_text || !String(d.extracted_text).trim()) return false; const fn = normalizeTr(d.filename); return !trashKeywords.some(kw => fn.includes(kw)); @@ -302,15 +304,10 @@ export const analyzeMediation = async (req: AuthenticatedRequest, res: Response) return res.status(400).json({ error: 'clientName (Müvekkil Adı) zorunludur' }); } - 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 docs = await resolveCaseDocuments(String(caseId), req.body?.documents); const trashKeywords = ['tebligat', 'makbuz', 'pul', 'zarf', 'masraf', 'havale', 'barkod', 'alındı', 'görüldü', 'ilan', 'talimat', 'nüfus', 'sicil', 'sabıka']; - const targetDocs = (docs || []).filter(d => { + const targetDocs = docs.filter(d => { if (!d.extracted_text || !String(d.extracted_text).trim()) return false; const fn = (d.filename || '').toLowerCase().replace(/ı/g, 'i').replace(/ğ/g, 'g').replace(/ü/g, 'u').replace(/ş/g, 's').replace(/ö/g, 'o').replace(/ç/g, 'c'); return !trashKeywords.some(kw => fn.includes(kw)); @@ -405,14 +402,9 @@ export const analyzeDigitalIntern = async (req: AuthenticatedRequest, res: Respo return res.status(400).json({ error: 'Soru (question) girmelisiniz.' }); } - 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 docs = await resolveCaseDocuments(String(caseId), req.body?.documents); - const targetDocs = (docs || []).filter(d => d.extracted_text && String(d.extracted_text).trim()); + const targetDocs = docs.filter(d => d.extracted_text && String(d.extracted_text).trim()); if (targetDocs.length === 0) { return res.status(400).json({ error: 'Bu dosyada arama yapılabilecek evrak bulunamadı.' }); diff --git a/src/controllers/chat.controller.ts b/src/controllers/chat.controller.ts index 85c0075..13e26cc 100644 --- a/src/controllers/chat.controller.ts +++ b/src/controllers/chat.controller.ts @@ -100,6 +100,12 @@ interface CaseChatContextOpts { // mesajını insert ettikten SONRA bu fonksiyonu çağırıyor olmalı) kullanılacak // son çare — orijinal chatMessage'daki else dalıyla aynı savunma amaçlı davranış. fallbackUserContent?: string; + // Faz 2 — documents/analyses artık kalıcı olarak Postgres'te tutulmuyor. + // Electron (yerel SQLite'tan okuyarak) bunları isteğe ekleyebilir; verilirse + // ÖNCELİKLİ kaynaktır. Verilmezse (örn. WhatsApp — yerel dosya erişimi yok) + // aşağıdaki Postgres sorgularına düşülür, davranış aynen korunur. + caseDocuments?: { id?: string; filename: string; extracted_text: string | null }[]; + latestAnalysisSummaryJson?: unknown; } // chatMessage (masaüstü "AyrisLegal'e Sor" sohbeti) ve WhatsApp uç noktasının @@ -109,18 +115,22 @@ interface CaseChatContextOpts { // kullanıcı mesajını chat_messages'a insert ETTİKTEN SONRA çağırmalı, ki pastMessages // sorgusu o mesajı da (en sonda) içersin. export async function buildCaseChatContext(caseId: string, opts: CaseChatContextOpts = {}): Promise { - const { chat_mode = 'chat', persona, document_ids, attachments, historyLimit = 10, fallbackUserContent } = opts; + const { chat_mode = 'chat', persona, document_ids, attachments, historyLimit = 10, fallbackUserContent, caseDocuments, latestAnalysisSummaryJson } = opts; const targetPersona = persona || 'judge'; - const { data: analyses } = await supabase - .from('analyses') - .select('summary_json') - .eq('case_id', caseId); - - const { data: documents } = await supabase + const hasBodyDocuments = Array.isArray(caseDocuments) && caseDocuments.length > 0; + const documents = hasBodyDocuments ? caseDocuments : (await supabase .from('documents') .select('id, filename, extracted_text') - .eq('case_id', caseId); + .eq('case_id', caseId)).data; + + const latestAnalysis = latestAnalysisSummaryJson !== undefined ? latestAnalysisSummaryJson : (await (async () => { + const { data: analyses } = await supabase + .from('analyses') + .select('summary_json') + .eq('case_id', caseId); + return analyses && analyses.length > 0 ? analyses[analyses.length - 1].summary_json : undefined; + })()); const { data: pastMessages } = await supabase .from('chat_messages') @@ -146,8 +156,8 @@ export async function buildCaseChatContext(caseId: string, opts: CaseChatContext documentContext = '\n\nBu dosyaya henüz hiçbir belge yüklenmemiştir. Genel hukuki bilgiyle cevap ver, ama bunu kullanıcıya açıkça belirt.'; } - if (analyses && analyses.length > 0) { - documentContext += '\n\nANALİZ ÖZETİ:\n' + JSON.stringify(analyses[analyses.length - 1].summary_json); + if (latestAnalysis) { + documentContext += '\n\nANALİZ ÖZETİ:\n' + JSON.stringify(latestAnalysis); } documentContext += buildAttachmentContext(attachments); @@ -180,7 +190,7 @@ export async function buildCaseChatContext(caseId: string, opts: CaseChatContext // yerine yazılırken görür; gerçek üretim süresi aynı kalsa da algılanan hız artar. export const chatMessage = async (req: AuthenticatedRequest, res: Response) => { const { caseId } = req.params; - const { content, message, attachments, document_ids, chat_mode = 'chat', persona, simulator_persona } = req.body || {}; + const { content, message, attachments, document_ids, chat_mode = 'chat', persona, simulator_persona, case_documents, latest_analysis_summary_json } = req.body || {}; const messageText = content || message; const targetPersona = simulator_persona || persona || 'judge'; const userId = req.user?.id; @@ -208,6 +218,8 @@ export const chatMessage = async (req: AuthenticatedRequest, res: Response) => { document_ids, attachments, fallbackUserContent: persistedContent, + caseDocuments: Array.isArray(case_documents) ? case_documents : undefined, + latestAnalysisSummaryJson: latest_analysis_summary_json, }); startSse(res); diff --git a/src/controllers/drafting.controller.ts b/src/controllers/drafting.controller.ts index 318b901..1ec2b61 100644 --- a/src/controllers/drafting.controller.ts +++ b/src/controllers/drafting.controller.ts @@ -47,7 +47,7 @@ function toTraceableLegislation(matches: LegislationMatch[]) { // gerçek bir AI taslağı üretir. export const generateDraft = async (req: AuthenticatedRequest, res: Response) => { try { - const { case_id, petition_type, notes, template_id } = req.body || {}; + const { case_id, petition_type, notes, template_id, latest_analysis_summary_json } = req.body || {}; const userId = req.user?.id; if (!case_id || !petition_type) { @@ -66,13 +66,22 @@ export const generateDraft = async (req: AuthenticatedRequest, res: Response) => return res.status(403).json({ error: 'Bu dosyaya erişim yetkiniz yok.' }); } - const { data: lastAnalysis } = await supabase - .from('analyses') - .select('summary_json') - .eq('case_id', case_id) - .order('created_at', { ascending: false }) - .limit(1) - .maybeSingle(); + // Faz 2 — analyses artık kalıcı olarak Postgres'te tutulmuyor. İstekte + // gönderilmişse (Electron'un yerel SQLite'tan okuduğu son analiz) öncelikli + // kaynak; yoksa (eski istemci/web) mevcut Postgres sorgusuna düşülür. + let lastAnalysis: { summary_json: any } | null = latest_analysis_summary_json !== undefined + ? { summary_json: latest_analysis_summary_json } + : null; + if (!lastAnalysis) { + const { data } = await supabase + .from('analyses') + .select('summary_json') + .eq('case_id', case_id) + .order('created_at', { ascending: false }) + .limit(1) + .maybeSingle(); + lastAnalysis = data; + } let template: { name: string; extracted_text: string | null } | null = null; if (template_id) {