From c4947422dc5bc7dc9e253b62a6e4462c0c857e78 Mon Sep 17 00:00:00 2001 From: mstfyldz Date: Fri, 14 Aug 2026 11:48:49 +0300 Subject: [PATCH] feat: add digital-intern live-qa endpoints for pinpoint hearing assistance --- src/controllers/case.controller.ts | 50 +++++++++++++++++++++++++- src/controllers/document.controller.ts | 28 +++++++++++++++ src/routes/case.routes.ts | 4 ++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/controllers/case.controller.ts b/src/controllers/case.controller.ts index e5d8433..21d9890 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 { runIddianameAnalysis, runCaseNarrativeAnalysis, runStatementAnalysis, runDeficiencyAnalysis, runStrategyAnalysis, runMediationAnalysis } from './document.controller'; +import { runIddianameAnalysis, runCaseNarrativeAnalysis, runStatementAnalysis, runDeficiencyAnalysis, runStrategyAnalysis, runMediationAnalysis, runDigitalInternQa } from './document.controller'; import { findOrCreateCaseByTitle } from '../lib/caseLookup'; export const createCase = async (req: AuthenticatedRequest, res: Response) => { @@ -385,3 +385,51 @@ export const deleteCase = async (req: AuthenticatedRequest, res: Response) => { res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; + +export const analyzeDigitalIntern = async (req: AuthenticatedRequest, res: Response) => { + try { + const { caseId } = req.params; + const { question, query } = req.body || {}; + const questionToUse = question || query; + + if (!caseId) { + return res.status(400).json({ error: 'Geçersiz caseId' }); + } + + if (!questionToUse || !String(questionToUse).trim()) { + 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 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ı.' }); + } + + const perDocLimit = Math.max(1000, Math.floor(30000 / targetDocs.length)); + const combinedText = targetDocs + .map((d) => `--- ${d.filename} ---\n${String(d.extracted_text).slice(0, perDocLimit)}`) + .join('\n\n'); + + const analysisInput = `Aşağıdaki belgeler dava dosyasına aittir:\n\n${combinedText}`; + + const { summaryJson, aiModel } = await runDigitalInternQa(analysisInput, questionToUse); + + // İsteğe bağlı olarak arama sonucunu da 'analyses' tablosuna kaydet + await supabase + .from('analyses') + .insert([{ case_id: caseId, document_id: null, summary_json: { digitalInternResult: summaryJson, question: questionToUse }, model_used: aiModel }]); + + res.json({ result: summaryJson, modelUsed: aiModel }); + } catch (error: any) { + console.error('Digital Intern Error:', error); + res.status(500).json({ error: error.message || 'Internal Server Error' }); + } +}; diff --git a/src/controllers/document.controller.ts b/src/controllers/document.controller.ts index 6ab67f5..962a1f2 100644 --- a/src/controllers/document.controller.ts +++ b/src/controllers/document.controller.ts @@ -564,3 +564,31 @@ export const uploadDocument = async (req: AuthenticatedRequest, res: Response) = res.status(500).json({ error: error.message || 'Internal Server Error' }); } }; + +// Dijital Stajyer (Duruşma Asistanı - Canlı ve Nokta Atışı Soru/Cevap) +export async function runDigitalInternQa(combinedText: string, question: string): Promise<{ summaryJson: any; aiModel: string }> { + const prompt = `Sen duruşma salonundaki avukatın yanında duran, saniyeler içinde nokta atışı cevap veren kıdemli DİJİTAL STAJYERSİN. + +AVUKATIN SORUSU: "${question}" + +GÖREVİN & SIKI İLKELERİN: +1. Duruşma anında avukatın saniyeler içinde okuyabileceği NOKTA ATIŞI ve NET cevabı ver. +2. Uzun giriş paragrafı, merhaba mesajı veya lakırdı KESİNLİKLE YASAKTIR. Doğrudan sonuca gir. +3. Bulduğun veriyi (Tarih, Saat, Belge Adı/No, İsim, Tutar, Sayfa) açıkça yaz. +4. Aranan bilgi dava evraklarında açıkça yer almıyorsa: "found": false yaz ve "Bu bilgi dosyadaki evraklarda bulunmamaktadır" de. + +ÇIKTI FORMATI (SADECE GEÇERLİ JSON): +{ + "found": true, + "answer": "📌 12 Ekim 2021 tarihinde düzenlenen Marmara Devlet Hastanesi Adli Muayene Raporu'nda altta kalça kırığı tespit edilmiştir.", + "dateInfo": "12 Ekim 2021", + "documentRef": "Marmara Devlet Hastanesi Adli Muayene Raporu (Belge No: 45)", + "exactExcerpt": "Dosyadan alınan birebir pasaj veya ilgili cümle...", + "confidenceScore": "%95" +} + +Sadece ve sadece JSON döndür, kod bloğu işareti (\`\`\`) veya başka bir açıklama ekleme.`; + + const { resultJson, aiModel } = await callAiModel(prompt, combinedText); + return { summaryJson: resultJson, aiModel }; +} diff --git a/src/routes/case.routes.ts b/src/routes/case.routes.ts index b3ed612..57a2e5f 100644 --- a/src/routes/case.routes.ts +++ b/src/routes/case.routes.ts @@ -1,6 +1,6 @@ import { Router } from 'express'; import { requireAuth } from '../middleware/auth'; -import { createCase, deleteCase, summarizeCase, updateCaseParties, analyzeStatements, analyzeDeficiencies, analyzeStrategy, analyzeMediation } from '../controllers/case.controller'; +import { createCase, deleteCase, summarizeCase, updateCaseParties, analyzeStatements, analyzeDeficiencies, analyzeStrategy, analyzeMediation, analyzeDigitalIntern } from '../controllers/case.controller'; const router = Router(); @@ -15,6 +15,8 @@ router.post('/:caseId/analyze-strategy', requireAuth, analyzeStrategy); router.post('/:caseId/strategy', requireAuth, analyzeStrategy); router.post('/:caseId/analyze-mediation', requireAuth, analyzeMediation); router.post('/:caseId/mediation', requireAuth, analyzeMediation); +router.post('/:caseId/digital-intern', requireAuth, analyzeDigitalIntern); +router.post('/:caseId/live-qa', requireAuth, analyzeDigitalIntern); router.post('/parties', requireAuth, updateCaseParties); export default router;