From 9f5c5c1e45def97b17471d66bb1f31917e2f8076 Mon Sep 17 00:00:00 2001 From: mstfyldz Date: Sun, 9 Aug 2026 16:14:41 +0300 Subject: [PATCH] feat: add case-level AI summary endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /api/cases/:caseId/summarize combines extracted text from all of a case's documents and runs the existing AI analysis prompt, storing the result as a case-level analysis (document_id: null). Powers the "Özet Hazırla" button on the case detail screen. Co-Authored-By: Claude Sonnet 5 --- src/controllers/case.controller.ts | 46 ++++++++++++++++++++++++++ src/controllers/document.controller.ts | 7 ++-- src/routes/case.routes.ts | 3 +- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/controllers/case.controller.ts b/src/controllers/case.controller.ts index a70df27..fd550bd 100644 --- a/src/controllers/case.controller.ts +++ b/src/controllers/case.controller.ts @@ -1,6 +1,7 @@ import { Response } from 'express'; import { supabase } from '../lib/supabase'; import { AuthenticatedRequest } from '../middleware/auth'; +import { runDocumentAnalysis } from './document.controller'; export const createCase = async (req: AuthenticatedRequest, res: Response) => { try { @@ -26,6 +27,51 @@ export const createCase = async (req: AuthenticatedRequest, res: Response) => { } }; +// 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. +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.' }); + } + + // 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. + 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 { summaryJson, aiModel } = await runDocumentAnalysis(combinedText); + + 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' }); + } +}; + export const deleteCase = async (req: AuthenticatedRequest, res: Response) => { try { const { caseId } = req.params; diff --git a/src/controllers/document.controller.ts b/src/controllers/document.controller.ts index 09b0fc6..4061ab7 100644 --- a/src/controllers/document.controller.ts +++ b/src/controllers/document.controller.ts @@ -16,9 +16,10 @@ const ANALYSIS_SYSTEM_PROMPT = ` Sadece ve sadece JSON döndür, başka hiçbir açıklama ekleme. `; -// uploadDocument ve registerLocalDocument'in ortak kullandığı AI analiz adımı: -// çıkarılmış metni modele gönderip yapılandırılmış özet JSON'u üretir. -async function runDocumentAnalysis(extractedText: string): Promise<{ summaryJson: any; aiModel: string }> { +// uploadDocument, registerLocalDocument ve summarizeCase'in (case.controller.ts) +// ortak kullandığı AI analiz adımı: çıkarılmış metni modele gönderip yapılandırılmış +// özet JSON'u üretir. +export async function runDocumentAnalysis(extractedText: string): Promise<{ summaryJson: any; aiModel: string }> { const aiModel = process.env.AI_MODEL || 'mizan-fixed'; const runpodUrl = process.env.RUNPOD_API_BASE_URL || 'https://q190env94stwis-11434.proxy.runpod.net'; diff --git a/src/routes/case.routes.ts b/src/routes/case.routes.ts index 707ebfe..92d9bc7 100644 --- a/src/routes/case.routes.ts +++ b/src/routes/case.routes.ts @@ -1,10 +1,11 @@ import { Router } from 'express'; import { requireAuth } from '../middleware/auth'; -import { createCase, deleteCase } from '../controllers/case.controller'; +import { createCase, deleteCase, summarizeCase } from '../controllers/case.controller'; const router = Router(); router.post('/', requireAuth, createCase); router.delete('/:caseId', requireAuth, deleteCase); +router.post('/:caseId/summarize', requireAuth, summarizeCase); export default router;