28 depo taramasindan alinan alti ozellik. Kod kopyalanmadi (depolar lisanssiz), fikirler uygulandi. 1) Zeyilname / duzeltme ilani takibi (server/zeyilname.mjs): takipteki IKN'ler sunucuya yazilir; 3 saatte bir EKAP detayindan ilan sayisi ve ihale tarihi karsilastirilir, degisince "ne degisti" bildirimi (uygulama ici + e-posta + Telegram). Test: anlik kaydi eskitilince tur "yeni ilan yayimlandi; ihale tarihi 01.01.2026 → 08.10.2026" bildirimi uretti. 2) Sonuc bildirimi: takipteki ihale sonuclaninca "X firmasi Y'ye aldi, %Z kirim". Yalniz takiptekiler; kismi ihalede oran yazilmaz. 3) Ihaleden goreve (MOBIT-DURT): detayda "Hazirlik Gorevi Ac" → teslim tarihi ihale saati, pano "Teklif Hazirlaniyor", derin analizden gelen belge kontrol listesi gorevde; Kanban kartinda geri sayim rozeti. 4) Sirket belgesi sure takibi: Belge Kasasi'nda son kullanma tarihi girisi, dolmus / 30 gun icinde dolacak uyarisi; girise gunde bir bildirim. PATCH /api/me/documents/meta. 5) Telegram (server/telegram.mjs): TELEGRAM_BOT_TOKEN sunucuda; kullanici 6 haneli kodu bota yazar, "Dogrula" → getUpdates ile chat id baglanir. Arayuzde adim adim kurulum rehberi (BotFather → env → Redeploy → kod). Ihale ozeti, zeyilname ve sonuc bildirimleri Telegram'a da gider. 6) OCR (tesseract.js, tur): PDF metin katmani bossa (taranmis) ilk 8 sayfa OCR. Olcum: sentetik cetvelde guven 87, ~0,3 sn/sayfa; IKN ve kalemler dogru okundu. Kalem havuzu ve derin analiz artik taranmis PDF'ten de besleniyor. 7) Rakip Radari'nda KIK yasakli sorgusu baglantisi (firma adi panoya kopyalanir; EKAP sorguyu API'den vermiyor). Sinir deger hesaplayici KURULMADI: Kamu Ihale Genel Tebligi'ne ulasilamadi (mevzuat.gov.tr / ihale.gov.tr bu makineden erisilemiyor); formul kaynaksiz yazilmadi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
75 lines
3.0 KiB
JavaScript
75 lines
3.0 KiB
JavaScript
/**
|
||
* PDF'ten metin çıkarma (tarayıcı içinde, pdfjs-dist ile).
|
||
* Dinamik import edilir — ana bundle'a girmez.
|
||
*/
|
||
|
||
export async function extractPdfText(file, { maxPages = 40 } = {}) {
|
||
const pdfjs = await import('pdfjs-dist');
|
||
// Worker'ı Vite'ın bundle ettiği dosyadan al (CDN'e çıkmaz)
|
||
const workerUrl = (await import('pdfjs-dist/build/pdf.worker.min.mjs?url')).default;
|
||
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
|
||
|
||
const buf = await file.arrayBuffer();
|
||
const doc = await pdfjs.getDocument({ data: buf }).promise;
|
||
const pages = Math.min(doc.numPages, maxPages);
|
||
const parts = [];
|
||
for (let i = 1; i <= pages; i++) {
|
||
const page = await doc.getPage(i);
|
||
const content = await page.getTextContent();
|
||
parts.push(content.items.map((it) => it.str).join(' '));
|
||
}
|
||
const text = parts.join('\n\n').replace(/[ \t]+/g, ' ').trim();
|
||
return { text, pageCount: doc.numPages, usedPages: pages };
|
||
}
|
||
|
||
// Metin dosyaları için basit okuyucu
|
||
export async function extractPlainText(file) {
|
||
const text = await file.text();
|
||
return { text: text.trim(), pageCount: 1, usedPages: 1 };
|
||
}
|
||
|
||
/**
|
||
* Taranmış (görüntü) PDF için OCR — tesseract.js, Türkçe dil paketi,
|
||
* tarayıcıda. Ölçüldü: sentetik teklif cetvelinde güven 87, sayfa başı
|
||
* ~0,3 sn (Node). Yalnızca metin katmanı boşsa devreye girer; ilk 8 sayfa.
|
||
*/
|
||
export async function ocrPdfText(file, { maxPages = 8, onProgress } = {}) {
|
||
const pdfjs = await import('pdfjs-dist');
|
||
const workerUrl = (await import('pdfjs-dist/build/pdf.worker.min.mjs?url')).default;
|
||
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
|
||
const { createWorker } = await import('tesseract.js');
|
||
const doc = await pdfjs.getDocument({ data: await file.arrayBuffer() }).promise;
|
||
const pages = Math.min(doc.numPages, maxPages);
|
||
const worker = await createWorker('tur');
|
||
const parts = [];
|
||
try {
|
||
for (let i = 1; i <= pages; i++) {
|
||
onProgress?.({ sayfa: i, toplam: pages });
|
||
const page = await doc.getPage(i);
|
||
const viewport = page.getViewport({ scale: 2 }); // ~150 dpi: OCR için yeterli, hızlı
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = viewport.width; canvas.height = viewport.height;
|
||
await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
|
||
const { data } = await worker.recognize(canvas);
|
||
parts.push(data.text);
|
||
}
|
||
} finally {
|
||
await worker.terminate();
|
||
}
|
||
const text = parts.join('\n\n').replace(/[ \t]+/g, ' ').trim();
|
||
return { text, pageCount: doc.numPages, usedPages: pages, ocr: true };
|
||
}
|
||
|
||
export async function extractText(file, { ocr = true, onProgress } = {}) {
|
||
const name = (file.name || '').toLowerCase();
|
||
if (name.endsWith('.pdf') || file.type === 'application/pdf') {
|
||
const r = await extractPdfText(file);
|
||
// Metin katmanı yoksa taranmış PDF'tir — OCR'a düş
|
||
if (ocr && (r.text || '').replace(/\s/g, '').length < 100) {
|
||
try { return await ocrPdfText(file, { onProgress }); } catch { return r; }
|
||
}
|
||
return r;
|
||
}
|
||
return extractPlainText(file);
|
||
}
|