feat: iddianame-first case summary + remove hardcoded AI endpoint

- summarizeCase: if an iddianame is among the case's documents, run a
  narrow first-pass analysis on it alone (isnatEdilenSuc, iddiaOzeti,
  onemliTarihler), then feed that as context into the general
  case-wide evaluation instead of treating every document equally.
- ANALYSIS_SYSTEM_PROMPT no longer asks the AI to guess "taraflar" -
  that now comes from real UYAP data (cases.parties) instead.
- Removed the hardcoded RunPod proxy URL fallback from every call
  site (document/chat/precedent controllers + lib/openai.ts) - it was
  a real infra endpoint checked into source. New lib/aiConfig.ts
  throws if RUNPOD_API_BASE_URL isn't set rather than silently
  falling back to it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
mstfyldz
2026-08-09 20:00:06 +03:00
co-authored by Claude Sonnet 5
parent af5a03db92
commit c939dcf564
6 changed files with 105 additions and 33 deletions
+36 -13
View File
@@ -4,12 +4,14 @@ import { AuthenticatedRequest } from '../middleware/auth';
import * as officeParser from 'officeparser';
import Tesseract from 'tesseract.js';
import { findOrCreateCaseByTitle } from '../lib/caseLookup';
import { getAiConfig } from '../lib/aiConfig';
const heicConvert = require('heic-convert');
// "taraflar" (davacı/davalı/sanık) burada YOK — o bilgiyi UYAP Evrak İndirici
// eklentisinden gerçek veri olarak alıyoruz (cases.parties), AI'ye tahmin ettirmiyoruz.
const ANALYSIS_SYSTEM_PROMPT = `
Sen uzman bir Türk avukatısın. Sana verilen dava dosyasını/belgeyi okuyup analiz edeceksin.
Çıktıyı KESİNLİKLE geçerli bir JSON formatında vermelisin. JSON şu anahtarları içermelidir:
- "taraflar": (string) Davacı ve Davalı bilgileri
- "tarihler": (string) Belgedeki kritik tarihler (Dava tarihi, tebliğ vb.)
- "hukukiDayanak": (string) Belgedeki ilgili kanun maddeleri ve hukuki temeller
- "ilkDegerlendirme": (string) Dosyaya dair ilk stratejik ve hukuki değerlendirmen
@@ -17,12 +19,20 @@ const ANALYSIS_SYSTEM_PROMPT = `
Sadece ve sadece JSON döndür, başka hiçbir açıklama ekleme.
`;
// 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';
// summarizeCase (case.controller.ts) dosyada iddianame bulursa önce SADECE onun
// üzerinden bir ön çıkarım yapıyor, genel değerlendirme buna dayanıyor.
const IDDIANAME_SYSTEM_PROMPT = `
Sen uzman bir Türk ceza avukatısın. Sana bir iddianame metni verilecek.
Çıktıyı KESİNLİKLE geçerli bir JSON formatında ver. JSON şu anahtarları içermelidir:
- "isnatEdilenSuc": (string) İsnat edilen suç(lar) ve ilgili kanun maddeleri
- "iddiaOzeti": (string) İddia makamının olay örgüsü, delilleri ve talebinin özeti
- "onemliTarihler": (string) İddianamedeki kritik tarihler
Sadece ve sadece JSON döndür, başka hiçbir açıklama ekleme.
`;
async function callAiModel(systemPrompt: string, userText: string): Promise<{ resultJson: any; aiModel: string }> {
const { runpodUrl, aiModel } = getAiConfig();
const response = await fetch(`${runpodUrl}/v1/chat/completions`, {
method: 'POST',
@@ -30,8 +40,8 @@ export async function runDocumentAnalysis(extractedText: string): Promise<{ summ
body: JSON.stringify({
model: aiModel,
messages: [
{ role: 'system', content: ANALYSIS_SYSTEM_PROMPT },
{ role: 'user', content: extractedText.substring(0, 15000) }
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userText.substring(0, 15000) }
],
think: false,
stream: false,
@@ -44,14 +54,27 @@ export async function runDocumentAnalysis(extractedText: string): Promise<{ summ
const data = await response.json();
const aiContent = data.message?.content || (data.choices && data.choices[0]?.message?.content);
let summaryJson: any = {};
let resultJson: any = {};
try {
summaryJson = JSON.parse(aiContent || '{}');
resultJson = JSON.parse(aiContent || '{}');
} catch (e) {
console.error('Failed to parse AI response as JSON:', aiContent);
summaryJson = { error: 'AI did not return valid JSON', raw: aiContent };
resultJson = { error: 'AI did not return valid JSON', raw: aiContent };
}
return { summaryJson, aiModel };
return { resultJson, aiModel };
}
// uploadDocument, registerLocalDocument ve summarizeCase'in (case.controller.ts)
// ortak kullandığı genel 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 };
}
// summarizeCase'in iddianame ön-analizi için kullandığı, dar kapsamlı adım.
export async function runIddianameAnalysis(extractedText: string): Promise<{ summaryJson: any; aiModel: string }> {
const { resultJson, aiModel } = await callAiModel(IDDIANAME_SYSTEM_PROMPT, extractedText);
return { summaryJson: resultJson, aiModel };
}
// UYAP Evrak İndirici eklentisinden gelen, Electron ana sürecinde YERELDE metni