Preserve complete input in multi-part offer draft generation
This commit is contained in:
+11
-7
@@ -1,5 +1,5 @@
|
||||
import { auditOfferInParts } from './offer-audit-parts.mjs';
|
||||
import { qualifyOfferDraft } from './offer-draft.mjs';
|
||||
import { draftOfferInParts } from './offer-draft-parts.mjs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { questionChunks, qualifyQuestionAnswer } from './document-question.mjs';
|
||||
import { documentContent } from './document-content.mjs';
|
||||
@@ -116,15 +116,19 @@ const BOLUMLER = {
|
||||
export async function teklifTaslak({ ikn, tur, tender, firma, belgeler, fiyat, gereklilikler, chain, text, signal }) {
|
||||
const istenen = BOLUMLER[tur] || BOLUMLER.tam;
|
||||
const { text: metin } = await ihaleMetni(ikn, text, tender?.aciklama);
|
||||
const liste = gereklilikMetni(tender, gereklilikler, metin);
|
||||
const liste = gereklilikMetni(tender, gereklilikler, metin, true);
|
||||
const baglam = [
|
||||
`İHALE: ${tender?.baslik ?? '?'} · İdare: ${tender?.kurum ?? '?'} · İl: ${tender?.sehir ?? '?'} · Tarih: ${tender?.tarih ?? '?'} · Usul: ${tender?.usul ?? tender?.alimTuru ?? '?'}${tender?.yaklasikMaliyet ? ' · Yaklaşık maliyet: ' + tender.yaklasikMaliyet : ''}${ikn ? ' · İKN ' + ikn : ''}`,
|
||||
firma ? `FİRMA: ${typeof firma === 'string' ? firma : JSON.stringify(firma).slice(0, 1500)}` : null,
|
||||
firma ? `FİRMA: ${typeof firma === 'string' ? firma : JSON.stringify(firma)}` : null,
|
||||
liste.length ? 'GEREKLİLİKLER / KALEMLER:\n' + liste.map((g) => '- ' + g).join('\n') : null,
|
||||
Array.isArray(belgeler) && belgeler.length ? 'DEPODAKİ BELGELER:\n' + belgeler.slice(0, 40).map((b) => `- ${b.ad || b}${b.kategori ? ' [' + b.kategori + ']' : ''}`).join('\n') : null,
|
||||
fiyat ? `FİYAT VERİSİ: ${typeof fiyat === 'string' ? fiyat : JSON.stringify(fiyat).slice(0, 1500)}` : null,
|
||||
Array.isArray(belgeler) && belgeler.length ? 'DEPODAKİ BELGELER:\n' + belgeler.map((b) => `- ${b.ad || b}${b.kategori ? ' [' + b.kategori + ']' : ''}`).join('\n') : null,
|
||||
metin ? `KAYNAK METNİ (tam içerik):\n${metin}` : null,
|
||||
fiyat ? `FİYAT VERİSİ: ${typeof fiyat === 'string' ? fiyat : JSON.stringify(fiyat)}` : null,
|
||||
].filter(Boolean).join('\n\n');
|
||||
const sistem = `Sen Türkiye kamu ihalelerinde (4734) deneyimli bir teklif yazarısın. Verilen bilgilerle resmî, kısa ve somut bir teklif dosyası taslağı yaz. Verilmeyen bilgiyi UYDURMA: firma adı, yetkili, tarih, fiyat, süre boşluklarına [DOLDURUN: ...] yaz. Fiyat yalnızca FİYAT VERİSİ varsa yazılır. Ekler bölümünde istenen belgeleri madde madde ver. Üslup resmî; abartı yok. Yalnızca JSON: {"bolumler":[{"baslik":"","icerik":""}]} — istenen bölümleri aynı sırada ver; bölüm başlıklarını değiştirmeden aynen kopyala.`;
|
||||
const out = await analyzeSpec({ text: `BAĞLAM:\n${baglam}\n\nİSTENEN BÖLÜMLER:\n${istenen.map((x, i) => `${i + 1}. ${x}`).join('\n')}\n\nYalnızca JSON döndür.`, tender, chain, signal, systemOverride: sistem, mode: 'derin' });
|
||||
return qualifyOfferDraft(out?.result, istenen);
|
||||
return draftOfferInParts(baglam, istenen, async (part,total) => {
|
||||
const out = await analyzeSpec({ text: `BAĞLAM BÖLÜMÜ ${part.index + 1}/${total}:\n${part.text}\n\nİSTENEN BÖLÜMLER:\n${istenen.map((x,i)=>`${i+1}. ${x}`).join('\n')}\n\nBu kaynak bölümünde bulunmayan bilgiyi uydurma. Diğer kaynak bölümlerinde ek veya çelişen şartlar bulunabilir. Yalnızca JSON döndür.`,
|
||||
tender:tender ? {...tender,items:[]} : undefined,chain,signal,systemOverride:sistem,mode:'derin',_singlePart:true});
|
||||
return out?.result;
|
||||
},{signal});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { splitAnalysisText } from './analysis-parts.mjs';
|
||||
import { qualifyOfferDraft } from './offer-draft.mjs';
|
||||
|
||||
/** Draft-specific merging: the general specification schema cannot merge sections. */
|
||||
export async function draftOfferInParts(context, requested, runPart, {signal} = {}) {
|
||||
const parts = splitAnalysisText(context, 16000), results = new Map();
|
||||
for (let start = 0; start < parts.length; start += 2) {
|
||||
signal?.throwIfAborted();
|
||||
await Promise.all(parts.slice(start, start + 2).map(async part => {
|
||||
try {
|
||||
const result = qualifyOfferDraft(await runPart(part, parts.length), requested);
|
||||
results.set(part.index, result);
|
||||
} catch (error) {
|
||||
signal?.throwIfAborted();
|
||||
results.set(part.index, null);
|
||||
}
|
||||
}));
|
||||
}
|
||||
signal?.throwIfAborted();
|
||||
if (![...results.values()].some(Boolean)) throw new Error('Taslak için hiçbir kaynak bölümü işlenemedi. Yeniden deneyin.');
|
||||
if (parts.length === 1) return results.get(0);
|
||||
const failedParts = parts.filter(part => !results.get(part.index)).map(part => part.index + 1);
|
||||
const missingSections = requested.filter(title => parts.some(part => !results.get(part.index) || results.get(part.index).draftReview.missingSections.includes(title)));
|
||||
const bolumler = requested.map(baslik => ({baslik, icerik:parts.map(part => {
|
||||
const found = results.get(part.index)?.bolumler.find(row => row.baslik === baslik);
|
||||
return `Kaynak bölümü ${part.index + 1}/${parts.length}:\n${found?.icerik || '[DOLDURUN: Bu kaynak bölümünün taslağı üretilemedi; kaynak metni kontrol edin.]'}`;
|
||||
}).join('\n\n')}));
|
||||
// Unexpected but useful sections remain visible and attributed to their input part.
|
||||
for (const part of parts) for (const row of results.get(part.index)?.bolumler || []) {
|
||||
if (!requested.includes(row.baslik)) bolumler.push({...row, baslik:`${row.baslik} (Kaynak bölümü ${part.index + 1})`});
|
||||
}
|
||||
return {bolumler, draftReview:{status:'partial', requestedSections:requested, missingSections,
|
||||
extraSections:bolumler.slice(requested.length).map(row => row.baslik), failedParts,
|
||||
sourceHash:createHash('sha256').update(context).digest('hex'),
|
||||
parts:parts.map(part => ({index:part.index + 1,start:part.start,end:part.end,status:results.get(part.index) ? 'processed' : 'failed'})),
|
||||
note:`Uzun kaynak ${parts.length} bölümde işlendi. ${failedParts.length ? `${failedParts.length} bölüm üretilemedi. ` : ''}Bölüm taslakları kaynak sırasıyla birleştirildi; tekrarlar ve bölümler arası çelişkiler kullanıcı tarafından kontrol edilmelidir. Her kaynak parçasının işlenmesi, içindeki her şartın doğru karşılandığını kanıtlamaz. Bu çıktı resmi başvuruya hazır değildir.`,
|
||||
}};
|
||||
}
|
||||
Reference in New Issue
Block a user