Persist general specification analysis and expose saved results
This commit is contained in:
@@ -4,7 +4,7 @@ import {dirname,join} from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {randomUUID,createHash} from 'node:crypto';
|
||||
const root=process.env.IHALEIST_USER_DATA_DIR || join(dirname(fileURLToPath(import.meta.url)),'data');
|
||||
const operations=new Set(['belge-sor','teklif-denetim','teklif-taslak']);
|
||||
const operations=new Set(['analiz','belge-sor','teklif-denetim','teklif-taslak']);
|
||||
function dbWork(fn) {
|
||||
mkdirSync(root,{recursive:true,mode:0o700});const db=new DatabaseSync(join(root,'ai-jobs.sqlite'));chmodSync(join(root,'ai-jobs.sqlite'),0o600);
|
||||
try {
|
||||
@@ -40,7 +40,7 @@ export function createAiJob(email,requestId,operation,input) {
|
||||
if(db.prepare("SELECT COUNT(*) n FROM ai_jobs WHERE email=? AND state IN ('queued','running')").get(email).n>=5)throw new Error('En fazla beş etkin AI işi olabilir. Mevcut işleri tamamlayın veya iptal edin.');
|
||||
const id=randomUUID(),now=new Date().toISOString();
|
||||
db.prepare("INSERT INTO ai_jobs(id,email,request_id,operation,ikn,input,input_hash,state,created_at,updated_at) VALUES (?,?,?,?,?,?,?,'queued',?,?)")
|
||||
.run(id,email,requestId,operation,input.ikn||null,serialized,hash,now,now);
|
||||
.run(id,email,requestId,operation,input.ikn||input.tender?.ikn||null,serialized,hash,now,now);
|
||||
const result=publicJob(db.prepare('SELECT * FROM ai_jobs WHERE id=?').get(id));db.exec('COMMIT');return result;
|
||||
} catch(error){db.exec('ROLLBACK');throw error;}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
export async function executeAnalysisJob(job,{analyze,reviewSource,pin,signal}) {
|
||||
signal?.throwIfAborted();
|
||||
let input=job.input;
|
||||
const reviewed=input.reviewedSource ? reviewSource(job.email,input.reviewedSource,input.tender?.ikn) : null;
|
||||
if(!job.sourcePinned) {
|
||||
const text=reviewed?.text ?? input.text;
|
||||
if(typeof text!=='string'||text.trim().length<40)throw new Error('Analiz için yeterli metin yok (en az 40 karakter).');
|
||||
input={...input,text,fileName:reviewed?.fileName || input.fileName,
|
||||
extraction:reviewed ? {fileName:reviewed.fileName,complete:true,ocr:false} : input.extraction,
|
||||
humanReview:reviewed?.humanReview || null};
|
||||
if(!pin(job.id,job.runId,input))throw new Error('İş artık etkin değil; kaynak kaydedilemedi.');
|
||||
}
|
||||
if(reviewed && reviewed.humanReview.textHash!==input.humanReview?.textHash)throw new Error('İnceleme değişti; güncel kaynakla yeni analiz başlatın.');
|
||||
const out=await analyze({text:input.text,fileName:input.fileName,extraction:input.extraction,tender:input.tender,
|
||||
chain:input.chain,mode:input.mode==='derin'?'derin':undefined,signal});
|
||||
signal?.throwIfAborted();
|
||||
if(reviewed) {
|
||||
const latest=reviewSource(job.email,input.reviewedSource,input.tender?.ikn);
|
||||
if(latest.humanReview.textHash!==input.humanReview.textHash)throw new Error('İnceleme analiz sırasında değişti.');
|
||||
out.result.sourceReview={...out.result.sourceReview,humanReview:input.humanReview};
|
||||
}
|
||||
return {success:true,...out,analyzedAt:new Date().toISOString()};
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { executeAnalysisJob } from './analysis-job.mjs';
|
||||
import { resolveAiJobInput } from './ai-job-source.mjs';
|
||||
import * as aiJobs from './ai-job-store.mjs';
|
||||
import { startAiJobRunner } from './ai-job-runner.mjs';
|
||||
@@ -689,6 +690,7 @@ async function tendersPayload(force = false) {
|
||||
|
||||
const aiJobRunner = startAiJobRunner(async (job,{signal}) => {
|
||||
if(!aiConfigured())throw new Error('AI hizmeti kullanılamıyor. Ayarlar düzeltildikten sonra yeniden başlatın.');
|
||||
if(job.operation==='analiz')return executeAnalysisJob(job,{analyze:analyzeSpec,reviewSource:reviewedAnalysisSource,pin:aiJobs.pinAiJobInput,signal});
|
||||
const body=await resolveAiJobInput(job,ihaleMetni,aiJobs.pinAiJobInput,signal,input=>gereklilikMetni(input.tender,input.gereklilikler,'',true));
|
||||
const shared={signal,ikn:body.ikn,tender:body.tender,chain:body.chain,text:body.text,resolvedSource:body.resolvedSource};
|
||||
const result=job.operation==='belge-sor' ? await belgeyeSor({...shared,soru:body.soru})
|
||||
|
||||
Reference in New Issue
Block a user