diff --git a/.gitattributes b/.gitattributes index ddaaf474a30358ea3c74902ae8fdf6d50d5f96db..551d6d69925701ab307b41de58b2b29a610d03d4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -81,3 +81,38 @@ samples/vekaletname_01.scan.pdf filter=lfs diff=lfs merge=lfs -text samples/zabit_01.pdf filter=lfs diff=lfs merge=lfs -text samples/zabit_01.png filter=lfs diff=lfs merge=lfs -text samples/zabit_01.scan.pdf filter=lfs diff=lfs merge=lfs -text +hero/dilekce_maskeli.png filter=lfs diff=lfs merge=lfs -text +hero/dilekce_orijinal.png filter=lfs diff=lfs merge=lfs -text +samples/aydinlatma_03.png filter=lfs diff=lfs merge=lfs -text +samples/aydinlatma_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/aydinlatma_05.png filter=lfs diff=lfs merge=lfs -text +samples/aydinlatma_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/bilirkisi_03.png filter=lfs diff=lfs merge=lfs -text +samples/bilirkisi_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/bilirkisi_05.png filter=lfs diff=lfs merge=lfs -text +samples/bilirkisi_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/dilekce_03.png filter=lfs diff=lfs merge=lfs -text +samples/dilekce_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/dilekce_05.png filter=lfs diff=lfs merge=lfs -text +samples/dilekce_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/epikriz_03.png filter=lfs diff=lfs merge=lfs -text +samples/epikriz_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/epikriz_05.png filter=lfs diff=lfs merge=lfs -text +samples/epikriz_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/eposta_03.png filter=lfs diff=lfs merge=lfs -text +samples/eposta_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/eposta_05.png filter=lfs diff=lfs merge=lfs -text +samples/eposta_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/fatura_03.png filter=lfs diff=lfs merge=lfs -text +samples/fatura_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/fatura_05.png filter=lfs diff=lfs merge=lfs -text +samples/fatura_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/icra_03.png filter=lfs diff=lfs merge=lfs -text +samples/icra_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/icra_05.png filter=lfs diff=lfs merge=lfs -text +samples/icra_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/ihtarname_03.png filter=lfs diff=lfs merge=lfs -text +samples/ihtarname_03.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/ihtarname_05.png filter=lfs diff=lfs merge=lfs -text +samples/ihtarname_05.scan.pdf filter=lfs diff=lfs merge=lfs -text +samples/is_sozlesmesi_03.png filter=lfs diff=lfs merge=lfs -text diff --git a/README.md b/README.md index 76a913e1389672a9be622081358f42377cc793c6..c057706891501d13c99a25cd61f0e0372ff6b698 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,10 @@ running entirely client-side: Python rules in Pyodide, true PDF redaction with M OCR with Tesseract.js, and an optional 2.6 MB on-device name tagger ([katip-tr-name-tagger-tiny](https://huggingface.co/f1k/katip-tr-name-tagger-tiny)) via transformers.js. -- 16 synthetic sample documents with answer keys: see recall, precision, misses and false alarms live. -- Switch the detection model, OCR model, confidence threshold and data types. -- Before/after view, leak check on the output, report a mistake by e-mail. +- Drop a file and get the masked copy: scanning, masking and the leak check run automatically. +- Untick anything that should stay visible, or type a missed term; then download. +- 16 synthetic samples with answer keys; model, OCR, threshold, data types and mask style under "Gelişmiş ayarlar". +- "Görüş bildir" opens a ready e-mail for feedback. Source: https://github.com/fikrifirat/katip · Licence: AGPL-3.0, with a commercial licence available for use in products or real operations (contact fikrifirat@gmail.com or open an issue). diff --git a/app.js b/app.js index 424e1800cc09f8b1637daaa24d9279baaa26da25..cd5db9a438d0d3d84aa4f1d17a8e812405e71676 100644 --- a/app.js +++ b/app.js @@ -12,108 +12,122 @@ const DEV = new URLSearchParams(location.search).has("dev"); const I18N = { tr: { tagline: "Türkçe KVKK maskeleme, tamamen tarayıcınızda. Belge hiçbir sunucuya yüklenmez.", - netLbl: "Belge sonrası ağ isteği", netTip: "Belgeyi açtıktan sonra bu sayfanın internete kaç kez bağlandığı. 0 kalmalı.||Motor ve model dosyaları sayılmaz; bunlar belgeyi açmadan önce indirilir.", + netLbl: "Belge açıldıktan sonra ağ isteği", netTip: "Belgeniz bu sekmede açılır, taranır ve maskelenir; hiçbir sunucuya yüklenmez.||Sayfanın güvenlik politikası (CSP) başka bir adrese veri gönderilmesini engeller ve hiçbir şey kaydedilmez: sekmeyi kapatınca belge silinir. Belge açıldıktan sonraki ağ isteği sayacı Gelişmiş ayarlarda görünür ve 0 kalmalıdır.", cspLbl: "Dışarı gönderim", blocked: "engelli", cspTip: "Bu sayfa belgenizi başka bir yere gönderemez.||Sayfanın güvenlik politikası (CSP), tarayıcının başka bir adrese veri göndermesini engeller.", storeLbl: "Kayıt", none: "yok", storeTip: "Hiçbir şey kaydedilmez; sekmeyi kapatınca belge silinir.||Belge yalnızca bu sekmenin belleğinde tutulur.", docH: "Belge", sampleTab: "Örnek belge", fileTab: "Kendi belgem", sampleHint: "Sentetik belgeler, cevap anahtarıyla puanlanır.", sampleTip: "Örneklerdeki kişiler uydurmadır. Doğru cevaplar bilindiği için kaçanları da görürsünüz.||Bulunan, kaçan ve fazladan işaretlenen değerler cevap anahtarına göre canlı sayılır.", - dropH: "Belgeyi bırakın veya tıklayın", fileHint: "Dosya yalnızca bu sekmenin belleğinde kalır.", + dropH: "Belgeyi buraya bırakın veya seçin", fileHint: "Dosya yalnızca bu sekmenin belleğinde kalır.", fileTip: "Dosyanız bilgisayarınızdan çıkmaz.||Açma, tarama, maskeleme ve kontrol bu sekmede yapılır; sekmeyi kapatınca silinir.", detectH: "Algılama", ocrH: "OCR (tarama ve fotoğraf)", ocrTip: "Taranmış belge ve fotoğrafları okuyan model. Önerilen yeterlidir.||Türkçe Tesseract modeli. 96 taranmış örnekte ikisi neredeyse aynı değerleri buldu; önerilen 4 kat küçüktür çünkü yalnızca yeni (LSTM) okuyucuyu içerir.", - thrH: "Güven eşiği", thrTip: "Düşük: daha çok yakalar, daha çok yanlış alarm. Yüksek: tersi.||T.C. kimlik, VKN, IBAN gibi sağlama kontrollü numaralar her zaman yakalanır.", - typesH: "Veri türleri", maskH: "Maske", colorH: "Renk", styleH: "Word ve metinde", + thrH: "Güven eşiği", thrTip: "Düşük: daha çok yakalar, daha çok yanlış alarm. Yüksek: tersi. 0,70 önerilir.||T.C. kimlik, VKN, IBAN gibi sağlama kontrollü numaralar her zaman yakalanır.", + typesH: "Veri türleri", maskH: "Maske", colorH: "Maske rengi", styleH: "Word ve metinde", styleTip: "Değerin yerine ne konacağı: kutu, ████ veya [AD SOYAD-1] gibi etiket.||PDF ve görüntülerde seçilen renkte kutu çizilir. Word, UDF ve metinde değer ██████ veya etiketle değiştirilir.", styleBox: "██████ blok", styleLabel: "[AD SOYAD-1] etiket", + unreadItem: "Sayfa {p} · alan {i}", addrH: "Adresler", addrStreet: "Sokak görünür, bina/daire gizli", addrFull: "Adresin tamamı gizli", + addrTip: "Mahalle, cadde/sokak, ilçe ve il kişisel veri değildir; bina adı, blok, kapı ve daire numarası kişiyi gösterir.||Varsayılan: Alparslan Türkeş Cad. gibi sokak adları görünür kalır, yalnızca bina, blok, kat, kapı ve daire maskelenir. Sokak adları hiçbir zaman kişi adı sayılmaz. Tüm adresi gizlemek için \"Adresin tamamı gizli\"yi seçin.", tipMore: "Ayrıntılar", markH: "Ortaya Katip logosu", markTip: "Her sayfanın ortasına soluk bir Katip logosu basar.||PDF ve görüntülerde. Kapalıyken alttaki tek satırlık filigran yine eklenir.", - wmH: "Filigran ekle", wmTip: "Çıktının altına küçük bir \"Katip Yerel ile maskelendi\" satırı ekler.||Tam metin: \"{wm}\". PDF'te bağlantılıdır. UYAP .udf dosyalarına eklenmez.", - viewReview: "İnceleme", viewResult: "Sonuç", apply: "Maskeleri uygula", reapply: "Yeniden uygula", download: "İndir", report: "Hata bildir", + wmH: "Filigran ekle", wmTip: "Çıktının altına küçük bir \"Katip Mask ile maskelendi\" satırı ekler.||Tam metin: \"{wm}\". PDF'te bağlantılıdır. UYAP .udf dosyalarına eklenmez.", + viewReview: "Orijinal", viewResult: "Maskeli", apply: "Maskeleri uygula", reapply: "Yeniden uygula", download: "Maskeli belgeyi indir", report: "Hata bildir", copy: "Etiketli metni kopyala", forget: "Belgeyi unut", empty: "Soldan bir örnek seçin veya kendi belgenizi açın.", - termPh: "Kaçan bir terim ekle", add: "Ekle", + termPh: "Kaçan bir şey mi var? Buraya yazın", add: "Ekle", findTip: "İşaretini kaldırdığınız değer maskelenmez.||Belgedeki işarete tıklamak da aç/kapa yapar. ⚑ o değeri hata bildirimine ekler.", - found: "{n} değer maskelenecek", missedH: "Kaçanlar", addMissed: "Maskeye ekle", flag: "Hata bildirimine ekle", warnOnly: "uyarı", + found: "{n} kişisel veri karartıldı", missedH: "Kaçanlar", addMissed: "Maskeye ekle", flag: "Hata bildirimine ekle", warnOnly: "uyarı", details: "Ayrıntılar", sRecall: "bulundu", sFp: "fazla", sLeak: "çıktıda okunabilir", sNotApplied: "Değişiklikler henüz uygulanmadı", sPreview: "Henüz maskelenmedi: işaretleri kontrol edin, sonra Maskeleri uygula'ya basın.", - leakBad: "Dikkat: {n} değer çıktıda hâlâ okunabiliyor. Göndermeyin.", leakUnchecked: "Taranmış sayfalar tekrar okunmadı; çıktıyı gözle kontrol edin.", - leakOk: "Sızıntı kontrolü geçti: maskelenen değerlerin hiçbiri çıktıda okunamıyor.", - loading: "Motor yükleniyor…", ready: "Motor hazır ({s} sn).", preparing: "Hazırlanıyor…", done: "{n} değer maskelendi.", copied: "Etiketli metin kopyalandı.", forgotten: "Belge bellekten silindi.", - waiting: "Motor yükleniyor; sonra Kişisel verileri bul'a basın.", toScan: "Önizleme hazır. Kişisel verileri bul'a basın.", listHint: "Kişisel verileri bul'a basınca bulunan değerler burada türlerine göre listelenir; her birini açıp kapatabilirsiniz.", scan: "Kişisel verileri bul", rescan: "Yeniden tara", + leakBad: "Dikkat: {n} değer çıktıda hâlâ okunabiliyor. Bu dosyayı göndermeyin.", leakUnchecked: "Taranmış sayfalar tekrar okunmadı; indirmeden önce gözle kontrol edin.", + leakOk: "Kontrol edildi: karartılan değerlerin hiçbiri çıktıda okunamıyor.", + loading: "Katip hazırlanıyor… (ilk açılışta birkaç saniye)", ready: "Katip hazır.", preparing: "Hazırlanıyor…", done: "{n} kişisel veri karartıldı.", copied: "Etiketli metin kopyalandı.", forgotten: "Belge bellekten silindi.", + waiting: "Katip hazırlanıyor; hazır olunca belge kendiliğinden taranır.", toScan: "Önizleme hazır. Kişisel verileri bul'a basın.", listHint: "Kişisel verileri bul'a basınca bulunan değerler burada türlerine göre listelenir; her birini açıp kapatabilirsiniz.", scan: "Kişisel verileri bul", rescan: "Yeniden tara", error: "Hata: ", page: "Sayfa", ok: "doğru", fpL: "fazla", missL: "kaçan", tType: "Tür", tGold: "Doğru cevap", tFound: "Bulunan", tMissed: "Kaçan", tFp: "Fazla", partial: "kısmen: yalnız \"{p}\"", goldNote: "Doğru cevaplar belgeyi üreten betikten gelir. Taranmış belgelerde OCR hataları da puanı etkiler.", kModel: "Model", kOcr: "OCR modeli", kPages: "Sayfa", kParse: "Okuma ve OCR", kRules: "Kurallar", kRedact: "Maskeleme ve kontrol", kVersion: "Motor sürümü", unused: "kullanılmadı", - rWhatWrong: "Ne yanlış?", rMissed: "Bir şey maskelenmedi", rFp: "Gereksiz bir şey maskelendi", rBroken: "Çıktı bozuk veya açılmıyor", rOther: "Diğer", - rWhere: "Hangi değer veya yer? (isteğe bağlı)", rDesc: "Açıklama", rDescPh: "Ne bekliyordunuz, ne oldu?", rInclude: "Rapora eklenecekler", + rWhatWrong: "Konu", rMissed: "Bir şey açık kaldı", rFp: "Gereksiz karartıldı", rBroken: "Dosya açılmıyor", rOther: "Diğer", + rWhere: "Hangi değer veya yer? (isteğe bağlı)", rDesc: "Mesajınız", rDescPh: "Ne işe yaradı, ne eksik, neyi farklı yapardınız?", rInclude: "Rapora eklenecekler", rIncSettings: "Ayarlar, sürüm, süreler, puanlar", rIncMasked: "Maskeli çıktı", rIncDet: "Bulunan değerler", rIncOrig: "Orijinal belge", - rPersonal: "(kişisel veri içerir)", rPersonalOk: "(kişisel veri içerir; yalnızca izniniz varsa)", cancel: "Vazgeç", rSend: "Doğrudan gönder", rMail: "Raporu indir ve e-posta aç", - rHowMail: "Rapor bilgisayarınıza indirilir ve e-posta uygulamanız {to} adresine açılır. İndirilen .zip dosyasını ekleyip gönderin. Siz göndermeden hiçbir şey çıkmaz.", + rPersonal: "(kişisel veri içerir)", rPersonalOk: "(kişisel veri içerir; yalnızca izniniz varsa)", cancel: "Vazgeç", rSend: "Doğrudan gönder", rMail: "E-postayla gönder", + rHowMail: "E-posta uygulamanız {to} adresine hazır bir mesajla açılır. Belgeyle ilgili bir sorunsa küçük bir rapor (.zip) da indirilir; onu ekleyin. Kişisel veri içeren hiçbir şey siz seçmeden eklenmez.", rHowSend: "\"Doğrudan gönder\" raporu {host} üzerinden iletir. Ya da raporu indirip e-postayla kendiniz gönderin.", rDownloaded: "{f} indirildi. E-posta açılmadıysa {to} adresine dosyayı ekleyerek yazın.", rSending: "Gönderiliyor…", rSent: "Gönderildi, teşekkürler.", rFailed: "Gönderilemedi", - foot: "Kurallar Katip sunucusundakilerle aynı (Pyodide) · PDF: MuPDF · OCR: Tesseract", source: "Kaynak kod", issues: "Soru ve öneriler", + foot: "Açık kaynak · tamamen cihazınızda çalışır", source: "Kaynak kod", issues: "Soru ve öneriler", onSpace: "Bu sayfa için çok büyük (110 MB) · Hugging Face'te deneyin →", + ocrAuto: "Otomatik (el yazısı için PaddleOCR)", ocrAutoMeta: "El yazılı ve fotoğraflı sayfalar PaddleOCR, basılı taramalar Tesseract · +13 MB", + ocrAutoTip: "Mavi tükenmez kalem izi olan ya da fotoğraftan kesilmiş sayfalar PaddleOCR ile, diğerleri Küçük Tesseract ile okunur.||36 sentetik el yazılı kira sözleşmesi fotoğrafında PaddleOCR hiç TC kimlik numarası ve isim kaçırmadı (Tesseract: 72 TC numarasından 12'si). PaddleOCR basılı taramalarda Tesseract'tan iyi değil, o yüzden onlara karışmaz. Model ilk el yazılı sayfada bu siteden bir kez yüklenir.", ocrBest: "Küçük", ocrBestMeta: "Aynı doğruluk, 4 kat küçük · 2 MB", ocrFast: "Standart", ocrFastMeta: "Eski motor verisiyle · 7,7 MB", origAlt: "Orijinal sayfa", maskAlt: "Maskeli sayfa", ocrText: "Okunan metin", + trust: "Cihazınızda çalışır · belgeniz bilgisayarınızdan çıkmaz", feedback: "Görüş bildir", feedbackH: "Görüşünüz bizim için çok değerli", hero: "Belgedeki kişisel verileri saniyeler içinde maskeleyin.", lead: "Ad, T.C. kimlik, adres, telefon, IBAN, sağlık bilgisi… Katip bulur ve karartır; siz kontrol edip indirirsiniz.", dropFormats: "PDF · fotoğraf · Word · UYAP .udf · metin", tryH: "Elinizde belge yok mu? Örnekle deneyin:", allSamples: "Tüm örnekler", openSample: "Aç", step1: "Belgeyi bırakın", step2: "Karartılanları kontrol edin", step3: "Maskeli halini indirin", newDoc: "← Yeni belge", listHint2: "Tik kaldırdığınız değer açık kalır. Belgedeki işarete tıklamak da aynı işi görür.", advH: "Gelişmiş ayarlar", modelH: "Algılama modeli", scanning: "Kişisel veriler aranıyor…", applying: "Karartılıyor…", sampleNote: "Örnek belge (kişiler uydurma). Cevap anahtarı:", leakFix: "Bunları da karart", qKira: "Kira sözleşmesi · fotoğraf", qDilekce: "Dava dilekçesi · UYAP .udf", qBilirkisi: "Bilirkişi raporu · tarama", qIs: "İş sözleşmesi · Word", rGeneral: "Genel görüş", rHowGeneral: "E-posta uygulamanız {to} adresine hazır bir mesajla açılır. Siz göndermeden hiçbir şey çıkmaz.", + profH: "Ne için maskeliyorsunuz?", pCourt: "Mahkemeye sunum", pAi: "Yapay zekâ ile paylaşım", pAnon: "Tam anonim", pCustom: "Özel seçim", pCourtHint: "Kişisel verilerin tamamı karartılır; sokak, dosya numarası ve tarihler okunur kalır. Delil ve dilekçe ekleri için.", pAiHint: "Buna ek olarak adresin tamamı, esas/karar ve UYAP numaraları, baro sicil numaraları ve sabıka bilgisi karartılır.", pAnonHint: "Buna ek olarak tarihler, il ve ilçeler, mahkeme, şirket ve kurum adları da karartılır. Metin okunur, kim olduğu çıkmaz.", pCustomHint: "Veri türlerini kendiniz seçtiniz.", pickTypes: "Türleri tek tek seç", baCap: "Örnek dava dilekçesi (kişiler uydurma). Çizgiyi kaydırın.", baOpen: "Bu örneği aç →", sampleN: "örnek", baMadeUp: "kişiler uydurma", pickType: "Örnek belge türü", pickVariant: "Örnek numarası", baOrigAlt: "Örnek dilekçe, orijinal", baMaskAlt: "Aynı dilekçe, Katip ile maskelenmiş", baAria: "Orijinal ile maskeli arasında kaydır", }, en: { tagline: "Turkish KVKK redaction that runs entirely in your browser. Nothing is uploaded.", - netLbl: "Requests after opening", netTip: "How many times this page went online after you opened the document. It should stay 0.||Engine and model files are not counted; they download before you open a document.", + netLbl: "Network requests after opening the document", netTip: "Your document is opened, scanned and masked in this tab; it is never uploaded.||The page's Content-Security-Policy blocks sending data anywhere else, and nothing is stored: closing the tab deletes it. The counter of requests after opening a document is under Advanced settings and should stay 0.", cspLbl: "Sending out", blocked: "blocked", cspTip: "This page cannot send your document anywhere.||The page's Content-Security-Policy makes the browser block any request to another address.", storeLbl: "Stored", none: "nothing", storeTip: "Nothing is saved; closing the tab deletes the document.||The document lives only in this tab's memory.", docH: "Document", sampleTab: "Sample", fileTab: "My file", sampleHint: "Synthetic documents, scored against an answer key.", sampleTip: "The people in the samples are made up. The right answers are known, so you also see what was missed.||Found, missed and extra values are counted live against the answer key.", - dropH: "Drop a file or click", fileHint: "The file stays in this tab's memory only.", + dropH: "Drop a document here or choose one", fileHint: "The file stays in this tab's memory only.", fileTip: "Your file never leaves your computer.||Opening, scanning, masking and checking all happen in this tab; closing the tab deletes it.", detectH: "Detection", ocrH: "OCR (scans and photos)", ocrTip: "The model that reads scans and photos. The recommended one is enough.||Turkish Tesseract model. On 96 scanned samples both found almost the same values; the recommended one is 4× smaller because it holds only the newer (LSTM) reader.", - thrH: "Confidence threshold", thrTip: "Lower catches more but raises more false alarms. Higher does the opposite.||Numbers with a checksum (T.C. kimlik, VKN, IBAN) are always caught.", - typesH: "Data types", maskH: "Mask", colorH: "Colour", styleH: "In Word and text", + thrH: "Confidence threshold", thrTip: "Lower catches more but raises more false alarms. Higher does the opposite. 0.70 is recommended.||Numbers with a checksum (T.C. kimlik, VKN, IBAN) are always caught.", + typesH: "Data types", maskH: "Mask", colorH: "Mask colour", styleH: "In Word and text", styleTip: "What replaces each value: a box, ████ or a label such as [AD SOYAD-1].||PDFs and images get a box in the chosen colour. In Word, UDF and text the value is replaced with ██████ or a label.", styleBox: "██████ block", styleLabel: "[AD SOYAD-1] label", + unreadItem: "Page {p} · area {i}", addrH: "Addresses", addrStreet: "Street visible, building/flat hidden", addrFull: "Whole address hidden", + addrTip: "Neighbourhood, street, district and province are not personal data; building name, block, door and flat number point to a person.||Default: street names such as Alparslan Türkeş Cad. stay readable and only the building, block, floor, door and flat are masked. Street names are never treated as people's names. Pick \"Whole address hidden\" to mask all of it.", tipMore: "Details", markH: "Katip logo in the centre", markTip: "Stamps a faint Katip logo across the middle of each page.||PDFs and images only. Off, the one-line watermark at the bottom is still added.", - wmH: "Add watermark", wmTip: "Adds a small \"Redacted with Katip Yerel\" line at the bottom.||Full text: \"{wm}\". Linked in PDFs. Not added to UYAP .udf files.", - viewReview: "Review", viewResult: "Result", apply: "Apply masks", reapply: "Apply again", download: "Download", report: "Report a mistake", + wmH: "Add watermark", wmTip: "Adds a small \"Redacted with Katip Mask\" line at the bottom.||Full text: \"{wm}\". Linked in PDFs. Not added to UYAP .udf files.", + viewReview: "Original", viewResult: "Masked", apply: "Apply masks", reapply: "Apply again", download: "Download masked file", report: "Report a mistake", copy: "Copy labelled text", forget: "Forget document", empty: "Pick a sample on the left or open your own file.", - termPh: "Add a missed term", add: "Add", + termPh: "Something missed? Type it here", add: "Add", findTip: "Untick a value and it will not be masked.||Clicking a highlight in the document toggles it too. ⚑ adds the value to a mistake report.", - found: "{n} values to mask", missedH: "Missed", addMissed: "Add to masks", flag: "Add to mistake report", warnOnly: "flag", + found: "{n} personal data items masked", missedH: "Missed", addMissed: "Add to masks", flag: "Add to mistake report", warnOnly: "flag", details: "Details", sRecall: "found", sFp: "extra", sLeak: "readable in output", sNotApplied: "Changes not applied yet", sPreview: "Nothing is masked yet: check the highlights, then press Apply masks.", - leakBad: "Warning: {n} values are still readable in the output. Do not send it.", leakUnchecked: "Scanned pages were not re-read; check the output by eye.", - leakOk: "Leak check passed: none of the masked values can be read in the output.", - loading: "Loading engine…", ready: "Engine ready ({s} s).", preparing: "Preparing…", done: "{n} values masked.", copied: "Labelled text copied.", forgotten: "Document removed from memory.", - waiting: "Engine loading; then press Find personal data.", toScan: "Preview ready. Press Find personal data.", listHint: "After Find personal data, the values found are listed here by type; you can switch each one on or off.", scan: "Find personal data", rescan: "Scan again", + leakBad: "Warning: {n} values are still readable in the output. Do not send this file.", leakUnchecked: "Scanned pages were not re-read; check by eye before downloading.", + leakOk: "Checked: none of the masked values can be read in the output.", + loading: "Getting Katip ready… (a few seconds the first time)", ready: "Katip is ready.", preparing: "Preparing…", done: "{n} personal data items masked.", copied: "Labelled text copied.", forgotten: "Document removed from memory.", + waiting: "Getting Katip ready; the document is scanned as soon as it is.", toScan: "Preview ready. Press Find personal data.", listHint: "After Find personal data, the values found are listed here by type; you can switch each one on or off.", scan: "Find personal data", rescan: "Scan again", error: "Error: ", page: "Page", ok: "correct", fpL: "extra", missL: "missed", tType: "Type", tGold: "Answer key", tFound: "Found", tMissed: "Missed", tFp: "Extra", partial: "partly: only \"{p}\"", goldNote: "The answer key comes from the script that generated the document. On scans, OCR errors also affect the score.", kModel: "Model", kOcr: "OCR model", kPages: "Pages", kParse: "Reading and OCR", kRules: "Rules", kRedact: "Masking and check", kVersion: "Engine version", unused: "not used", - rWhatWrong: "What went wrong?", rMissed: "Something was not masked", rFp: "Something was masked by mistake", rBroken: "The output is broken or won't open", rOther: "Other", - rWhere: "Which value or place? (optional)", rDesc: "Description", rDescPh: "What did you expect, what happened?", rInclude: "Include in the report", + rWhatWrong: "Topic", rMissed: "Something was left visible", rFp: "Masked by mistake", rBroken: "File won't open", rOther: "Other", + rWhere: "Which value or place? (optional)", rDesc: "Your message", rDescPh: "What helped, what is missing, what would you do differently?", rInclude: "Include in the report", rIncSettings: "Settings, version, timings, scores", rIncMasked: "Masked output", rIncDet: "Detected values", rIncOrig: "Original document", - rPersonal: "(contains personal data)", rPersonalOk: "(contains personal data; only with permission)", cancel: "Cancel", rSend: "Send directly", rMail: "Download report and open e-mail", - rHowMail: "The report is downloaded to your computer and your e-mail app opens a message to {to}. Attach the downloaded .zip and send it. Nothing leaves until you send it.", + rPersonal: "(contains personal data)", rPersonalOk: "(contains personal data; only with permission)", cancel: "Cancel", rSend: "Send directly", rMail: "Send by e-mail", + rHowMail: "Your e-mail app opens a ready message to {to}. For a problem with a document a small report (.zip) is also downloaded; attach it. Nothing with personal data is added unless you tick it.", rHowSend: "\"Send directly\" delivers the report through {host}. Or download it and e-mail it yourself.", rDownloaded: "{f} downloaded. If no e-mail opened, write to {to} and attach the file.", rSending: "Sending…", rSent: "Sent, thank you.", rFailed: "Could not send", - foot: "Same rules as the Katip server (Pyodide) · PDF: MuPDF · OCR: Tesseract", source: "Source code", issues: "Questions and ideas", + foot: "Open source · runs entirely on your device", source: "Source code", issues: "Questions and ideas", onSpace: "Too big for this page (110 MB) · try it on Hugging Face →", + ocrAuto: "Automatic (PaddleOCR for handwriting)", ocrAutoMeta: "Handwritten and photographed pages: PaddleOCR; printed scans: Tesseract · +13 MB", + ocrAutoTip: "Pages with blue ballpoint ink, or a sheet cut out of a photo, are read with PaddleOCR; the rest with Compact Tesseract.||On 36 synthetic photos of hand-filled lease contracts PaddleOCR missed no TC number and no name (Tesseract: 12 of 72 TC numbers). PaddleOCR is not better than Tesseract on printed scans, so it leaves those alone. The model loads once from this site, on the first handwritten page.", ocrBest: "Compact", ocrBestMeta: "Same accuracy, 4× smaller · 2 MB", ocrFast: "Standard", ocrFastMeta: "Includes old engine data · 7.7 MB", origAlt: "Original page", maskAlt: "Masked page", ocrText: "Recognised text", + trust: "Runs on your device · your file never leaves it", feedback: "Send feedback", feedbackH: "We'd love your feedback", hero: "Mask personal data in legal documents in seconds.", lead: "Names, ID numbers, addresses, phones, IBANs, health details… Katip finds and blacks them out; you check and download.", dropFormats: "PDF · photo · Word · UYAP .udf · text", tryH: "No document at hand? Try a sample:", allSamples: "All samples", openSample: "Open", step1: "Drop the document", step2: "Check what's masked", step3: "Download the masked copy", newDoc: "← New document", listHint2: "Untick a value to leave it visible. Clicking a highlight in the document does the same.", advH: "Advanced settings", modelH: "Detection model", scanning: "Looking for personal data…", applying: "Masking…", sampleNote: "Sample document (made-up people). Answer key:", leakFix: "Mask these too", qKira: "Lease · photo", qDilekce: "Petition · UYAP .udf", qBilirkisi: "Expert report · scan", qIs: "Employment contract · Word", rGeneral: "General feedback", rHowGeneral: "Your e-mail app opens a ready message to {to}. Nothing leaves until you send it.", + profH: "What are you masking it for?", pCourt: "Court filing", pAi: "Sharing with AI", pAnon: "Fully anonymous", pCustom: "Custom", pCourtHint: "All personal data is masked; streets, case numbers and dates stay readable. For evidence and petition attachments.", pAiHint: "On top of that, the whole address, case and UYAP numbers, bar registry numbers and criminal records are masked.", pAnonHint: "On top of that, dates, provinces and districts, and court, company and institution names are masked too. The text reads, the people can't be traced.", pCustomHint: "You picked the data types yourself.", pickTypes: "Pick types one by one", baCap: "Sample petition (made-up people). Drag the line.", baOpen: "Open this sample →", sampleN: "sample", baMadeUp: "made-up people", pickType: "Sample document type", pickVariant: "Sample number", baOrigAlt: "Sample petition, original", baMaskAlt: "The same petition masked by Katip", baAria: "Slide between original and masked", }, }; const LABELS = { tr: { TCKN: "T.C. Kimlik No", VKN: "Vergi Kimlik No", IBAN: "IBAN", CREDIT_CARD: "Kredi kartı", PHONE: "Telefon", EMAIL: "E-posta", PLATE: "Araç plakası", - PERSON: "Ad soyad", ADDRESS: "Adres", BIRTH_DATE: "Doğum tarihi", IP_ADDRESS: "IP adresi", HEALTH: "Sağlık", RELIGION: "Din/inanç", POLITICAL: "Siyasi görüş", - UNION: "Sendika", ETHNICITY: "Etnik köken", SEXUAL_LIFE: "Cinsel hayat", BIOMETRIC_GENETIC: "Biyometrik/genetik", CRIMINAL_RECORD: "Ceza mahkumiyeti", CUSTOM: "Eklenen terim" }, + PERSON: "Ad soyad", ADDRESS: "Adres", BIRTH_DATE: "Doğum tarihi", IP_ADDRESS: "IP adresi", YKN: "Yabancı kimlik no", PASSPORT: "Pasaport no", FOREIGN_ID: "Yabancı ülke kimlik no", HEALTH: "Sağlık", RELIGION: "Din/inanç", POLITICAL: "Siyasi görüş", + UNION: "Sendika", ETHNICITY: "Etnik köken", SEXUAL_LIFE: "Cinsel hayat", BIOMETRIC_GENETIC: "Biyometrik/genetik", CRIMINAL_RECORD: "Ceza mahkumiyeti", UNREAD: "El yazısı alanı", + CASE_NO: "Esas/karar/UYAP no", BAR_NO: "Baro/sicil no", DATE: "Tarih", PLACE: "İl/ilçe", INSTITUTION: "Mahkeme/kurum/şirket", CUSTOM: "Eklenen terim" }, en: { TCKN: "Turkish ID no.", VKN: "Tax ID", IBAN: "IBAN", CREDIT_CARD: "Card number", PHONE: "Phone", EMAIL: "E-mail", PLATE: "Licence plate", - PERSON: "Name", ADDRESS: "Address", BIRTH_DATE: "Birth date", IP_ADDRESS: "IP address", HEALTH: "Health", RELIGION: "Religion/belief", POLITICAL: "Political opinion", - UNION: "Trade union", ETHNICITY: "Ethnic origin", SEXUAL_LIFE: "Sex life", BIOMETRIC_GENETIC: "Biometric/genetic", CRIMINAL_RECORD: "Criminal record", CUSTOM: "Added term" }, + PERSON: "Name", ADDRESS: "Address", BIRTH_DATE: "Birth date", IP_ADDRESS: "IP address", YKN: "Foreigner ID no.", PASSPORT: "Passport no.", FOREIGN_ID: "Foreign national ID", HEALTH: "Health", RELIGION: "Religion/belief", POLITICAL: "Political opinion", + UNION: "Trade union", ETHNICITY: "Ethnic origin", SEXUAL_LIFE: "Sex life", BIOMETRIC_GENETIC: "Biometric/genetic", CRIMINAL_RECORD: "Criminal record", UNREAD: "Handwritten area", + CASE_NO: "Case/UYAP no.", BAR_NO: "Bar/registry no.", DATE: "Date", PLACE: "Province/district", INSTITUTION: "Court/institution/company", CUSTOM: "Added term" }, }; const FORMATS = { tr: { txt: "Metin", docx: "Word", udf: "UYAP .udf", pdf: "PDF", "scan.pdf": "Taranmış PDF", png: "Fotoğraf" }, en: { txt: "Text", docx: "Word", udf: "UYAP .udf", pdf: "PDF", "scan.pdf": "Scanned PDF", png: "Photo" } }; @@ -121,8 +135,18 @@ const SAMPLE_EN = { aydinlatma: "Privacy notice", bilirkisi: "Expert report", di fatura: "Invoice", icra: "Enforcement request", ihtarname: "Formal notice", is_sozlesmesi: "Employment contract", karar: "Court judgment", kira_sozlesmesi: "Lease", mektup: "Letter", mesaj: "Chat log", tutanak: "Minutes", vekaletname: "Power of attorney", zabit: "Hearing record" }; const PROGRESS_EN = [[/^NER modeli yükleniyor/, "Loading name model"], [/^NER çalışıyor/, "Running name model"], [/^OCR sayfa (\d+)\/(\d+)/, "OCR page $1/$2"], [/^Sayfa (\d+)\/(\d+)/, "Page $1/$2"], - [/^OCR$/, "OCR"], [/^Kişisel veriler aranıyor/, "Looking for personal data"], [/^Maskeleniyor/, "Masking"], [/tekrar okunuyor/, "Re-reading output (leak check)"]]; + [/^OCR$/, "OCR"], [/^Kişisel veriler aranıyor/, "Looking for personal data"], [/^Maskeleniyor/, "Masking"], [/tekrar okunuyor/, "Re-reading output (leak check)"], [/^El yazısı OCR modeli yükleniyor/, "Loading handwriting OCR model"]]; const ART6 = ["HEALTH", "RELIGION", "POLITICAL", "UNION", "ETHNICITY", "SEXUAL_LIFE", "BIOMETRIC_GENETIC", "CRIMINAL_RECORD"]; +// Masking profiles: one click sets the data types and the address rule; the checkboxes stay for fine control. +// A ladder: each profile masks everything the one before it does, plus more. +const CASE = ["CASE_NO", "BAR_NO"], ANON = ["DATE", "PLACE", "INSTITUTION"]; +const PERSONAL = () => Object.keys(LABELS.tr).filter(k => k !== "CUSTOM" && !CASE.includes(k) && !ANON.includes(k)); +const PROFILES = { + court: { name: "pCourt", hint: "pCourtHint", addr: "street", types: () => PERSONAL().filter(k => k !== "CRIMINAL_RECORD") }, + ai: { name: "pAi", hint: "pAiHint", addr: "full", types: () => [...PERSONAL(), ...CASE] }, + anon: { name: "pAnon", hint: "pAnonHint", addr: "full", types: () => [...PERSONAL(), ...CASE, ...ANON] }, +}; +let profile = "court"; const COLORS = [["#000000", "Siyah / Black"], ["#3a3a3a", "Grafit / Graphite"], ["#1f3a5f", "Lacivert / Navy"], ["#1f5f4a", "Yeşil / Green"], ["#7a1f1f", "Bordo / Maroon"], ["#ffffff", "Beyaz / White"]]; let lang = (() => { @@ -145,7 +169,7 @@ let loaded = null; // { name, bytes, gold, isSample } // ---------------------------------------------------------------- network watch let docOpenedAt = Infinity, netCount = 0; -const runtimeFile = u => /\/(vendor|models|samples)\/|katip_py\.zip|\.(m?js|css|wasm|json)(\?|$)/.test(u); +const runtimeFile = u => /\/(vendor|models|samples|hero)\/|katip_py\.zip|\.(m?js|css|wasm|json)(\?|$)/.test(u); new PerformanceObserver(list => { for (const e of list.getEntries()) if (e.startTime >= docOpenedAt && !runtimeFile(e.name)) netCount++; $("netCount").textContent = netCount; $("netPill").classList.toggle("bad", netCount > 0); @@ -210,10 +234,12 @@ function applyLang() { document.documentElement.lang = lang; document.querySelectorAll("[data-i18n]").forEach(e => (e.textContent = t(e.dataset.i18n))); document.querySelectorAll("[data-i18n-ph]").forEach(e => (e.placeholder = t(e.dataset.i18nPh))); + document.querySelectorAll("[data-i18n-alt]").forEach(e => (e.alt = t(e.dataset.i18nAlt))); + document.querySelectorAll("[data-i18n-aria]").forEach(e => e.setAttribute("aria-label", t(e.dataset.i18nAria))); document.querySelectorAll("[data-i18n-tip]").forEach(e => { const s = t(e.dataset.i18nTip, { wm: wmText() }); e.dataset.tip = s; e.setAttribute("aria-label", s.split("||")[0]); e.setAttribute("role", "button"); }); document.querySelectorAll("[data-lang]").forEach(b => b.setAttribute("aria-pressed", b.dataset.lang === lang)); - document.title = lang === "tr" ? "Katip Yerel: tarayıcıda KVKK maskeleme" : "Katip Yerel: in-browser Turkish PII redaction"; - if (samples.length) { buildSampleOptions(); drawFormats(); buildModelUI(); buildEntityUI(); } + document.title = lang === "tr" ? "Katip Mask: belgedeki kişisel verileri maskeleyin" : "Katip Mask: mask personal data in documents"; + if (samples.length) { drawPicker(); buildModelUI(); buildEntityUI(); buildProfileUI(); } updateButtons(); if (res?.spans) render(); else if (preview) renderPreview(); } document.querySelectorAll("[data-lang]").forEach(b => (b.onclick = () => { @@ -224,12 +250,12 @@ document.querySelectorAll("[data-lang]").forEach(b => (b.onclick = () => { // ---------------------------------------------------------------- settings const selModel = () => document.querySelector("#models input:checked")?.value ?? models.find(m => !m.dev && !m.disabled && !m.elsewhere)?.id; -const selOcr = () => document.querySelector("#ocrModels input:checked")?.value ?? "best_int"; +const selOcr = () => document.querySelector("#ocrModels input:checked")?.value ?? "auto"; function settings() { const m = models.find(x => x.id === selModel()) ?? models[0]; return { engine: m?.id, engineName: loc(m?.name), ner: m?.ner ?? null, baseline: m?.baseline ?? null, nerDtype: m?.dtype ?? "q8", trust: m?.trust ?? null, ocrLang: selOcr(), - threshold: Number($("threshold").value), style: $("style").value, + threshold: Number($("threshold").value), style: $("style").value, addressDetail: $("addrDetail").value, entities: [...document.querySelectorAll("#entities input:checked")].map(i => i.value), mask: maskTerms, }; } @@ -248,7 +274,8 @@ function buildModelUI() { $("models").append(card); } $("ocrModels").textContent = ""; - $("ocrModels").append(optCard("ocr", "best_int", t("ocrBest"), t("ocrBestMeta"), t("ocrTip"), curOcr === "best_int", lang === "tr" ? "önerilen" : "recommended"), + $("ocrModels").append(optCard("ocr", "auto", t("ocrAuto"), t("ocrAutoMeta"), t("ocrAutoTip"), curOcr === "auto", lang === "tr" ? "önerilen" : "recommended"), + optCard("ocr", "best_int", t("ocrBest"), t("ocrBestMeta"), t("ocrTip"), curOcr === "best_int"), optCard("ocr", "fast", t("ocrFast"), t("ocrFastMeta"), t("ocrTip"), curOcr === "fast")); } $("models").onchange = () => res?.spans && redetect(); @@ -258,12 +285,35 @@ function buildEntityUI() { $("entities").textContent = ""; for (const k of Object.keys(LABELS.tr)) { if (k === "CUSTOM") continue; - const cb = el("input", { type: "checkbox", value: k, checked: prev.length ? on.has(k) : k !== "CRIMINAL_RECORD" }); + const cb = el("input", { type: "checkbox", value: k, checked: prev.length ? on.has(k) : PROFILES.court.types().includes(k) }); $("entities").append(el("label", {}, cb, " " + L(k) + (ART6.includes(k) ? " ·6" : ""))); } entCount(); } -$("entities").onchange = () => { entCount(); res?.spans && redetect(); }; +$("entities").onchange = () => { entCount(); syncProfile(); res?.spans && redetect(); }; +function buildProfileUI() { + $("profiles").textContent = ""; + for (const [id, p] of Object.entries(PROFILES)) { + const b = el("button", { type: "button", textContent: t(p.name) }); b.dataset.p = id; b.setAttribute("role", "radio"); + b.onclick = () => setProfile(id); $("profiles").append(b); + } + syncProfile(); +} +function setProfile(id) { + const p = PROFILES[id], types = new Set(p.types()); + document.querySelectorAll("#entities input").forEach(i => (i.checked = types.has(i.value))); + $("addrDetail").value = p.addr; entCount(); syncProfile(); + if (res?.spans) redetect(); +} +function syncProfile() { + const on = new Set([...document.querySelectorAll("#entities input:checked")].map(i => i.value)); + profile = Object.keys(PROFILES).find(id => { const ty = PROFILES[id].types(); return PROFILES[id].addr === $("addrDetail").value && ty.length === on.size && ty.every(k => on.has(k)); }) ?? "custom"; + document.querySelectorAll("#profiles button").forEach(b => b.setAttribute("aria-checked", b.dataset.p === profile)); + const h = $("profHint"); h.textContent = (profile === "custom" ? t("pCustomHint") : t(PROFILES[profile].hint)) + " "; + const pick = el("button", { type: "button", className: "linkbtn", textContent: t("pickTypes") }); + pick.onclick = () => { $("adv").open = true; document.querySelector("#adv details.ents").open = true; document.querySelector("#adv details.ents").scrollIntoView({ block: "center", behavior: "smooth" }); }; + h.append(pick); +} function entCount() { $("entCount").textContent = `(${document.querySelectorAll("#entities input:checked").length}/${Object.keys(LABELS.tr).length - 1})`; } function buildColorUI() { for (const [hex, name] of COLORS) { @@ -284,49 +334,93 @@ const rgb = hex => [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16) / 255); $("threshold").oninput = () => ($("thresholdVal").textContent = Number($("threshold").value).toFixed(2)); $("threshold").onchange = () => res?.spans && redetect(); $("style").onchange = () => maskOptionsChanged(); +$("addrDetail").onchange = () => { syncProfile(); res?.spans && redetect(); }; $("watermark").onchange = () => maskOptionsChanged(); $("centerMark").onchange = () => maskOptionsChanged(); function maskOptionsChanged() { if (res?.spans && applied && !busy) redactNow(); } // ---------------------------------------------------------------- sources +// ---- home: sample picker next to the before/after preview. Picking a type, variant or format runs the real pipeline +// in the background (same worker, nothing leaves the tab) and swaps the slider; "Bu örneği aç" then opens it instantly. +const sampleTitle = sm => lang === "en" ? SAMPLE_EN[sm.type] ?? sm.title : sm.title; +const types = () => [...new Map(samples.map(x => [x.type, x])).values()]; function buildSampleOptions() { - const cur = $("sampleType").value; $("sampleType").textContent = ""; - for (const s of samples) $("sampleType").append(el("option", { value: s.id, textContent: lang === "en" ? SAMPLE_EN[s.type] ?? s.title : s.title })); - if (cur) $("sampleType").value = cur; -} -function buildSampleUI() { - buildSampleOptions(); - $("sampleType").onchange = () => { src.sample = samples.find(s => s.id === $("sampleType").value); drawFormats(); loadSource(); }; - src.sample = samples[0]; drawFormats(); - $("srcSampleBtn").onclick = () => { if (src.mode === "sample") return; setMode("sample"); loadSource(); }; - $("srcFileBtn").onclick = () => { if (src.mode === "file") return; setMode("file"); if (src.file) loadSource(); }; + for (const sm of types()) $("sampleType").append(el("option", { value: sm.type, textContent: sampleTitle(sm) })); + if (src.sample) $("sampleType").value = src.sample.type; +} +function drawVariants() { + const v = $("variantBtns"); v.textContent = ""; + for (const sm of samples.filter(x => x.type === src.sample.type)) { + const b = el("button", { type: "button", textContent: String(sm.n ?? sm.id.split("_").pop()), title: `${t("sampleN")} ${sm.n}` }); + b.dataset.id = sm.id; b.setAttribute("aria-pressed", sm.id === src.sample.id); + b.onclick = () => { if (sm.id === src.sample.id) return; src.sample = sm; drawPicker(); previewSample(); }; + v.append(b); + } } function drawFormats() { const f = $("formatBtns"); f.textContent = ""; - if (!src.sample.formats.includes(src.format)) src.format = src.sample.formats[0]; + if (!src.sample.formats.includes(src.format)) src.format = src.sample.formats.includes("pdf") ? "pdf" : src.sample.formats[0]; for (const fmt of src.sample.formats) { const b = el("button", { type: "button", textContent: FORMATS[lang][fmt] ?? fmt }); b.dataset.fmt = fmt; b.setAttribute("aria-pressed", fmt === src.format); - b.onclick = () => { if (src.format === fmt) return; src.format = fmt; drawFormats(); loadSource(); }; + b.onclick = () => { if (src.format === fmt) return; src.format = fmt; drawFormats(); previewSample(); }; f.append(b); } } -function setMode(m) { - src.mode = m; - $("srcSampleBtn").setAttribute("aria-selected", m === "sample"); $("srcFileBtn").setAttribute("aria-selected", m === "file"); - $("srcSample").classList.toggle("hidden", m !== "sample"); $("srcFile").classList.toggle("hidden", m !== "file"); +function drawPicker() { buildSampleOptions(); drawVariants(); drawFormats(); baCaption(); } +function baCaption() { $("baCap").textContent = `${sampleTitle(src.sample)} · ${t("sampleN")} ${src.sample.n} · ${FORMATS[lang][src.format] ?? src.format} — ${t("baMadeUp")}`; } +const sameAsLoaded = () => loaded?.isSample && loaded.name === `${src.sample.id}.${src.format}`; +function previewSample() { baCaption(); src.mode = "sample"; loadSource({ stay: true }); } +function openSample(sm, fmt) { + src.mode = "sample"; src.sample = sm; src.format = fmt; drawPicker(); + if (sameAsLoaded() && res) { showWork(true); render(); updateButtons(); status(busy ? $("status").textContent : ""); } else loadSource(); } -function pickFile(f) { if (!f) return; src.file = f; $("fileName").textContent = f.name; setMode("file"); loadSource(); } +function buildSampleUI() { + src.sample = samples.find(x => x.id === "dilekce_01") ?? samples[0]; src.format = "png"; // matches the static hero images + $("sampleType").onchange = () => { + const keep = src.sample.n; + src.sample = samples.find(x => x.type === $("sampleType").value && x.n === keep) ?? samples.find(x => x.type === $("sampleType").value); + drawPicker(); previewSample(); + }; + $("openSample").onclick = () => openSample(src.sample, src.format); + drawPicker(); +} +// the masked/original pair for the home slider, from whatever the pipeline produced +function drawBA() { + if (!res?.spans || !applied) return; + const O = $("baFrame").querySelector(".ba-o"), M = $("baFrame").querySelector(".ba-m"); + O.textContent = ""; M.textContent = ""; + if (res.kind === "pdf" && res.origPreviews?.[0] && res.maskPreviews?.[0]) { + O.append(el("img", { src: res.origPreviews[0], alt: t("baOrigAlt") })); M.append(el("img", { src: res.maskPreviews[0], alt: t("baMaskAlt") })); + } else if (res.kind === "image") { + O.append(el("img", { src: res.imageUrl, alt: t("baOrigAlt") })); M.append(el("img", { src: res.maskUrl, alt: t("baMaskAlt") })); + } else { // Word, UDF, text: the same text twice, the masked layer with the chosen values blacked out + const tx = res.text.slice(0, 2200), on = chosen().filter(x => x.end <= tx.length).sort((a, b) => a.start - b.start); + const plain = el("div", { className: "ba-txt", textContent: tx }), masked = el("div", { className: "ba-txt" }); + let pos = 0; + for (const x of on) { if (x.start < pos) continue; masked.append(tx.slice(pos, x.start), el("span", { className: "bm", textContent: tx.slice(x.start, x.end) })); pos = x.end; } + masked.append(tx.slice(pos)); O.append(plain); M.append(masked); + } + $("baFrame").classList.remove("loading"); +} +function pickFile(f) { if (!f) return; src.file = f; src.mode = "file"; loadSource(); } $("drop").onclick = () => $("file").click(); $("drop").onkeydown = e => (e.key === "Enter" || e.key === " ") && $("file").click(); $("file").onchange = e => { pickFile(e.target.files[0]); e.target.value = ""; }; -$("drop").ondragover = e => { e.preventDefault(); $("drop").classList.add("over"); }; -$("drop").ondragleave = () => $("drop").classList.remove("over"); -$("drop").ondrop = e => { e.preventDefault(); $("drop").classList.remove("over"); pickFile(e.dataTransfer.files[0]); }; +// the whole page accepts a dropped file, not only the box +addEventListener("dragover", e => { e.preventDefault(); $("drop").classList.add("over"); }); +addEventListener("dragleave", e => { if (!e.relatedTarget) $("drop").classList.remove("over"); }); +addEventListener("drop", e => { e.preventDefault(); $("drop").classList.remove("over"); if (!$("reportDlg").open) pickFile(e.dataTransfer.files[0]); }); + +// before/after slider on the home screen +$("baRange").oninput = () => $("ba").style.setProperty("--pos", $("baRange").value + "%"); +function showWork(on) { $("home").classList.toggle("hidden", on); $("work").classList.toggle("hidden", !on); if (!on) window.scrollTo(0, 0); } // Selecting a document shows it at once; analysis starts as soon as the engine is ready. -async function loadSource() { +let stayHome = false; +async function loadSource({ stay = false } = {}) { + stayHome = stay; if (stay) $("baFrame").classList.add("loading"); const my = ++seq, isSample = src.mode === "sample"; let name, bytes, gold = null; status(t("preparing")); @@ -340,22 +434,24 @@ async function loadSource() { } catch (e) { if (my === seq) status(t("error") + (e?.message ?? e)); console.error(e); return; } if (my !== seq) return; loaded = { name, bytes, gold, isSample }; + if (!stay) showWork(true); + $("docName").textContent = isSample ? `${sampleTitle(src.sample)} ${src.sample.n} · ${FORMATS[lang][src.format] ?? src.format}` : name; res = null; applied = false; view = "review"; outBlob = null; busy = false; preview = { seq: my, kind: /\.(png|jpe?g|tiff?|bmp|webp)$/i.test(name) ? "image" : null, text: null, pages: [], imageUrl: null }; if (preview.kind === "image") preview.imageUrl = URL.createObjectURL(new Blob([bytes])); renderPreview(); if (preview.kind !== "image") send({ type: "preview", name, bytes: bytes.slice(), seq: my }); updateButtons(); - status(ready ? t("toScan") : t("waiting")); + if (ready) analyze(); else status(t("waiting")); } // ---------------------------------------------------------------- run -const status = m => ($("status").textContent = m); +const status = m => { $("status").textContent = m; $("engineState").textContent = loaded && !stayHome ? "" : m; }; const progressText = m => { if (lang === "tr") return m; for (const [re, en] of PROGRESS_EN) if (re.test(m)) return m.replace(re, en); return m; }; function analyze() { if (!loaded) return; const s = settings(), { name, gold, isSample } = loaded, bytes = loaded.bytes.slice(); - status(t("preparing")); + status(t("scanning")); docOpenedAt = performance.now(); netCount = 0; $("netCount").textContent = "0"; $("netPill").classList.remove("bad"); res = { name, isSample, gold, settings: s, imageUrl: preview?.imageUrl ?? null, maskUrl: null, seq }; off = new Set(); applied = false; view = "review"; busy = true; updateButtons(); @@ -363,34 +459,36 @@ function analyze() { } function redetect() { if (!res?.spans || busy) return; res.settings = settings(); busy = true; updateButtons(); worker.postMessage({ type: "redetect", settings: res.settings }); } function redactNow() { - busy = true; updateButtons(); status(progressText("Maskeleniyor") + "…"); + clearTimeout(reapplyTimer); busy = true; updateButtons(); status(t("applying")); worker.postMessage({ type: "redact", spans: chosen(), style: $("style").value, verifyOcr: true, ocrLang: res.settings.ocrLang, color: rgb(maskColor), watermark: $("watermark").checked && CONFIG.watermark ? { text: wmText(), url: CONFIG.watermark.url } : null, mark: $("centerMark").checked }); } -$("apply").onclick = () => res?.spans && !busy && redactNow(); +let reapplyTimer = 0, dlAfter = false; +const scheduleReapply = () => { clearTimeout(reapplyTimer); reapplyTimer = setTimeout(() => res?.spans && (busy ? scheduleReapply() : redactNow()), 500); }; $("scan").onclick = () => ready && loaded && !busy && analyze(); $("addTerm").onclick = () => addTerm($("term").value); $("term").onkeydown = e => e.key === "Enter" && $("addTerm").click(); function addTerm(v) { v = (v ?? "").trim(); if (!v || !res?.spans) return; maskTerms.push(v); $("term").value = ""; redetect(); } -document.querySelectorAll("#viewSeg [data-view]").forEach(b => (b.onclick = () => { if (b.disabled) return; view = b.dataset.view; render(); })); +document.querySelectorAll("#viewSeg [data-view]").forEach(b => (b.onclick = () => { + if (b.disabled) return; view = b.dataset.view; + if (view === "result" && dirty() && !busy) redactNow(); render(); +})); function updateButtons() { const has = !!res?.spans; - $("scan").disabled = !ready || !loaded || busy; $("scan").textContent = has ? t("rescan") : t("scan"); $("scan").classList.toggle("primary", !has); - $("apply").classList.toggle("hidden", !has); $("download").classList.toggle("hidden", !has); - $("apply").disabled = !has || busy; $("apply").textContent = applied ? t("reapply") : t("apply"); - $("apply").classList.toggle("primary", !applied || dirty()); - $("download").disabled = !applied || busy; $("download").classList.toggle("primary", applied && !dirty()); - $("reportBtn").disabled = !has; + $("scan").disabled = !ready || !loaded || busy; + $("download").disabled = !has || busy; document.querySelector('#viewSeg [data-view="result"]').disabled = !applied; document.querySelectorAll("#viewSeg [data-view]").forEach(b => b.setAttribute("aria-selected", b.dataset.view === view)); + $("work").classList.toggle("busy", busy); } worker.onmessage = async ({ data: m }) => { if (m.type === "alive") { workerAlive = true; for (const [msg, tr] of outbox.splice(0)) worker.postMessage(msg, tr); return; } if (m.type === "ready") { - ready = true; status(loaded && !res ? t("toScan") : t("ready", { s: (m.ms / 1000).toFixed(1) })); getTess("best_int"); updateButtons(); + ready = true; getTess("best_int"); updateButtons(); + if (loaded && !res) analyze(); else status(t("ready")); } else if (m.type === "progress") status(progressText(m.msg) + "…"); else if (m.type === "preview") { if (!preview || m.seq !== preview.seq) return; @@ -403,13 +501,13 @@ worker.onmessage = async ({ data: m }) => { if (!res || m.seq !== res.seq || m.seq !== seq) return; Object.assign(res, { kind: m.kind, text: m.text, spans: m.spans, pages: m.pages, pageSizes: m.pageSizes, ocrPages: m.ocrPages, timing: m.timing, nerCount: m.nerCount, origPreviews: m.previews.map(p => URL.createObjectURL(new Blob([p], { type: "image/png" }))) }); - off = new Set(m.spans.filter(s => s.flag_only || !res.settings.entities.includes(s.entity_type)).map(key)); - busy = false; status(t("found", { n: chosen().length }) + "."); render(); + off = new Set(m.spans.filter(s => !res.settings.entities.includes(s.entity_type)).map(key)); + busy = false; render(); redactNow(); // show the masked result straight away; the list lets them undo any item } else if (m.type === "redetected") { if (!res?.spans) return; const wasOff = new Set([...off].filter(k => res.spans.some(s => key(s) === k && !s.flag_only))); res.spans = m.spans; Object.assign(res.timing, m.timing); res.nerCount = m.nerCount; - off = new Set(m.spans.filter(s => s.flag_only || wasOff.has(key(s))).map(key)); + off = new Set(m.spans.filter(s => wasOff.has(key(s))).map(key)); busy = false; if (applied) redactNow(); else { status(t("found", { n: chosen().length }) + "."); render(); } } else if (m.type === "redacted") { if (!res?.spans) return; @@ -417,14 +515,15 @@ worker.onmessage = async ({ data: m }) => { Object.assign(res, { outText: m.outText, leaks: m.leaks, unchecked: m.unchecked, redactMs: m.ms, maskPreviews: m.previews.map(p => URL.createObjectURL(new Blob([p], { type: "image/png" }))), appliedKeys: new Set(chosen().map(key)) }); if (res.kind === "image") res.maskUrl = URL.createObjectURL(outBlob); - busy = false; applied = true; view = "result"; - status(t("done", { n: chosen().length })); render(); + busy = false; applied = true; if (!dirty()) view = "result"; + status(""); render(); if (!$("home").classList.contains("hidden")) drawBA(); + if (dlAfter && !dirty()) { dlAfter = false; saveOut(); } else if (dirty()) scheduleReapply(); } else if (m.type === "maskedText") { await navigator.clipboard.writeText(m.text); status(t("copied")); } else if (m.type === "original") { pendingOriginal?.(m); } else if (m.type === "forgotten") { res = null; outBlob = null; loaded = null; preview = null; applied = false; busy = false; seq++; - $("rv").classList.add("hidden"); $("more").classList.add("hidden"); $("scorebar").classList.add("hidden"); $("empty").hidden = false; - updateButtons(); status(t("forgotten")); + $("scorebar").classList.add("hidden"); showWork(false); + updateButtons(); status(ready ? t("ready") : t("loading")); } else if (m.type === "error") { busy = false; updateButtons(); status(t("error") + m.msg); console.error(m.msg, m.stack); } }; worker.onerror = e => { status(t("error") + (e.message || "worker")); console.error(e); }; @@ -440,12 +539,16 @@ function editDist(a, b) { return prev[b.length]; } const ocrMatches = (a, b) => { if (exactMatches(a, b)) return true; const x = sq(a), y = sq(b); return y.length >= 5 && editDist(x, y) <= Math.max(1, Math.floor(y.length * 0.15)); }; -function goldEval(spans, gold, ocr = false) { +function goldEval(spans, gold, ocr = false, streetVisible = false) { + // image areas and the case-detail types of the stricter profiles are outside the answer key + spans = spans.filter(s => s.entity_type !== "UNREAD" && !CASE.includes(s.entity_type) && !ANON.includes(s.entity_type)); const matches = ocr ? ocrMatches : exactMatches; const g = gold.filter(x => x.entity_type !== "CRIMINAL_RECORD"); - const found = g.map(x => spans.some(s => matches(s.text, x.text))); + // With "street visible" (the default) only the building/door part of an address is masked by design, + // so an address counts as found when a masked address span sits inside it. + const found = g.map(x => spans.some(s => matches(s.text, x.text) || (streetVisible && x.entity_type === "ADDRESS" && s.entity_type === "ADDRESS" && sq(s.text).length >= 2 && sq(x.text).includes(sq(s.text))))); const partial = g.map((x, i) => found[i] ? null : spans.find(s => sq(s.text).length >= 2 && sq(x.text).includes(sq(s.text)))?.text ?? null); - const fps = spans.filter(s => !g.some(x => matches(s.text, x.text) || matches(x.text, s.text))); + const fps = spans.filter(s => !g.some(x => matches(s.text, x.text) || matches(x.text, s.text) || (streetVisible && s.entity_type === "ADDRESS" && x.entity_type === "ADDRESS" && sq(x.text).includes(sq(s.text))))); const per = {}; g.forEach((x, i) => { const p = per[x.entity_type] ??= { gold: 0, found: 0, fp: 0 }; p.gold++; if (found[i]) p.found++; }); for (const s of fps) (per[s.entity_type] ??= { gold: 0, found: 0, fp: 0 }).fp++; @@ -454,14 +557,14 @@ function goldEval(spans, gold, ocr = false) { } // ---------------------------------------------------------------- rendering -const key = s => `${s.entity_type}:${s.start}:${s.end}`; +const key = s => s.uid ?? `${s.entity_type}:${s.start}:${s.end}`; const chosen = () => (res?.spans ?? []).filter(s => !off.has(key(s))); const dirty = () => !!(applied && res?.appliedKeys && (res.appliedKeys.size !== chosen().length || chosen().some(s => !res.appliedKeys.has(key(s))))); -function toggle(s) { off.has(key(s)) ? off.delete(key(s)) : off.add(key(s)); if (view === "result") view = "review"; render(); } +function toggle(s) { off.has(key(s)) ? off.delete(key(s)) : off.add(key(s)); render(); if (view === "result") scheduleReapply(); } function renderPreview() { // before analysis: the document as it is, no highlights yet - $("empty").hidden = true; $("rv").classList.remove("hidden"); $("scorebar").classList.add("hidden"); $("more").classList.add("hidden"); + $("scorebar").classList.add("hidden"); const D = $("docView"); D.textContent = ""; D.className = "docview"; if (preview.kind === "image" && preview.imageUrl) D.append(el("div", { className: "pg" }, el("img", { src: preview.imageUrl, alt: t("origAlt") }))); else if (preview.pages.length) for (const u of preview.pages) D.append(el("div", { className: "pg" }, el("img", { src: u, alt: t("origAlt") }))); @@ -469,31 +572,33 @@ function renderPreview() { else D.append(el("div", { className: "skel" })); $("list").textContent = ""; $("fCount").textContent = ""; document.querySelector(".fhead").classList.add("hidden"); - $("list").append(el("p", { className: "hint", textContent: t("listHint") })); + $("list").append(el("div", { className: "skel sm" })); } function render() { if (!res?.spans) { if (preview) renderPreview(); updateButtons(); return; } if (!applied && view === "result") view = "review"; - $("empty").hidden = true; $("rv").classList.remove("hidden"); $("more").classList.remove("hidden"); - res.eval = res.gold ? goldEval(chosen(), res.gold, res.kind === "image" || res.ocrPages > 0) : null; + + res.eval = res.gold ? goldEval(chosen(), res.gold, res.kind === "image" || res.ocrPages > 0, res.settings.addressDetail === "street") : null; updateButtons(); renderScore(); renderDoc(); renderList(); renderGold(); renderTiming(); } function renderScore() { const S = $("scorebar"); S.textContent = ""; const ev = res.eval, items = []; - if (ev) { - items.push(el("span", { className: "sc" }, el("b", { textContent: `${ev.found.filter(Boolean).length}/${ev.gold.length}` }), " " + t("sRecall"))); - items.push(el("span", { className: "sc" + (ev.fps.length ? " warn-t" : "") }, el("b", { textContent: String(ev.fps.length) }), " " + t("sFp"))); - } - if (applied) { + if (applied && !busy) { const n = res.leaks.length, cls = n ? "bad-t" : res.unchecked ? "warn-t" : "ok-t"; - items.push(el("span", { className: "sc " + cls }, el("b", { textContent: String(n) }), " " + t("sLeak"))); - items.push(el("span", { className: "msg " + cls, textContent: n ? t("leakBad", { n }) : res.unchecked ? t("leakUnchecked") : t("leakOk") })); + items.push(el("span", { className: "msg " + cls }, el("b", { textContent: n ? "⚠ " : res.unchecked ? "! " : "✓ " }), n ? t("leakBad", { n }) : res.unchecked ? t("leakUnchecked") : t("leakOk"))); + if (n) { // the same value appears again somewhere it was not caught: one click masks every occurrence + const fix = el("button", { type: "button", className: "primary sm", textContent: t("leakFix") }); + fix.onclick = () => { for (const id of res.leaks) { const sp = res.spans.find(x => x.id === id || x.uid === id); if (sp?.text && !maskTerms.includes(sp.text)) maskTerms.push(sp.text); } redetect(); }; + items.push(fix); + } } if (dirty()) items.push(el("span", { className: "msg warn-t", textContent: t("sNotApplied") })); - else if (!applied) items.push(el("span", { className: "msg", textContent: t("sPreview") })); + if (ev) items.push(el("span", { className: "msg sample" }, t("sampleNote") + " ", + el("b", { textContent: `${ev.found.filter(Boolean).length}/${ev.gold.length}` }), " " + t("sRecall") + " · ", + el("b", { className: ev.fps.length ? "warn-t" : "", textContent: String(ev.fps.length) }), " " + t("sFp"))); S.append(...items); S.classList.toggle("hidden", !items.length); } @@ -534,7 +639,7 @@ function textWithMarks() { const box = el("div", { className: "txt" }); const tx = res.text, ev = res.eval, miss = []; if (ev) ev.gold.forEach((g, i) => { if (!ev.found[i]) { const j = tx.toLocaleLowerCase("tr").indexOf(g.text.toLocaleLowerCase("tr")); if (j >= 0) miss.push({ start: j, end: j + g.text.length, miss: true, g }); } }); - const items = [...res.spans, ...miss].sort((a, b) => a.start - b.start); + const items = [...res.spans.filter(s => s.end > s.start), ...miss].sort((a, b) => a.start - b.start); let pos = 0; for (const s of items) { if (s.start < pos) continue; @@ -560,14 +665,19 @@ function renderList() { const on = spans.filter(s => !off.has(key(s))).length; const all = el("input", { type: "checkbox", checked: on === spans.length, indeterminate: on > 0 && on < spans.length, ariaLabel: L(type) }); all.onchange = () => { for (const s of spans) all.checked ? off.delete(key(s)) : off.add(key(s)); render(); }; - const rows = spans.map(s => { - const cb = el("input", { type: "checkbox", checked: !off.has(key(s)), ariaLabel: s.text }); cb.onchange = () => toggle(s); - const fp = res.eval && res.eval.fps.some(f => key(f) === key(s)); + // the same value found several times is one row (×n); its tick covers every occurrence + const same = new Map(); + spans.forEach((s, k) => { const id = s.text ? sq(s.text) : `#${k}`; same.set(id, [...(same.get(id) ?? []), [s, k]]); }); + const rows = [...same.values()].map(occ => { + const [s, k] = occ[0], all = occ.map(([x]) => x), isOn = all.some(x => !off.has(key(x))); + const cb = el("input", { type: "checkbox", checked: isOn, ariaLabel: s.text }); + cb.onchange = () => { for (const x of all) cb.checked ? off.delete(key(x)) : off.add(key(x)); render(); if (view === "result") scheduleReapply(); }; + const fp = res.eval && all.some(x => res.eval.fps.some(f => key(f) === key(x))); const flag = el("button", { type: "button", className: "flag", textContent: "⚑", title: t("flag"), ariaLabel: t("flag") }); - flag.onclick = e => { e.preventDefault(); openReport(off.has(key(s)) ? "missed" : "false_positive", s.text); }; - const v = el("span", { className: "v" + (fp ? " fp-t" : ""), textContent: s.text.replace(/\s+/g, " "), title: `${s.recognizer} · ${s.score.toFixed(2)}${s.flag_only ? " · " + t("warnOnly") : ""}` }); + flag.onclick = e => { e.preventDefault(); openReport(isOn ? "false_positive" : "missed", s.text); }; + const v = el("span", { className: "v" + (fp ? " fp-t" : ""), textContent: s.text ? s.text.replace(/\s+/g, " ") : t("unreadItem", { p: s.page + 1, i: k + 1 }), title: `${s.recognizer} · ${s.score.toFixed(2)}${s.flag_only ? " · " + t("warnOnly") : ""}` }); v.onclick = e => { e.preventDefault(); document.querySelector(`#docView [data-k="${CSS.escape(key(s))}"]`)?.scrollIntoView({ block: "center", behavior: "smooth" }); }; - return el("div", { className: "it" }, cb, v, flag); + return el("div", { className: "it" }, cb, v, occ.length > 1 ? el("span", { className: "times", textContent: `×${occ.length}` }) : null, flag); }); box.append(el("div", { className: "grp" }, el("label", { className: "gh" }, all, el("b", { textContent: L(type) }), el("span", { textContent: `${on}/${spans.length}` })), ...rows)); } @@ -590,19 +700,27 @@ function renderGold() { el("p", { className: "hint", textContent: t("goldNote") })); } function renderTiming() { - const tm = res.timing ?? {}, rows = [[t("kModel"), res.settings.engineName], [t("kOcr"), res.ocrPages ? (res.settings.ocrLang === "fast" ? t("ocrFast") : t("ocrBest")) : t("unused")], + const tm = res.timing ?? {}, rows = [[t("kModel"), res.settings.engineName], [t("kOcr"), res.ocrPages ? ({ fast: t("ocrFast"), auto: t("ocrAuto"), best_int: t("ocrBest") }[res.settings.ocrLang] ?? res.settings.ocrLang) : t("unused")], [t("kPages"), `${res.pages}${res.ocrPages ? ` (${res.ocrPages} OCR)` : ""}`], [t("kParse"), `${tm.parse} ms`], ...(res.settings.ner ? [["NER", `${tm.ner} ms · ${res.nerCount}`]] : []), [t("kRules"), `${tm.rules} ms`], ...(applied ? [[t("kRedact"), `${res.redactMs} ms`]] : []), [t("kVersion"), version.rules ?? ""]]; $("timing").textContent = ""; for (const [a, b] of rows) $("timing").append(el("tr", {}, el("td", { textContent: a }), el("td", { textContent: b }))); } -$("download").onclick = () => { if (!outBlob) return; const a = el("a", { href: URL.createObjectURL(outBlob), download: outName }); a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 30000); }; -$("copy").onclick = () => { res?.spans && worker.postMessage({ type: "maskedText", spans: chosen() }); $("copy").closest("details").open = false; }; -$("clear").onclick = () => { worker.postMessage({ type: "forget" }); $("clear").closest("details").open = false; }; +function saveOut() { if (!outBlob) return; const a = el("a", { href: URL.createObjectURL(outBlob), download: outName }); a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 30000); } +// one button: if the ticks changed since the last masking, mask again first, then save +$("download").onclick = () => { if (!res?.spans || busy) return; if (!applied || dirty()) { dlAfter = true; redactNow(); } else saveOut(); }; +$("copy").onclick = () => { res?.spans && worker.postMessage({ type: "maskedText", spans: chosen() }); }; +$("clear").onclick = () => { + res = null; outBlob = null; loaded = null; preview = null; applied = false; busy = false; seq++; maskTerms = []; dlAfter = false; + stayHome = false; $("baFrame").classList.remove("loading"); + send({ type: "forget" }); $("scorebar").classList.add("hidden"); $("adv").open = false; showWork(false); updateButtons(); status(ready ? t("ready") : t("loading")); +}; // ---------------------------------------------------------------- report a mistake let pendingOriginal = null; -function openReport(kind = "missed", what = "") { - if (!res?.spans) return; +function openReport(kind = "general", what = "") { + const has = !!res?.spans; + document.querySelectorAll("#reportForm .docOnly").forEach(e => e.classList.toggle("hidden", !has)); + if (!has) { kind = "general"; $("rStatus").textContent = ""; $("rHow").textContent = t("rHowGeneral", { to: CONFIG.reportEmail }); $("rSend").classList.add("hidden"); return $("reportDlg").showModal(); } document.querySelector(`#reportForm input[name=kind][value=${kind}]`).checked = true; $("rWhat").value = what; $("rStatus").textContent = ""; $("incMasked").checked = applied; $("incMasked").disabled = !applied; @@ -611,8 +729,16 @@ function openReport(kind = "missed", what = "") { $("rSend").classList.toggle("hidden", !CONFIG.reportEndpoint); $("rHow").textContent = CONFIG.reportEndpoint ? t("rHowSend", { host: new URL(CONFIG.reportEndpoint).host }) : t("rHowMail", { to: CONFIG.reportEmail }); $("reportDlg").showModal(); + document.querySelector(`#reportForm input[name=kind][value=${kind}]`).dispatchEvent(new Event("change", { bubbles: true })); } $("reportBtn").onclick = () => openReport(); +$("reportForm").onchange = e => { + if (e.target.name !== "kind" || !res?.spans) return; + const general = e.target.value === "general"; + for (const id of ["rWhat"]) { $(id).classList.toggle("hidden", general); document.querySelector(`label[for=${id}]`).classList.toggle("hidden", general); } + document.querySelector("#reportForm details.checks").classList.toggle("hidden", general); + $("rHow").textContent = general ? t("rHowGeneral", { to: CONFIG.reportEmail }) : CONFIG.reportEndpoint ? t("rHowSend", { host: new URL(CONFIG.reportEndpoint).host }) : t("rHowMail", { to: CONFIG.reportEmail }); +}; async function buildReport() { const kind = document.querySelector("#reportForm input[name=kind]:checked").value; @@ -638,8 +764,19 @@ async function buildReport() { } return { id, report, blob: await zip.generateAsync({ type: "blob", compression: "DEFLATE" }) }; } -const KIND_TR = { missed: "Kaçan veri", false_positive: "Gereksiz maskeleme", broken: "Bozuk çıktı", other: "Diğer" }; +const KIND_TR = { general: "Genel görüş", missed: "Kaçan veri", false_positive: "Gereksiz maskeleme", broken: "Bozuk çıktı", other: "Diğer" }; +const KIND_EN = { general: "General feedback", missed: "Left visible", false_positive: "Masked by mistake", broken: "File won't open", other: "Other" }; +function mailGeneral() { + const kind = document.querySelector("#reportForm input[name=kind]:checked").value; + const subject = lang === "en" ? `Katip Mask feedback: ${KIND_EN[kind]}` : `Katip Mask görüş: ${KIND_TR[kind]}`; + const body = [$("rText").value, "", `${location.origin + location.pathname} · ${version.app} · ${version.rules}`, + res?.spans ? `${res.kind} · ${res.settings.engineName} · ${res.spans.length}/${chosen().length}` : null].filter(x => x != null).join("\n"); + location.href = `mailto:${CONFIG.reportEmail}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body.slice(0, 1800))}`; + $("rStatus").textContent = t("rHowGeneral", { to: CONFIG.reportEmail }); + window.__lastMailto = { subject, body, fname: null }; +} $("rMail").onclick = async () => { + if (!res?.spans || document.querySelector("#reportForm input[name=kind]:checked").value === "general") return mailGeneral(); const { id, report, blob } = await buildReport(); const fname = `katip-rapor-${id}.zip`; const a = el("a", { href: URL.createObjectURL(blob), download: fname }); a.click(); @@ -648,7 +785,7 @@ $("rMail").onclick = async () => { `Belge: ${report.document.name} (${report.document.kind}, ${report.document.pages} sayfa)`, `Model: ${report.settings.engineName} · eşik ${report.settings.threshold} · OCR ${report.settings.ocrLang}`, sc && `Puan: yakalama ${(sc.recall * 100).toFixed(1)}%, kesinlik ${(sc.precision * 100).toFixed(1)}%`, `Sürüm: ${version.rules} (${version.built})`, "", `Lütfen indirilen ${fname} dosyasını bu e-postaya ekleyin. / Please attach ${fname}.`].filter(x => x !== null && x !== undefined && x !== false).join("\n"); - const subject = `Katip Yerel hata bildirimi: ${KIND_TR[report.feedback.kind]} (${report.document.kind})`; + const subject = `Katip Mask hata bildirimi: ${KIND_TR[report.feedback.kind]} (${report.document.kind})`; location.href = `mailto:${CONFIG.reportEmail}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body.slice(0, 1800))}`; $("rStatus").textContent = t("rDownloaded", { f: fname, to: CONFIG.reportEmail }); window.__lastMailto = { subject, body, fname }; @@ -658,7 +795,7 @@ $("rSend").onclick = async () => { try { const { id, report, blob } = await buildReport(); const fd = new FormData(); - fd.append("_subject", `Katip Yerel hata bildirimi ${id}`); fd.append("email", CONFIG.reportEmail); + fd.append("_subject", `Katip Mask hata bildirimi ${id}`); fd.append("email", CONFIG.reportEmail); fd.append("message", `${KIND_TR[report.feedback.kind]}: ${report.feedback.what}\n\n${report.feedback.text}`); fd.append("report", JSON.stringify(report)); fd.append("attachment", blob, `katip-rapor-${id}.zip`); const r = await fetch(CONFIG.reportEndpoint, { method: "POST", body: fd, headers: { Accept: "application/json" } }); @@ -673,8 +810,9 @@ $("rSend").onclick = async () => { [samples, models, version] = await Promise.all(["samples/manifest.json", "models/registry.json", "version.json"].map(async u => { const r = await fetch(u); if (!r.ok) throw new Error(`${u}: HTTP ${r.status}`); return r.json(); })); } catch (e) { status(t("error") + e.message); return; } - $("version").textContent = `Katip Yerel ${version.app} · ${version.rules} · ${version.built}`; + $("version").textContent = `Katip Mask ${version.app} · ${version.rules} · ${version.built}`; if (version.source) $("srcLink").href = version.source; - buildColorUI(); buildSampleUI(); buildModelUI(); buildEntityUI(); applyLang(); - loadSource(); + buildColorUI(); buildSampleUI(); buildModelUI(); buildEntityUI(); buildProfileUI(); applyLang(); + const q = new URLSearchParams(location.search).get("sample"); // ?sample=dilekce:udf opens a sample directly (handy for sharing) + if (q) { const [ty, fm] = q.split(":"), sm = samples.find(x => x.type === ty || x.id === ty); if (sm) openSample(sm, sm.formats.includes(fm) ? fm : sm.formats[0]); } })(); diff --git a/config.js b/config.js index b62e432b6b9f09ee2b6343bea61d4fceaf414902..3887e1009037a3f3234b962d782a011da75d4d92 100644 --- a/config.js +++ b/config.js @@ -9,7 +9,7 @@ export default { // Credit line added to redacted PDF, image, Word and text output when "Filigran" is ticked. // Keep the text ASCII: it is drawn with a standard PDF font. watermark: { - text: { tr: "Katip Yerel ile maskelendi - fikrifirat.github.io/katip", en: "Redacted with Katip Yerel - fikrifirat.github.io/katip" }, + text: { tr: "Katip Mask ile maskelendi - fikrifirat.github.io/katip", en: "Redacted with Katip Mask - fikrifirat.github.io/katip" }, url: "https://fikrifirat.github.io/katip/", }, }; diff --git a/engine.js b/engine.js index 7c561a5292da5af128e54020e60c43f17cbe4a16..cc73d395578df8e3b4276f390d598b3a06e2a4bb 100644 --- a/engine.js +++ b/engine.js @@ -54,7 +54,7 @@ function pdfLines(page) { return lines.map(l => l.sort((a, b) => a.bbox[0] - b.bbox[0])); } -// ocr(pngBytes) must resolve to lines of {text, bbox:[x0,y0,x1,y1]} in pixels +// ocr(pngBytes) must resolve to lines of {text, bbox:[x0,y0,x1,y1]} in pixels; ocr.engine names it ("paddle" or Tesseract by default) export async function parsePdf(mupdf, bytes, ocr, { forceOcr = false, onProgress } = {}) { const doc = mupdf.Document.openDocument(bytes, "application/pdf"); const n = doc.countPages(), pages = []; @@ -63,29 +63,224 @@ export async function parsePdf(mupdf, bytes, ocr, { forceOcr = false, onProgress onProgress?.(`Sayfa ${i + 1}/${n}`); const page = doc.loadPage(i); const [x0, y0, x1, y1] = page.getBounds(); - const p = { number: i, width: x1 - x0, height: y1 - y0, words: [], ocr: false, lines: forceOcr ? [] : pdfLines(page) }; + const p = { number: i, width: x1 - x0, height: y1 - y0, words: [], ocr: false, engine: null, lines: forceOcr ? [] : pdfLines(page) }; if (p.lines.flat().reduce((s, w) => s + w.text.length, 0) < MIN_TEXT_CHARS) { if (!ocr) throw new Error("Taranmış sayfa var ama OCR yüklenmedi"); onProgress?.(`OCR sayfa ${i + 1}/${n}`); - const s = OCR_DPI / 72; + const s = ocrScale(p.width, p.height); const pix = page.toPixmap(mupdf.Matrix.scale(s, s), mupdf.ColorSpace.DeviceRGB, false); - const png = pix.asPNG(); pix.destroy?.(); - p.lines = (await ocr(png)).map(l => l.map(w => ({ text: w.text, bbox: w.bbox.map(v => v / s) }))); - p.ocr = true; ocrPages++; + const { colour, ...prep } = prepareScan(pix) ?? {}; + p.prep = prep; + const pen = unreadInk(pix, [], { colour }).filter(b => b.pen >= PEN_BLUE); + const read = pickOcr(ocr, pen, prep); + let raw = await read(pix.asPNG()); + const blobs = unreadInk(pix, raw.flat().map(w => w.bbox), { colour }); + const again = await reocrBlobs(mupdf, pix, blobs, read); + raw = mergeLines(raw, again.words); + p.unread = again.unread.map(b => Object.assign(b.map(v => v / s), { pen: b.pen })); + p.pen = pen.map(b => Object.assign(b.map(v => v / s), { pen: b.pen })); + p.reocr = again.words.length; + pix.destroy?.(); + p.lines = raw.map(l => l.map(w => ({ text: w.text, bbox: w.bbox.map(v => v / s) }))); + p.ocr = true; p.engine = read.engine ?? "tesseract"; ocrPages++; } pages.push(p); } return { kind: "pdf", text: assemble(pages), pages, ocrPages, _doc: doc }; } -export async function parseImage(bytes, ocr, size) { - const p = { number: 0, width: size?.width ?? 0, height: size?.height ?? 0, words: [], ocr: true, lines: await ocr(bytes) }; +// "auto" OCR: an ocr function may carry forPage({ pen, cropped }) to pick a reader per page. Katip Yerel +// uses it to send handwriting and phone photos to PaddleOCR and printed scans to Tesseract. +// pen = ballpoint-blue ink blobs found before any OCR; cropped = a photographed sheet was cut out of a background. +export const HANDWRITING_MIN_BLOBS = 3; +function pickOcr(ocr, pen, prep) { + return ocr.forPage?.({ pen: pen.length, cropped: !!prep?.cropped, handwriting: pen.length >= HANDWRITING_MIN_BLOBS || !!prep?.cropped }) ?? ocr; +} + +export async function parseImage(bytes, ocr, size, mupdf = null) { + let png = bytes, prep = null; + if (mupdf) { // same clean-up as scanned PDF pages; pixel coordinates stay those of the original image + const img = new mupdf.Image(bytes), pix = img.toPixmap(); + const rgb = pix.getNumberOfComponents() === 3 && !pix.getAlpha?.() ? pix : pix.convertToColorSpace(mupdf.ColorSpace.DeviceRGB, false); + const { colour, ...rest } = prepareScan(rgb) ?? {}; + prep = rest; png = rgb.asPNG().slice(); + const pen = unreadInk(rgb, [], { colour }).filter(b => b.pen >= PEN_BLUE), read = pickOcr(ocr, pen, prep); + let lines = await read(png); + const again = await reocrBlobs(mupdf, rgb, unreadInk(rgb, lines.flat().map(w => w.bbox), { colour }), read); + lines = mergeLines(lines, again.words); + const p = { number: 0, width: size?.width ?? rgb.getWidth(), height: size?.height ?? rgb.getHeight(), words: [], ocr: true, engine: read.engine ?? "tesseract", prep, unread: again.unread, pen, lines }; + return { kind: "image", text: assemble([p]), pages: [p], ocrPages: 1 }; + } + const p = { number: 0, width: size?.width ?? 0, height: size?.height ?? 0, words: [], ocr: true, prep, unread: [], lines: await ocr(png) }; return { kind: "image", text: assemble([p]), pages: [p], ocrPages: 1 }; } +// ---- handwriting the OCR could not read +// After prepareScan: dark strokes that no OCR word covers, with ruled lines (form borders) removed and +// strokes grouped into word-sized blobs. On a filled-in form these are mostly handwritten values the +// OCR skipped (an ID number, a name) and signatures; they become UNREAD items so they can be masked +// even though there is no text to check. Boxes are in pixmap pixels. +export function unreadInk(pix, wordBoxes, { dark = 150, colour = null } = {}) { + const w = pix.getWidth(), h = pix.getHeight(), stride = pix.getStride(), n = pix.getNumberOfComponents(), px = pix.getPixels(); + const ink = new Uint8Array(w * h); + for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) ink[y * w + x] = px[y * stride + x * n] < dark ? 1 : 0; + // ruled lines: long horizontal / vertical runs of ink. A photographed line is slanted and a few pixels + // thick, so a pixel counts as "on" for the run if any pixel within 2 rows (columns) of it is ink, small + // gaps are bridged, and the whole band around a found run is cleared. + const L = Math.max(40, Math.round(Math.max(w, h) / 30)), T = 2, GAP = 3; + const clearH = (y, a, b) => { for (let yy = Math.max(0, y - T - 1); yy <= Math.min(h - 1, y + T + 1); yy++) for (let k = a; k < b; k++) if (ink[yy * w + k]) ink[yy * w + k] = 2; }; + const clearV = (x, a, b) => { for (let xx = Math.max(0, x - T - 1); xx <= Math.min(w - 1, x + T + 1); xx++) for (let k = a; k < b; k++) if (ink[k * w + xx]) ink[k * w + xx] = 2; }; + for (let y = 0; y < h; y += 2) { + let a = -1, gap = 0; + for (let x = 0; x <= w; x++) { + let on = false; + if (x < w) for (let yy = Math.max(0, y - T); yy <= Math.min(h - 1, y + T) && !on; yy++) on = ink[yy * w + x] > 0; + if (on) { if (a < 0) a = x; gap = 0; } else if (a >= 0 && ++gap > GAP) { if (x - gap - a >= L) clearH(y, a, x - gap + 1); a = -1; gap = 0; } + } + } + for (let x = 0; x < w; x += 2) { + let a = -1, gap = 0; + for (let y = 0; y <= h; y++) { + let on = false; + if (y < h) for (let xx = Math.max(0, x - T); xx <= Math.min(w - 1, x + T) && !on; xx++) on = ink[y * w + xx] === 1; + if (on) { if (a < 0) a = y; gap = 0; } else if (a >= 0 && ++gap > GAP) { if (y - gap - a >= L) clearV(x, a, y - gap + 1); a = -1; gap = 0; } + } + } + // what the OCR read + const P = 3; + for (const [x0, y0, x1, y1] of wordBoxes) + for (let y = Math.max(0, Math.floor(y0) - P); y < Math.min(h, Math.ceil(y1) + P); y++) + for (let x = Math.max(0, Math.floor(x0) - P); x < Math.min(w, Math.ceil(x1) + P); x++) ink[y * w + x] = 0; + // coarse grid, joined sideways so the strokes of one handwritten word become one blob + const C = Math.max(3, Math.round(Math.max(w, h) / 600)), gw = Math.ceil(w / C), gh = Math.ceil(h / C), g = new Uint8Array(gw * gh); + for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (ink[y * w + x] === 1) g[((y / C) | 0) * gw + ((x / C) | 0)]++; + const on = new Uint8Array(gw * gh), JX = 4, JY = 0; + for (let y = 0; y < gh; y++) for (let x = 0; x < gw; x++) if (g[y * gw + x] >= 2) + for (let dy = -JY; dy <= JY; dy++) for (let dx = -JX; dx <= JX; dx++) { const yy = y + dy, xx = x + dx; if (yy >= 0 && yy < gh && xx >= 0 && xx < gw) on[yy * gw + xx] = 1; } + const seen = new Uint8Array(gw * gh), boxes = [], stack = []; + for (let i = 0; i < gw * gh; i++) { + if (!on[i] || seen[i]) continue; + let bx0 = gw, by0 = gh, bx1 = 0, by1 = 0, mass = 0; + stack.push(i); seen[i] = 1; + while (stack.length) { + const j = stack.pop(), x = j % gw, y = (j / gw) | 0; + bx0 = Math.min(bx0, x); bx1 = Math.max(bx1, x); by0 = Math.min(by0, y); by1 = Math.max(by1, y); mass += g[j]; + for (const k of [j - 1, j + 1, j - gw, j + gw]) if (k >= 0 && k < gw * gh && on[k] && !seen[k] && Math.abs((k % gw) - x) <= 1) { seen[k] = 1; stack.push(k); } + } + // undo the sideways join on the box edges, then keep word-sized blobs with real ink in them + const x0 = (bx0 + JX) * C, x1 = (bx1 - JX + 1) * C, y0 = (by0 + JY) * C, y1 = (by1 - JY + 1) * C; + const bw = x1 - x0, bh = y1 - y0, minH = Math.max(h, w) / 160; + if (bw < 2 * minH || bh < minH || bh > h / 16 || bw > w * 0.5 || mass < 4 * minH) continue; // thin = left-over ruling + // mean blueness (blue minus red) of the blob's ink: ballpoint ink is bluer than printed black + let inkN = 0, colN = 0; + if (colour) for (let y = Math.max(0, y0); y < Math.min(h, y1); y++) for (let x = Math.max(0, x0); x < Math.min(w, x1); x++) + if (ink[y * w + x] === 1) { inkN++; colN += colour[y * w + x] - 128; } + const box = [x0, y0, x1, y1]; + box.pen = inkN ? colN / inkN : 0; + boxes.push(box); + } + return boxes; +} + +// Second look at what the page OCR skipped: Tesseract's page layout analysis often drops whole rows of +// a ruled form (two-line label cells, handwriting between lines), but reads the same strokes when given +// them alone. Each blob (with a margin) is cut out and read again; blobs that now give text become words +// of the page, the rest stay "unread". Coordinates are pixmap pixels. +export async function reocrBlobs(mupdf, pix, blobs, ocr, { pad = 10, maxBlobs = 80 } = {}) { + const w = pix.getWidth(), h = pix.getHeight(), stride = pix.getStride(), n = pix.getNumberOfComponents(), px = pix.getPixels(); + const words = [], unread = []; + for (const [k, b] of blobs.entries()) { + if (k >= maxBlobs) { unread.push(b); continue; } + const x0 = Math.max(0, Math.floor(b[0] - pad)), y0 = Math.max(0, Math.floor(b[1] - pad)), x1 = Math.min(w, Math.ceil(b[2] + pad)), y1 = Math.min(h, Math.ceil(b[3] + pad)); + // white frame around the crop helps Tesseract find the line + const F = 20, cw = x1 - x0 + 2 * F, ch = y1 - y0 + 2 * F; + const crop = new mupdf.Pixmap(mupdf.ColorSpace.DeviceRGB, [0, 0, cw, ch], false); + const cp = crop.getPixels(), cs = crop.getStride(); + cp.fill(255); + for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) for (let c = 0; c < 3; c++) cp[(y - y0 + F) * cs + (x - x0 + F) * 3 + c] = px[y * stride + x * n + c]; + let got = []; + try { got = (await ocr(crop.asPNG())).flat(); } catch { got = []; } + crop.destroy?.(); + const good = got.filter(t => (t.text.match(/[\p{L}\p{N}]/gu) ?? []).length >= 2); + if (!good.length) { unread.push(b); continue; } + for (const t of good) words.push({ text: t.text, bbox: [t.bbox[0] - F + x0, t.bbox[1] - F + y0, t.bbox[2] - F + x0, t.bbox[3] - F + y0], reocr: true }); + } + return { words, unread }; +} + +// Put re-read words into the page's lines by vertical overlap (so "Kiraya verenin T.C. Kimlik No" and the +// number written next to it end up on one text line), then order lines top to bottom, words left to right. +export function mergeLines(lines, words) { + if (!words.length) return lines; + const out = lines.map(l => [...l]); + const mid = b => (b[1] + b[3]) / 2; + for (const wd of words) { + let best = null, bestD = Infinity; + for (const l of out) { + const y0 = Math.min(...l.map(x => x.bbox[1])), y1 = Math.max(...l.map(x => x.bbox[3])); + const m = mid(wd.bbox); + if (m >= y0 && m <= y1) { const d = Math.abs(m - (y0 + y1) / 2); if (d < bestD) { bestD = d; best = l; } } + } + if (best) best.push(wd); else out.push([wd]); + } + for (const l of out) l.sort((a, b) => a.bbox[0] - b.bbox[0]); + out.sort((a, b) => Math.min(...a.map(x => x.bbox[1])) - Math.min(...b.map(x => x.bbox[1]))); + return out; +} + +// Blueness above which an unread blob is taken as pen (handwriting). On the one real photographed lease we +// have, handwritten values measured +7 to +9 and printed labels -3 to -11; to be checked on the synthetic +// photo set. +export const PEN_BLUE = 6; +// A row label that asks for personal data: "Kiraya verenin", "Kiracının", "T.C. Kimlik No", "Adı Soyadı" ... +const PII_LABEL = /kimlik|t\.?c\.?$|tckn|soyad|ad soyad|kiraya veren|kirac[ıi]|kefil|mal sahibi|telefon|^tel\b|gsm|iban|hesap no|vergi no|ikamet|adres|imza|vekil|davac[ıi]|daval[ıi]|borçlu|alacakl[ıi]/i; +// Labels whose handwritten value is always masked, read or not: ID numbers and names. Not addresses (the +// street stays readable; the text rules mask the building and door) and not party words alone. +const ID_NAME_LABEL = /k[iı]ml[iı]k|kımlık|tckn|t\.\s?c\.|\btc\b|soy\s?ad|ad[ıi]\s+soyad/i; +const median = a => { const b = [...a].sort((x, y) => x - y); return b.length ? b[b.length >> 1] : 0; }; +const rowWords = (p, [x0, y0, x1, y1]) => { + const m = (y1 - y0) * 0.5; + return p.words.filter(w => w.bbox[2] <= x0 + 2 && w.bbox[3] > y0 - m && w.bbox[1] < y1 + m); +}; + +// Handwriting items for the review list, one per ink blob, each carrying its own box (no text offsets): +// * pen ink in a row labelled T.C. Kimlik No or Adı soyadı: masked, whatever the OCR made of it +// (handwritten digits often come out as letters, so the text rules cannot be relied on there); +// * pen ink the OCR could not read at all, next to any personal-data label: masked; +// * other unreadable marks: listed for review, unmasked. +export function unreadSpans(doc) { + const out = []; + for (const p of doc.pages ?? []) { + const add = (b, i, kind, masked, why) => out.push({ + id: `u${p.number}_${kind}${i}`, uid: `UNREAD:${p.number}:${kind}${i}`, entity_type: "UNREAD", start: p.end, end: p.end, text: "", score: 0.5, + recognizer: kind === "p" ? "pen_in_id_row" : "unread_ink", explanation: why, flag_only: !masked, page: p.number, boxes: [[b[0], b[1], b[2], b[3]]] }); + const penBoxes = []; + (p.pen ?? []).forEach((b, i) => { + // strict row match: the blob's middle must sit inside the label line, so a tall blob + // that spills over from a neighbouring (street) row does not count + // (two-line label cells: the ink sits between "Kiraya verenin" and "T.C. Kimlik No.", so allow one line of slack) + const lh = p.lineH ??= median(p.words.map(w => w.bbox[3] - w.bbox[1])) || 10; + // errs towards masking: a TC number left readable is worse than a street hidden by mistake + // PaddleOCR boxes are about a third taller than Tesseract's, so a full line of slack reaches the + // street row below a TC row; there the blob's middle must sit within 0.6 line of the label instead + const cy = (b[1] + b[3]) / 2; + const words = p.engine === "paddle" ? p.words.filter(w => w.bbox[2] <= b[0] + 2 && cy > w.bbox[1] - 0.6 * lh && cy < w.bbox[3] + 0.6 * lh) + : p.words.filter(w => w.bbox[2] <= b[0] + 2 && w.bbox[3] > b[1] - lh && w.bbox[1] < b[3] + lh); + if (b[3] - b[1] <= 3.5 * lh && ID_NAME_LABEL.test(words.map(w => w.text).join(" "))) { penBoxes.push(b); add(b, i, "p", true, "handwriting in a row labelled T.C. Kimlik No / Adı soyadı"); } + }); + (p.unread ?? []).forEach((b, i) => { + if (penBoxes.some(q => q[0] < b[2] && q[2] > b[0] && q[1] < b[3] && q[3] > b[1])) return; + const pen = b.pen >= PEN_BLUE, label = PII_LABEL.test(rowWords(p, b).map(w => w.text).join(" ").toLocaleLowerCase("tr")); + add(b, i, "u", pen && label, pen ? (label ? "handwriting next to a personal-data label, not readable by OCR" : "handwriting the OCR could not read") : "marks the OCR could not read"); + }); + } + return out; +} + // ---- spans -> boxes (port of redact.attach_boxes; errs towards over-masking) export function attachBoxes(doc, spans) { for (const sp of spans) { + if (sp.entity_type === "UNREAD") continue; // boxes come from the page image, not from text const page = doc.pages.find(p => sp.start >= p.start && sp.start < p.end); if (!page) continue; sp.page = page.number; @@ -103,6 +298,17 @@ export function attachBoxes(doc, spans) { byLine.set(key, [Math.min(box[0], cx0), Math.min(box[1], y0), Math.max(box[2], cx1), Math.max(box[3], y1)]); } sp.boxes = [...byLine.values()]; + // handwriting: OCR often drops the first or last letter of a word, so grow the box over pen ink + // that touches it on the same line (not over tall blobs such as signatures) + if (page.ocr && page.pen?.length && sp.entity_type !== "ADDRESS") sp.boxes = sp.boxes.map(b => { + let [x0, y0, x1, y1] = b; const h = y1 - y0; + for (const q of page.pen) { + const vo = Math.min(y1, q[3]) - Math.max(y0, q[1]), gap = Math.max(x0, q[0]) - Math.min(x1, q[2]); + if (q[3] - q[1] <= 2 * h && vo > 0.5 * Math.min(h, q[3] - q[1]) && gap < 0.8 * h) + [x0, y0, x1, y1] = [Math.min(x0, q[0]), Math.min(y0, q[1]), Math.max(x1, q[2]), Math.max(y1, q[3])]; + } + return [x0, y0, x1, y1]; + }); } } @@ -161,6 +367,9 @@ export function redactPdf(mupdf, doc, spans, opts = {}) { const pdf = doc._doc, PAD = 1, color = opts.color ?? [0, 0, 0], wm = opts.watermark; const per = new Map(); for (const sp of spans) if (sp.page != null) per.set(sp.page, [...(per.get(sp.page) ?? []), ...sp.boxes]); + // OCR pages: close small gaps between boxes on one line, so letters between two masked words + // of a handwritten name do not stay readable + for (const [pno, boxes] of per) if (doc.pages?.[pno]?.ocr) per.set(pno, closeGaps(boxes)); for (const [pno, boxes] of per) { const page = pdf.loadPage(pno); for (const [x0, y0, x1, y1] of boxes) { @@ -171,6 +380,10 @@ export function redactPdf(mupdf, doc, spans, opts = {}) { page.applyRedactions(false, mupdf.PDFPage.REDACT_IMAGE_PIXELS); paintBoxes(mupdf, pdf, page, boxes.map(([x0, y0, x1, y1]) => [x0 - PAD, y0 - PAD, x1 + PAD, y1 + PAD]), color); } + // Photographed or scanned pages: replace the page by its cleaned image (sheet only, shadows flattened, + // show-through from the back of the sheet wiped). Ink from the other side can carry the names and + // numbers we just masked on that page, mirrored and faint but readable with a contrast tweak. + if (opts.cleanScans !== false) for (const p of doc.pages ?? []) if (p.ocr) cleanScanPage(mupdf, pdf, p.number, per.get(p.number) ?? [], color); if (opts.mark) for (let i = 0; i < pdf.countPages(); i++) stampMark(mupdf, pdf, pdf.loadPage(i), opts.mark); const links = []; if (wm?.text) for (let i = 0; i < pdf.countPages(); i++) { @@ -187,6 +400,37 @@ export function redactPdf(mupdf, doc, spans, opts = {}) { return pdf.saveToBuffer("garbage=4,compress,clean").asUint8Array().slice(); } +export function closeGaps(boxes) { + const out = boxes.map(b => [...b]).sort((a, b) => a[1] - b[1] || a[0] - b[0]); + for (let merged = true; merged;) { + merged = false; + for (let i = 0; i < out.length && !merged; i++) for (let j = i + 1; j < out.length && !merged; j++) { + const a = out[i], b = out[j], h = Math.min(a[3] - a[1], b[3] - b[1]); + const vo = Math.min(a[3], b[3]) - Math.max(a[1], b[1]), gap = Math.max(a[0], b[0]) - Math.min(a[2], b[2]); + if (vo > 0.5 * h && gap < 1.5 * h) { + out[i] = [Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.max(a[2], b[2]), Math.max(a[3], b[3])]; + out.splice(j, 1); merged = true; + } + } + } + return out; +} + +function cleanScanPage(mupdf, pdf, i, boxes, color) { + const page = pdf.loadPage(i); + if (page.getObject().get("Rotate")?.asNumber?.()) return; // rotated pages keep the plain redaction + const [x0, y0, x1, y1] = page.getBounds(), w = x1 - x0, h = y1 - y0, s = ocrScale(w, h); + const pix = page.toPixmap(mupdf.Matrix.scale(s, s), mupdf.ColorSpace.DeviceRGB, false); + if (!prepareScan(pix)) return; + const res = pdf.newDictionary(), xo = pdf.newDictionary(); + xo.put("Im0", pdf.addImage(new mupdf.Image(pix))); + res.put("XObject", xo); + const np = pdf.addPage([0, 0, w, h], 0, res, `q ${w.toFixed(3)} 0 0 ${h.toFixed(3)} 0 0 cm /Im0 Do Q`); + pdf.insertPage(i, np); pdf.deletePage(i + 1); + // the masks went grey in the clean-up; paint them again, full colour, on top + if (boxes.length) paintBoxes(mupdf, pdf, pdf.loadPage(i), boxes.map(([a, b, c, d]) => [a - x0 - 1, b - y0 - 1, c - x0 + 1, d - y0 + 1]), color); +} + export function pdfText(mupdf, bytes) { const d = mupdf.Document.openDocument(bytes, "application/pdf"); let t = ""; for (let i = 0; i < d.countPages(); i++) t += d.loadPage(i).toStructuredText().asText() + "\n"; @@ -231,7 +475,65 @@ export function redactImage(mupdf, bytes, spans, mime = "image/png", color = [0, } // render each page of a PDF for a second OCR pass (leak check on scans) -export function pdfPagePngs(mupdf, bytes, pages, dpi = OCR_DPI) { - const d = mupdf.Document.openDocument(bytes, "application/pdf"), s = dpi / 72; - return pages.map(i => d.loadPage(i).toPixmap(mupdf.Matrix.scale(s, s), mupdf.ColorSpace.DeviceRGB, false).asPNG().slice()); +export function pdfPagePngs(mupdf, bytes, pages) { + const d = mupdf.Document.openDocument(bytes, "application/pdf"); + return pages.map(i => { + const page = d.loadPage(i), [x0, y0, x1, y1] = page.getBounds(), s = ocrScale(x1 - x0, y1 - y0); + const pix = page.toPixmap(mupdf.Matrix.scale(s, s), mupdf.ColorSpace.DeviceRGB, false); + prepareScan(pix); + return pix.asPNG().slice(); + }); +} + +// ---- scans and phone photos: what the OCR sees +// Render at 300 DPI for normal page sizes, but never wider than an A4 page at 300 DPI (3508 px on the +// long side): a phone photo wrapped in a PDF is often a 1152x2048 pt page holding a 72 ppi image, and +// 300 DPI would upscale it 4x, which makes Tesseract read handwriting and the wood grain under the paper +// as text. +const MAX_OCR_SIDE = 3508; +export function ocrScale(width, height) { + return Math.min(OCR_DPI / 72, MAX_OCR_SIDE / Math.max(1, width, height)); +} + +// In place, on an RGB pixmap: (1) find the sheet of paper (bright, unsaturated) and whiten everything +// around it, so the floor or desk is not read as words; (2) divide by a blurred copy to flatten shadows +// and uneven light; (3) wipe faint grey (text showing through from the back of the sheet). Clean flatbed +// scans come out almost unchanged. Returns what it did, for the report. +export function prepareScan(pix) { + const w = pix.getWidth(), h = pix.getHeight(), stride = pix.getStride(), n = pix.getNumberOfComponents(); + if (n < 3 || w < 64 || h < 64) return null; + const px = pix.getPixels(), g = new Float32Array(w * h), paper = new Uint8Array(w * h), colour = new Uint8Array(w * h); + for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { + const o = y * stride + x * n, r = px[o], gg = px[o + 1], b = px[o + 2]; + const v = 0.299 * r + 0.587 * gg + 0.114 * b, sat = Math.max(r, gg, b) - Math.min(r, gg, b); + g[y * w + x] = v; + paper[y * w + x] = v > 165 && sat < 45 ? 1 : 0; + colour[y * w + x] = Math.max(0, Math.min(255, 128 + b - r)); // blueness: ballpoint ink is bluer than printed black + } + // paper box: rows and columns that are at least 30% paper + const rowHit = new Array(h).fill(0), colHit = new Array(w).fill(0); + for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) if (paper[y * w + x]) { rowHit[y]++; colHit[x]++; } + const rows = rowHit.map((c, y) => c > 0.3 * w ? y : -1).filter(y => y >= 0); + const cols = colHit.map((c, x) => c > 0.3 * h ? x : -1).filter(x => x >= 0); + let box = [0, 0, w, h]; + if (rows.length && cols.length) { + const b = [cols[0], rows[0], cols.at(-1) + 1, rows.at(-1) + 1]; + if ((b[2] - b[0]) * (b[3] - b[1]) >= 0.3 * w * h) box = b; // a small "paper" is a guess gone wrong + } + // background estimate: box blur (integral image), radius ~2% of the long side + const R = Math.max(8, Math.round(Math.max(w, h) / 50)), I = new Float64Array((w + 1) * (h + 1)); + for (let y = 0; y < h; y++) { let row = 0; for (let x = 0; x < w; x++) { row += g[y * w + x]; I[(y + 1) * (w + 1) + x + 1] = I[y * (w + 1) + x + 1] + row; } } + let flattened = 0; + for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { + let v = 255; + if (x >= box[0] && x < box[2] && y >= box[1] && y < box[3]) { + const ya = Math.max(0, y - R), yb = Math.min(h, y + R + 1), xa = Math.max(0, x - R), xb = Math.min(w, x + R + 1); + const bg = (I[yb * (w + 1) + xb] - I[ya * (w + 1) + xb] - I[yb * (w + 1) + xa] + I[ya * (w + 1) + xa]) / ((yb - ya) * (xb - xa)); + v = Math.min(255, (g[y * w + x] / Math.max(1, bg)) * 255); + if (v > 200) v = 255; else flattened++; + } + const o = y * stride + x * n; + px[o] = px[o + 1] = px[o + 2] = v; + } + return { paper: box, cropped: box[2] - box[0] < w || box[3] - box[1] < h, ink: flattened / (w * h), colour }; } diff --git a/hero/dilekce_maskeli.png b/hero/dilekce_maskeli.png new file mode 100644 index 0000000000000000000000000000000000000000..74b7e6fb736319c35e3bab7fc01888335121a5fe --- /dev/null +++ b/hero/dilekce_maskeli.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b086252c6eed5322b4afa9e3ccda47473362c92769174358fe7bcab327703e62 +size 101819 diff --git a/hero/dilekce_orijinal.png b/hero/dilekce_orijinal.png new file mode 100644 index 0000000000000000000000000000000000000000..37afbd2f0f4050607ce1f1c0e9b538117b56d000 --- /dev/null +++ b/hero/dilekce_orijinal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f2351d94407a89dd03d11b7963755cf3e1ce5e465d203234804562bb8599029 +size 129717 diff --git a/index.html b/index.html index 595a2b2791b69273c8bc62c8546f63ab62858397..0cace74856bd427582371027ebd818562530af22 100644 --- a/index.html +++ b/index.html @@ -7,155 +7,164 @@ - + -Katip Yerel: tarayıcıda KVKK maskeleme +Katip Mask: cihazınızda KVKK maskeleme
-
-

Katip Yerel

-

Türkçe KVKK maskeleme, tamamen tarayıcınızda. Belge hiçbir sunucuya yüklenmez.

-
-
- Belge sonrası ağ isteği: 0 - Dışarı gönderim: engelli - Kayıt: yok -
+

Katip Mask

+ Cihazınızda çalışır · belgeniz bilgisayarınızdan çıkmaz + +
-
- +
+
+
+

Belgedeki kişisel verileri saniyeler içinde maskeleyin.

+

Ad, T.C. kimlik, adres, telefon, IBAN, sağlık bilgisi… Katip bulur ve karartır; siz kontrol edip indirirsiniz.

+
+ + Belgeyi buraya bırakın veya seçin + PDF · fotoğraf · Word · UYAP .udf · metin +
+ +
+
+
+ +
+
+
+
+
Örnek dilekçe, orijinal
+
Aynı dilekçe, Katip ile maskelenmiş
+ OrijinalMaskeli + + Karartılıyor… + +
+
+
+
    +
  1. 1Belgeyi bırakın
  2. +
  3. 2Karartılanları kontrol edin
  4. +
  5. 3Maskeli halini indirin
  6. +
+

+
-
+
-

Hata bildir

-
Ne yanlış? - - - - +

Görüşünüz bizim için çok değerli

+
Konu + + + +
- - - - -
Rapora eklenecekler + + + + +
Rapora eklenecekler -
+

- +

diff --git a/katip_py.zip b/katip_py.zip index 4c944ac84de48540d37179f591f77737939742e8..0422b746a004e272c9c7edbcdd9f941744f18ddc 100644 --- a/katip_py.zip +++ b/katip_py.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea2f306c211abdfce7ae6dc87d251e7220c6d10a3a8f71a5803934ea01c4339f -size 178793 +oid sha256:2c3edc248a95f35889b7013e598828fb6acc76566f0b43900f7072172d75c7d5 +size 188607 diff --git a/models/registry.json b/models/registry.json index 0a069a3707fb635df09238a911f69d83b7bac176..87094a8a9a62613771607ec282bf224569c758a5 100644 --- a/models/registry.json +++ b/models/registry.json @@ -2,8 +2,8 @@ { "id": "rules+tagger", "name": { - "tr": "Katip Tiny", - "en": "Katip Tiny" + "tr": "Katip Mask Nano", + "en": "Katip Mask Nano" }, "badge": { "tr": "önerilen", @@ -58,8 +58,8 @@ { "id": "berturk", "name": { - "tr": "Katip BERTurk", - "en": "Katip BERTurk" + "tr": "Katip Mask Base", + "en": "Katip Mask Base" }, "badge": { "tr": "en doğru", @@ -70,8 +70,8 @@ "en": "Rules + 110 MB BERTurk · 100% on blind set, 90% on real text" }, "info": { - "tr": "En doğru model: kör testte 757 değerin hepsini, gerçek Türkçe metinde isimlerin %90'ını buldu. İlk açılışta 110 MB indirir ve Tiny'den yavaştır.||Katip Kural'a ek olarak BERTurk'ten ince ayarlanmış model (katip-tr-pii-berturk v2): isim, adres ve sağlık terimlerini birlikte bulur. Sentetik belgeler ve 17.967 gerçek Türkçe cümleyle eğitildi. Kör sette 757'de 757, ayrılmış sette 618'de 618; gerçek Yargıtay metninde isimlerin %99,3'ü, Vikipedi cümlelerinde %89,8'i (v1: %40,7). İsim içermeyen metinde 3 yanlış alarm (Tiny ile aynı).", - "en": "The most accurate model: found all 757 values on the blind test and 90% of names in real Turkish text. Downloads 110 MB the first time and is slower than Tiny.||Katip Rules plus a model fine-tuned from BERTurk (katip-tr-pii-berturk v2) that finds names, addresses and health terms together. Trained on synthetic documents and 17,967 real Turkish sentences. Blind set 757 of 757, held-out 618 of 618; 99.3% of names in real Yargıtay prose and 89.8% in Wikipedia sentences (v1: 40.7%). 3 false alarms on name-free text (same as Tiny)." + "tr": "En doğru model: kör testte 757 değerin hepsini, gerçek Türkçe metinde isimlerin %90'ını buldu. İlk açılışta 110 MB indirir ve Nano'dan yavaştır.||Katip Kural'a ek olarak BERTurk'ten ince ayarlanmış model (katip-tr-pii-berturk v2): isim, adres ve sağlık terimlerini birlikte bulur. Sentetik belgeler ve 17.967 gerçek Türkçe cümleyle eğitildi. Kör sette 757'de 757, ayrılmış sette 618'de 618; gerçek Yargıtay metninde isimlerin %99,3'ü, Vikipedi cümlelerinde %89,8'i (v1: %40,7). İsim içermeyen metinde 3 yanlış alarm (Nano ile aynı).", + "en": "The most accurate model: found all 757 values on the blind test and 90% of names in real Turkish text. Downloads 110 MB the first time and is slower than Nano.||Katip Rules plus a model fine-tuned from BERTurk (katip-tr-pii-berturk v2) that finds names, addresses and health terms together. Trained on synthetic documents and 17,967 real Turkish sentences. Blind set 757 of 757, held-out 618 of 618; 99.3% of names in real Yargıtay prose and 89.8% in Wikipedia sentences (v1: 40.7%). 3 false alarms on name-free text (same as Nano)." }, "ner": "katip-tr-pii-berturk", "dtype": "q8", diff --git a/paddle_ocr.js b/paddle_ocr.js new file mode 100644 index 0000000000000000000000000000000000000000..beb62d457a5cb471eefeed71906552869e8fb02d --- /dev/null +++ b/paddle_ocr.js @@ -0,0 +1,145 @@ +// PaddleOCR (PP-OCRv5, Apache-2.0) on onnxruntime: text-line detection (DB) + Latin recogniser (CTC). +// Used for phone photos and handwriting, where Tesseract misses handwritten digits and names. +// Same output as the Tesseract path: lines of words {text, bbox:[x0,y0,x1,y1], conf} in image pixels. +// +// const ocr = await createPaddleOcr(ort, { det: detBytesOrUrl, rec: recBytesOrUrl, dict: [...chars] }); +// const lines = await ocr.recognize({ width, height, rgb }); // rgb: Uint8Array, 3 bytes per pixel +// +// Runs fully offline: the caller hands in the model bytes (or this site's own URLs). + +const MEAN = [0.485, 0.456, 0.406], STD = [0.229, 0.224, 0.225]; + +export async function createPaddleOcr(ort, { det, rec, dict, detLimit = 1600, detThresh = 0.3, boxThresh = 0.5, + unclip = 1.6, recHeight = 48, minScore = 0.5, opts = {} } = {}) { + const sess = m => ort.InferenceSession.create(m, { executionProviders: ["wasm"], graphOptimizationLevel: "all", logSeverityLevel: 3, ...opts }); + const [detS, recS] = await Promise.all([sess(det), sess(rec)]); + const chars = ["", ...dict, " "]; // CTC blank first; PaddleOCR appends the space class last + + async function detect(img) { + const { width: W, height: H, rgb } = img; + const r = Math.min(1, detLimit / Math.max(W, H)); + const w = Math.max(32, Math.round(W * r / 32) * 32), h = Math.max(32, Math.round(H * r / 32) * 32); + const sx = W / w, sy = H / h, x = new Float32Array(3 * w * h); + for (let yy = 0; yy < h; yy++) { + const y0 = Math.min(H - 1, Math.floor((yy + 0.5) * sy)); + for (let xx = 0; xx < w; xx++) { + const s = (y0 * W + Math.min(W - 1, Math.floor((xx + 0.5) * sx))) * 3, o = yy * w + xx; + // PaddleOCR feeds BGR: channel 0 = blue + x[o] = (rgb[s + 2] / 255 - MEAN[0]) / STD[0]; + x[w * h + o] = (rgb[s + 1] / 255 - MEAN[1]) / STD[1]; + x[2 * w * h + o] = (rgb[s] / 255 - MEAN[2]) / STD[2]; + } + } + const out = await detS.run({ [detS.inputNames[0]]: new ort.Tensor("float32", x, [1, 3, h, w]) }); + const prob = out[detS.outputNames[0]].data; + // connected components of the thresholded map; one axis-aligned box per component + const lab = new Int32Array(w * h), boxes = [], stack = []; + for (let i = 0; i < w * h; i++) { + if (lab[i] || prob[i] < detThresh) continue; + let x0 = w, y0 = h, x1 = 0, y1 = 0, n = 0, sum = 0; + lab[i] = 1; stack.push(i); + while (stack.length) { + const j = stack.pop(), jx = j % w, jy = (j / w) | 0; + n++; sum += prob[j]; + if (jx < x0) x0 = jx; if (jx > x1) x1 = jx; if (jy < y0) y0 = jy; if (jy > y1) y1 = jy; + for (const k of [j - 1, j + 1, j - w, j + w]) { + if (k < 0 || k >= w * h || lab[k] || prob[k] < detThresh) continue; + if ((k === j - 1 && jx === 0) || (k === j + 1 && jx === w - 1)) continue; + lab[k] = 1; stack.push(k); + } + } + const bw = x1 - x0 + 1, bh = y1 - y0 + 1; + if (n < 6 || Math.min(bw, bh) < 3 || sum / n < boxThresh) continue; + // DB shrinks text regions when training; grow back by area * unclip / perimeter + const d = n * unclip / (2 * (bw + bh)); + boxes.push([Math.max(0, (x0 - d) * sx), Math.max(0, (y0 - d) * sy), Math.min(W, (x1 + 1 + d) * sx), Math.min(H, (y1 + 1 + d) * sy)]); + } + return boxes; + } + + // crop a box, resize to recHeight (bilinear), normalise to [-1, 1] in BGR + function crop(img, [bx0, by0, bx1, by1]) { + const { width: W, rgb } = img, cw = bx1 - bx0, ch = by1 - by0; + const h = recHeight, w = Math.max(8, Math.min(3200, Math.round(cw * h / ch))); + const x = new Float32Array(3 * w * h); + for (let yy = 0; yy < h; yy++) for (let xx = 0; xx < w; xx++) { + const fx = bx0 + (xx + 0.5) * cw / w - 0.5, fy = by0 + (yy + 0.5) * ch / h - 0.5; + const ix = Math.max(0, Math.floor(fx)), iy = Math.max(0, Math.floor(fy)), ax = Math.min(1, Math.max(0, fx - ix)), ay = Math.min(1, Math.max(0, fy - iy)); + const ix1 = Math.min(W - 1, ix + 1), iy1 = Math.min(img.height - 1, iy + 1); + for (let c = 0; c < 3; c++) { + const p = (yy0, xx0) => rgb[(yy0 * W + xx0) * 3 + c]; + const v = (p(iy, ix) * (1 - ax) + p(iy, ix1) * ax) * (1 - ay) + (p(iy1, ix) * (1 - ax) + p(iy1, ix1) * ax) * ay; + x[(2 - c) * w * h + yy * w + xx] = (v / 255 - 0.5) / 0.5; + } + } + return { x, w }; + } + + async function read(img, box) { + const { x, w } = crop(img, box); + const out = await recS.run({ [recS.inputNames[0]]: new ort.Tensor("float32", x, [1, 3, recHeight, w]) }); + const t = out[recS.outputNames[0]], [, T, C] = t.dims, p = t.data; + // greedy CTC; keep the time step of every character so words get their own boxes + const seq = []; + let prev = 0; + for (let i = 0; i < T; i++) { + let best = 0, bp = -1; + for (let c = 0; c < C; c++) if (p[i * C + c] > bp) { bp = p[i * C + c]; best = c; } + if (best !== 0 && best !== prev) seq.push({ ch: chars[best] ?? "", t: i, p: bp }); + prev = best; + } + const step = (box[2] - box[0]) / T, words = []; + let cur = null; + for (const s of seq) { + if (s.ch === " ") { cur = null; continue; } + if (!cur) words.push(cur = { text: "", t0: s.t, t1: s.t, ps: [] }); + cur.text += s.ch; cur.t1 = s.t; cur.ps.push(s.p); + } + return words.map(wd => ({ + text: fixCase(wd.text), + conf: wd.ps.reduce((a, b) => a + b, 0) / wd.ps.length, + bbox: [Math.max(box[0], box[0] + (wd.t0 - 0.5) * step), box[1], Math.min(box[2], box[0] + (wd.t1 + 1.5) * step), box[3]], + })); + } + + async function recognize(img) { + const boxes = await detect(img); + // reading order: top to bottom, then left to right within a row + boxes.sort((a, b) => (a[1] + a[3]) / 2 - (b[1] + b[3]) / 2); + const lines = []; + for (const b of boxes) { + if ((b[3] - b[1]) > 1.6 * (b[2] - b[0])) continue; // vertical text: not on these forms + const words = (await read(img, b)).filter(wd => wd.conf >= minScore && wd.text.trim()); + if (words.length) lines.push(words); + } + // join boxes on the same row (Paddle splits a row at wide gaps: label | value) + const rows = []; + for (const l of lines) { + const cy = (l[0].bbox[1] + l[0].bbox[3]) / 2, lh = l[0].bbox[3] - l[0].bbox[1]; + const row = rows.find(r => Math.abs(r.cy - cy) < 0.5 * Math.min(lh, r.lh)); + if (row) row.words.push(...l); else rows.push({ cy, lh, words: [...l] }); + } + return rows.map(r => r.words.sort((a, b) => a.bbox[0] - b.bbox[0])); + } + + return { recognize, detect, read }; +} + +// The Latin recogniser mixes cases inside Turkish capitals ("SözERi", "HACısALiHOĞLU"): a word that is +// mostly capitals is upper-cased the Turkish way (i -> İ, ı -> I), and its suffix after an apostrophe is kept. +export function fixCase(w) { + const [stem, ...rest] = w.split(/(?=['’])/); + const letters = [...stem].filter(c => /\p{L}/u.test(c)), up = letters.filter(c => c !== c.toLocaleLowerCase("tr")).length; + if (letters.length < 3 || up === letters.length || up * 2 < letters.length) return w; + return stem.toLocaleUpperCase("tr") + rest.join(""); +} + +// PNG/JPEG bytes -> {width, height, rgb} via MuPDF (already loaded for PDFs; works in the browser and in Node) +export function decodeImage(mupdf, bytes) { + const pix = new mupdf.Image(bytes).toPixmap(); + const rgbPix = pix.getColorSpace()?.getName?.() === "DeviceRGB" && !pix.getAlpha() ? pix : pix.convertToColorSpace(mupdf.ColorSpace.DeviceRGB, false); + const W = rgbPix.getWidth(), H = rgbPix.getHeight(), n = rgbPix.getNumberOfComponents() + (rgbPix.getAlpha() ? 1 : 0), stride = rgbPix.getStride(), px = rgbPix.getPixels(); + const rgb = new Uint8Array(W * H * 3); + for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) for (let c = 0; c < 3; c++) rgb[(y * W + x) * 3 + c] = px[y * stride + x * n + c]; + return { width: W, height: H, rgb }; +} diff --git a/samples/aydinlatma_01.pdf b/samples/aydinlatma_01.pdf index 8b093da0838fe9b1e460d83696ebc25734120fae..05964868e6a700527f0bd07c477d80d0df14e347 100644 --- a/samples/aydinlatma_01.pdf +++ b/samples/aydinlatma_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa558cf0c07fdfcdea8af24801bc278b916e577594d8074072a508e92eb2a754 -size 791691 +oid sha256:043388dae3e83c47eed56a26d5b23ebb226701364004427af4cd598484b167bf +size 43370 diff --git a/samples/aydinlatma_01.scan.pdf b/samples/aydinlatma_01.scan.pdf index c1c3f88e1b146b04b00111510874cdfc23f5e949..0b14c413e9bf180fbd76aa06e0a9c158bc1dff1f 100644 --- a/samples/aydinlatma_01.scan.pdf +++ b/samples/aydinlatma_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e7ddf1ca8607bfe2430d4b214f7aeb55bd5db10725af945899254d92dae9554 -size 217901 +oid sha256:de9e2ea8e6d712fd60c677d9bc566795cb6dd9879235e97731dd1dc0a1903c32 +size 217762 diff --git a/samples/aydinlatma_02.docx b/samples/aydinlatma_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..9cb6fb4a32f0cd8183fe09aa427644a91fec73fa Binary files /dev/null and b/samples/aydinlatma_02.docx differ diff --git a/samples/aydinlatma_02.pdf b/samples/aydinlatma_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6a0dead236fcf6a739d4f6669d2b546e33f5b583 Binary files /dev/null and b/samples/aydinlatma_02.pdf differ diff --git a/samples/aydinlatma_02.txt b/samples/aydinlatma_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..78adb192d3d8071a6e44b4afc87388b0ed1bbdee --- /dev/null +++ b/samples/aydinlatma_02.txt @@ -0,0 +1,14 @@ +KİŞİSEL VERİLERİN KORUNMASI KANUNU KAPSAMINDA AYDINLATMA METNİ VE AÇIK RIZA BEYANI + +Veri Sorumlusu: Boğaziçi Yazılım Teknolojileri A.Ş. +6698 sayılı Kişisel Verilerin Korunması Kanunu'nun 10. maddesi uyarınca, kimlik, iletişim, sağlık ve finans verileriniz hizmetin ifası amacıyla işlenmektedir. Kanun'un 11. maddesindeki haklarınızı kvkk@sirket.com.tr adresine başvurarak kullanabilirsiniz. + +İLGİLİ KİŞİ BEYANI +Adı Soyadı: Rüzgar Uzunöz +T.C. Kimlik No: 18338758586 +Doğum Tarihi: 26.11.1975 +Adres: Hürriyet Mah. Cumhuriyet Cad. No:31 Daire:17 Çankaya/ANKARA +Cep Telefonu: 0 (312) 434 39 51 +E-posta: ruzgar.uzunoz@outlook.com +Kan grubu ve epilepsi bilgimin işlenmesine açık rıza veriyorum. +Tarih: 04.10.2023 İmza: ________ diff --git a/samples/aydinlatma_02.udf b/samples/aydinlatma_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..b6cb2ca2ea5a3fa31cb226a8bc08a966d47f492c Binary files /dev/null and b/samples/aydinlatma_02.udf differ diff --git a/samples/aydinlatma_03.docx b/samples/aydinlatma_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..cc033ecaa323fa7d235119f5a25eb903332684c2 Binary files /dev/null and b/samples/aydinlatma_03.docx differ diff --git a/samples/aydinlatma_03.pdf b/samples/aydinlatma_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..741129fed70dacde3b8ba0080aeaf27d6ed79994 Binary files /dev/null and b/samples/aydinlatma_03.pdf differ diff --git a/samples/aydinlatma_03.png b/samples/aydinlatma_03.png new file mode 100644 index 0000000000000000000000000000000000000000..8963114666f62107509fea50725cb8bc1e05af8e --- /dev/null +++ b/samples/aydinlatma_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a8d1d0193c45108c3d830384c0d650073a87093283971bf24ecb17d1e78134d +size 161833 diff --git a/samples/aydinlatma_03.scan.pdf b/samples/aydinlatma_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..85913f23ee4236ac6a00b405a4ae06805c8dcc6e --- /dev/null +++ b/samples/aydinlatma_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5844902b5d587ee0d99c3dbc5f274b33fdf3bfbc7b51fa00df486551e47913e5 +size 218680 diff --git a/samples/aydinlatma_03.txt b/samples/aydinlatma_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..a1cd7b77e9c2e44ea86c9cb9795c233395bcde3b --- /dev/null +++ b/samples/aydinlatma_03.txt @@ -0,0 +1,14 @@ +KİŞİSEL VERİLERİN KORUNMASI KANUNU KAPSAMINDA AYDINLATMA METNİ VE AÇIK RIZA BEYANI + +Veri Sorumlusu: Marmara Lojistik A.Ş. +6698 sayılı Kişisel Verilerin Korunması Kanunu'nun 10. maddesi uyarınca, kimlik, iletişim, sağlık ve finans verileriniz hizmetin ifası amacıyla işlenmektedir. Kanun'un 11. maddesindeki haklarınızı kvkk@sirket.com.tr adresine başvurarak kullanabilirsiniz. + +İLGİLİ KİŞİ BEYANI +Adı Soyadı: EFLİN ATASEVER +T.C. Kimlik No: 99649828892 +Doğum Tarihi: 04.09.1972 +Adres: Fenerbahçe Mah. Tunalı Hilmi Cad. No:43 Daire:13 Muratpaşa/ANTALYA +Cep Telefonu: +90 232 254 77 48 +E-posta: eflin.atasever@gmail.com +Kan grubu ve epilepsi bilgimin işlenmesine açık rıza veriyorum. +Tarih: 16.01.2023 İmza: ________ diff --git a/samples/aydinlatma_03.udf b/samples/aydinlatma_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..8f10e66fdcaef575eea98739d1a309810becb5a1 Binary files /dev/null and b/samples/aydinlatma_03.udf differ diff --git a/samples/aydinlatma_04.docx b/samples/aydinlatma_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..b16a2d51e0c809378c97f268033ce1411b4d9096 Binary files /dev/null and b/samples/aydinlatma_04.docx differ diff --git a/samples/aydinlatma_04.pdf b/samples/aydinlatma_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..48c95792a277c674f2431db4010e3c36624bfdea Binary files /dev/null and b/samples/aydinlatma_04.pdf differ diff --git a/samples/aydinlatma_04.txt b/samples/aydinlatma_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..e0a9d20c175e847e770189607b2b0e8b9063c1f9 --- /dev/null +++ b/samples/aydinlatma_04.txt @@ -0,0 +1,14 @@ +KİŞİSEL VERİLERİN KORUNMASI KANUNU KAPSAMINDA AYDINLATMA METNİ VE AÇIK RIZA BEYANI + +Veri Sorumlusu: Anadolu Gıda Sanayi ve Ticaret A.Ş. +6698 sayılı Kişisel Verilerin Korunması Kanunu'nun 10. maddesi uyarınca, kimlik, iletişim, sağlık ve finans verileriniz hizmetin ifası amacıyla işlenmektedir. Kanun'un 11. maddesindeki haklarınızı kvkk@sirket.com.tr adresine başvurarak kullanabilirsiniz. + +İLGİLİ KİŞİ BEYANI +Adı Soyadı: Sadiye SEZGİN +T.C. Kimlik No: 54482322594 +Doğum Tarihi: 02.07.1969 +Adres: Alsancak Mh. Halaskargazi Cd. Yasemin Apt. No:126 D:4 58921 Seyhan/Adana +Cep Telefonu: 0542 610 30 26 +E-posta: sadiye.sezgin@yandex.com.tr +Kan grubu ve hamilelik bilgimin işlenmesine açık rıza veriyorum. +Tarih: 25.09.2024 İmza: ________ diff --git a/samples/aydinlatma_04.udf b/samples/aydinlatma_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..de193f63a130fc437a34120ea3560e2d3a36fb96 Binary files /dev/null and b/samples/aydinlatma_04.udf differ diff --git a/samples/aydinlatma_05.docx b/samples/aydinlatma_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..a9117f926ce75ba73a4e275993f085c8dd884950 Binary files /dev/null and b/samples/aydinlatma_05.docx differ diff --git a/samples/aydinlatma_05.pdf b/samples/aydinlatma_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b0a278d1dccef98d7e8381d201b241402e4c31fc Binary files /dev/null and b/samples/aydinlatma_05.pdf differ diff --git a/samples/aydinlatma_05.png b/samples/aydinlatma_05.png new file mode 100644 index 0000000000000000000000000000000000000000..4e34d005ae5fdb932f76bec31c169cf02b10d22f --- /dev/null +++ b/samples/aydinlatma_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dafd56fdb0a104d2f0f3546826e5d434e54d927c5cbd47f68fbbe844a2f4b3a +size 157304 diff --git a/samples/aydinlatma_05.scan.pdf b/samples/aydinlatma_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d3b85e3ab17663a6ae93cebe719aa597d606690 --- /dev/null +++ b/samples/aydinlatma_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb6490d4dacf81cd9c609733ed6b66955b4069fcc1799b757eaac82fcedcd1da +size 216042 diff --git a/samples/aydinlatma_05.txt b/samples/aydinlatma_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..5aa3884dfc4620858df26d0a582ce2339ba4c787 --- /dev/null +++ b/samples/aydinlatma_05.txt @@ -0,0 +1,14 @@ +KİŞİSEL VERİLERİN KORUNMASI KANUNU KAPSAMINDA AYDINLATMA METNİ VE AÇIK RIZA BEYANI + +Veri Sorumlusu: Kuzey Enerji Ltd. Şti. +6698 sayılı Kişisel Verilerin Korunması Kanunu'nun 10. maddesi uyarınca, kimlik, iletişim, sağlık ve finans verileriniz hizmetin ifası amacıyla işlenmektedir. Kanun'un 11. maddesindeki haklarınızı kvkk@sirket.com.tr adresine başvurarak kullanabilirsiniz. + +İLGİLİ KİŞİ BEYANI +Adı Soyadı: Akay Atasever +T.C. Kimlik No: 81351468770 +Doğum Tarihi: 01.10.1983 +Adres: Kızılay Mahallesi, Cinnah Caddesi No: 116 Kat: 1 Nilüfer/BURSA +Cep Telefonu: +90 (216) 229 9917 +E-posta: akay.atasever@gmail.com +Kan grubu ve diyabet bilgimin işlenmesine açık rıza veriyorum. +Tarih: 20.04.2021 İmza: ________ diff --git a/samples/aydinlatma_05.udf b/samples/aydinlatma_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..c41cecd1fa6e938e7a52d051f5cbd58461c0640b Binary files /dev/null and b/samples/aydinlatma_05.udf differ diff --git a/samples/bilirkisi_01.pdf b/samples/bilirkisi_01.pdf index e7fad2a512e274db53b03b94a0ab0f7668f3b86f..e5e922016231594c1b138298aaac1bf20d38bafd 100644 --- a/samples/bilirkisi_01.pdf +++ b/samples/bilirkisi_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cbe3d61c66bd38995101b32bdc8ee220a68c46802ee37e0eed15b0769bfecc5 -size 789953 +oid sha256:e285901891f11fbf3e802c5bc371840ae2b3221aa650d763e3cc9af05b42af8d +size 40197 diff --git a/samples/bilirkisi_01.scan.pdf b/samples/bilirkisi_01.scan.pdf index b6fdf5f23f07eecaeb8ee3c3b5a5acd2fbe7cdbc..549adfa243ce0b43a26ab73ed7d63de057b80124 100644 --- a/samples/bilirkisi_01.scan.pdf +++ b/samples/bilirkisi_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6689cea1f421a3490c773977529141e059644ad702de6ed1382aea33ebb05b2 -size 193851 +oid sha256:6488afda439c6ab06bb663d938a7b2a09a96c0ea41d7afba2ce13c5b97720caf +size 193712 diff --git a/samples/bilirkisi_02.docx b/samples/bilirkisi_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..883f3b3a169c2b0c0ece866240e7daa6418deacf Binary files /dev/null and b/samples/bilirkisi_02.docx differ diff --git a/samples/bilirkisi_02.pdf b/samples/bilirkisi_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ca06572907facc329df804731b98ec3db0bbdcd4 Binary files /dev/null and b/samples/bilirkisi_02.pdf differ diff --git a/samples/bilirkisi_02.txt b/samples/bilirkisi_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..9c6be6728b1d8eb396f365d3bf3ad5c723159120 --- /dev/null +++ b/samples/bilirkisi_02.txt @@ -0,0 +1,3 @@ +BİLİRKİŞİ RAPORU + +Dosya kapsamındaki belgeler incelendiğinde, DURACAN TARHAN'ın 15.12.1975 tarihinde doğduğu, olay tarihinde Ilgın ATASEVER adına kayıtlı 33 LY 4593 plakalı araçla seyir halindeyken kazaya karıştığı anlaşılmıştır. Kazadan sonra Duracan yoğun bakım ünitesinde tedavi görmüştür. Sigorta şirketi ödemeyi TR59 0006 2088 5152 8974 8868 64 hesabına yapmıştır. Ilgın ATASEVER ise ifadesinde aracı ödünç verdiğini beyan etmiştir. diff --git a/samples/bilirkisi_02.udf b/samples/bilirkisi_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..bcb9e5a32817af5ea152f23ef25a1c184f3758a4 Binary files /dev/null and b/samples/bilirkisi_02.udf differ diff --git a/samples/bilirkisi_03.docx b/samples/bilirkisi_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..46a5fa7032892bd7bab7a9bbbbd8056fa7a6dec1 Binary files /dev/null and b/samples/bilirkisi_03.docx differ diff --git a/samples/bilirkisi_03.pdf b/samples/bilirkisi_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..61ccfaee1624b5a7d786d0100cd3fb0f20339644 Binary files /dev/null and b/samples/bilirkisi_03.pdf differ diff --git a/samples/bilirkisi_03.png b/samples/bilirkisi_03.png new file mode 100644 index 0000000000000000000000000000000000000000..34e92a3a8c1290554c281e92653daa479937d0b9 --- /dev/null +++ b/samples/bilirkisi_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4101487625cf2e0e4bece737dcb5810f74a465758ed2d1c0ff1af583cdab950 +size 129906 diff --git a/samples/bilirkisi_03.scan.pdf b/samples/bilirkisi_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..40c088822f482633de86df57e62c31ffd3e9c6a6 --- /dev/null +++ b/samples/bilirkisi_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1a544e74ee7e8f9800a397c984fecb0ca1023c4726a881c35734a2b3093adba +size 195313 diff --git a/samples/bilirkisi_03.txt b/samples/bilirkisi_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..41ccbc8ce7c2fef756c70e435d28bba06cb082ff --- /dev/null +++ b/samples/bilirkisi_03.txt @@ -0,0 +1,3 @@ +BİLİRKİŞİ RAPORU + +Dosya kapsamındaki belgeler incelendiğinde, Ginyas Korutürk'ın 24.08.1975 tarihinde doğduğu, olay tarihinde SEMAT KESKİNKILIÇ adına kayıtlı 80 FJT 795 plakalı araçla seyir halindeyken kazaya karıştığı anlaşılmıştır. Kazadan sonra Ginyas yoğun bakım ünitesinde tedavi görmüştür. Sigorta şirketi ödemeyi TR19 0006 7024 6096 6331 3208 93 hesabına yapmıştır. SEMAT KESKİNKILIÇ ise ifadesinde aracı ödünç verdiğini beyan etmiştir. diff --git a/samples/bilirkisi_03.udf b/samples/bilirkisi_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..66918672a007e5f5d5df394c1df72bc5ada40131 Binary files /dev/null and b/samples/bilirkisi_03.udf differ diff --git a/samples/bilirkisi_04.docx b/samples/bilirkisi_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..15166312ccd8533b96151b591c589b732c43f463 Binary files /dev/null and b/samples/bilirkisi_04.docx differ diff --git a/samples/bilirkisi_04.pdf b/samples/bilirkisi_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cbc7ea2e5e7666581f1125fbabf6214d91a0496c Binary files /dev/null and b/samples/bilirkisi_04.pdf differ diff --git a/samples/bilirkisi_04.txt b/samples/bilirkisi_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..5110ea9c8213ec4edc06ea955a7fe7a0ba5b6fbd --- /dev/null +++ b/samples/bilirkisi_04.txt @@ -0,0 +1,3 @@ +BİLİRKİŞİ RAPORU + +Dosya kapsamındaki belgeler incelendiğinde, Elvin Uzunöz'ın 10.11.1974 tarihinde doğduğu, olay tarihinde ADA KESKİNKILIÇ adına kayıtlı 37 IKK 934 plakalı araçla seyir halindeyken kazaya karıştığı anlaşılmıştır. Kazadan sonra Elvin yoğun bakım ünitesinde tedavi görmüştür. Sigorta şirketi ödemeyi TR72 0001 0046 8453 0373 7758 46 hesabına yapmıştır. ADA KESKİNKILIÇ ise ifadesinde aracı ödünç verdiğini beyan etmiştir. diff --git a/samples/bilirkisi_04.udf b/samples/bilirkisi_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..f7b0d7d0d8b054ef646529bc281c88a4842af97d Binary files /dev/null and b/samples/bilirkisi_04.udf differ diff --git a/samples/bilirkisi_05.docx b/samples/bilirkisi_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..26c029edcbf7cc855e40500f220ba49535f9f18f Binary files /dev/null and b/samples/bilirkisi_05.docx differ diff --git a/samples/bilirkisi_05.pdf b/samples/bilirkisi_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..47347c0e8b5eb0aaaf47c127144ad814cbb2d80e Binary files /dev/null and b/samples/bilirkisi_05.pdf differ diff --git a/samples/bilirkisi_05.png b/samples/bilirkisi_05.png new file mode 100644 index 0000000000000000000000000000000000000000..991218fd05c0af1a97551e950f4a0ea191527af6 --- /dev/null +++ b/samples/bilirkisi_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfe7703e7363805919ac6ff81d82e60635d8f58fdee4956d5eaece0c87feb3f3 +size 126308 diff --git a/samples/bilirkisi_05.scan.pdf b/samples/bilirkisi_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ee6633cbaa63b4368fc9e6486f1ea704197f2674 --- /dev/null +++ b/samples/bilirkisi_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41b1e4765c19a7a8965e2cac371b9bfbb4fee9bc9ed5861571a6b31fac37e04e +size 193630 diff --git a/samples/bilirkisi_05.txt b/samples/bilirkisi_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..ea984a95938f0dc7a9c884abd1c5b2c434f772cc --- /dev/null +++ b/samples/bilirkisi_05.txt @@ -0,0 +1,3 @@ +BİLİRKİŞİ RAPORU + +Dosya kapsamındaki belgeler incelendiğinde, Elvin Turgutlu'ın 24.03.1957 tarihinde doğduğu, olay tarihinde SEVİM ÇAKMAKÇI adına kayıtlı 19 PL 7063 plakalı araçla seyir halindeyken kazaya karıştığı anlaşılmıştır. Kazadan sonra Elvin yoğun bakım ünitesinde tedavi görmüştür. Sigorta şirketi ödemeyi TR14 0001 2095 8565 2817 5134 94 hesabına yapmıştır. SEVİM ÇAKMAKÇI ise ifadesinde aracı ödünç verdiğini beyan etmiştir. diff --git a/samples/bilirkisi_05.udf b/samples/bilirkisi_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..fd276a7f587b91496b1d2d92c43139f8074e57f2 Binary files /dev/null and b/samples/bilirkisi_05.udf differ diff --git a/samples/dilekce_01.pdf b/samples/dilekce_01.pdf index 6f13ec5fc35ef2e8c4d6ebf0ee3cf0c2c0a4b562..fee0bce6f26657cb36ab42b07dd99b425a6c721f 100644 --- a/samples/dilekce_01.pdf +++ b/samples/dilekce_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc3c3f3e232f52cb4cdee287ac53aa1dc0961e61b162d976acbd581287847354 -size 793536 +oid sha256:59105924d2cd3e1aca2f58dc64c4ddb1ab70906331bd92431719c5b632005bab +size 44978 diff --git a/samples/dilekce_01.scan.pdf b/samples/dilekce_01.scan.pdf index 596bd6effac4db2e9fc18c16783a113540b53106..6af7dbe864102e5cf518f4b40820a43a6d9caacf 100644 --- a/samples/dilekce_01.scan.pdf +++ b/samples/dilekce_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86b0bc15b0a26abd6374f4ac4a6f18dd17aa0b7e1065c26bfcbc51d4ee6813b5 -size 261273 +oid sha256:c7463c325da8207604ba2f1ed7572e84d71b7d47ea0ddd823d3dcfefb90ca02b +size 261134 diff --git a/samples/dilekce_02.docx b/samples/dilekce_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..ccce1a8c9839c48c20c99052bfdea0432c9a4813 Binary files /dev/null and b/samples/dilekce_02.docx differ diff --git a/samples/dilekce_02.pdf b/samples/dilekce_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7a224a4b9b8e10f8651e71fe849114136693e191 Binary files /dev/null and b/samples/dilekce_02.pdf differ diff --git a/samples/dilekce_02.txt b/samples/dilekce_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..44485e9ea4cf15efec47de6ddce43cfbebe526a1 --- /dev/null +++ b/samples/dilekce_02.txt @@ -0,0 +1,21 @@ +ANKARA 12. ASLİYE HUKUK MAHKEMESİ'NE + +DAVACI : Ada Uzunöz (T.C. Kimlik No: 80682545648) + Güzelyalı Mahallesi 2. Ilgın Sokak No: 5/8 Beşiktaş / İstanbul +VEKİLİ : Av. Orçin Kısakürek - İstanbul Barosu Sicil No: 34507 + Tel: 02329896887 E-posta: orcin.kisakurek@gmail.com +DAVALI : Lavin Bayraktaroğlu (TCKN: 34809357492) +Adres : Bahçelievler Mah. İnönü Cad. No:56 Daire:30 Beşiktaş/İstanbul +KONU : Boşanma ve 700.703,00 TL maddi tazminat talebimizdir. + +AÇIKLAMALAR : +1- Müvekkil Ada Uzunöz ile davalı Lavin Bayraktaroğlu 27.11.2022 tarihinde evlenmiştir. Davalı 27.09.1979 doğumlu olup tarafların müşterek çocuğu GÖKTÜRK CÖMERTOĞLU bulunmaktadır. +2- Müvekkil evlilik süresince epilepsi hastalığı görmüş, davalı bu süreçte destek olmamıştır. +3- Taraflar arasındaki 2020/822 Esas sayılı dosya ve UYAP 36922304195 numaralı evrak incelendiğinde davalının 51 AA 8252 plakalı aracı müvekkile devretmediği görülmüştür. +4- Nafaka ödemelerinin TR17 0006 7060 1108 1700 8589 96 IBAN numaralı hesaba yapılmasını talep ederiz. + +HUKUKİ NEDENLER : TMK m.166, 4721 sayılı Kanun ve ilgili mevzuat. +SONUÇ VE İSTEM : Yukarıda açıklanan nedenlerle davanın kabulüne karar verilmesini saygılarımla arz ve talep ederim. 28.03.2021 + +Davacı Vekili +Av. Orçin Kısakürek diff --git a/samples/dilekce_02.udf b/samples/dilekce_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..9aa365cae6b1ef3418a184ff2835c6179d75117e Binary files /dev/null and b/samples/dilekce_02.udf differ diff --git a/samples/dilekce_03.docx b/samples/dilekce_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..f30e41abe9d4a35400b117192b40ee275e1d4a0e Binary files /dev/null and b/samples/dilekce_03.docx differ diff --git a/samples/dilekce_03.pdf b/samples/dilekce_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a74653688a16090d2f702c6999c498dcc9bc359d Binary files /dev/null and b/samples/dilekce_03.pdf differ diff --git a/samples/dilekce_03.png b/samples/dilekce_03.png new file mode 100644 index 0000000000000000000000000000000000000000..4eee8c3fc1aedb6674a0341543bfd261049966a2 --- /dev/null +++ b/samples/dilekce_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03e244ea76deeb69b3b973ca0f9f99d357a27ecb907e23ec1a4c343b8bfff946 +size 227193 diff --git a/samples/dilekce_03.scan.pdf b/samples/dilekce_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..634ff1c2248b508baebe058a4d32746155bf950a --- /dev/null +++ b/samples/dilekce_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:048b514e0e37b22666bb128cd8e4f9d2acc7eba0e194ed52bca3c3a51c3bfb84 +size 260009 diff --git a/samples/dilekce_03.txt b/samples/dilekce_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..ef60d48470c7031ea0729e1e8870b1665e423c21 --- /dev/null +++ b/samples/dilekce_03.txt @@ -0,0 +1,20 @@ +İZMİR 3. İŞ MAHKEMESİ'NE + +DAVACI : RÜZGAR ATASEVER (T.C. Kimlik No: 42158763376) + Koşuyolu Mah. Tunalı Hilmi Cad. No:98 Daire:33 Şahinbey/GAZİANTEP +VEKİLİ : Av. Günden Akçay - İstanbul Barosu Sicil No: 34507 + Tel: +90 (532) 888 1170 E-posta: gunden.akcay@hotmail.com +DAVALI : Elvin Güleryüz (TCKN: 49943381494) +Adres : Hürriyet Mh. Halaskargazi Cd. Papatya Apt. No:57 D:31 24277 Şahinbey/GAZİANTEP +KONU : Boşanma ve 375.470,00 TL maddi tazminat talebimizdir. + +AÇIKLAMALAR : +1- Müvekkil RÜZGAR ATASEVER ile davalı Elvin Güleryüz 11.03.2022 tarihinde evlenmiştir. Davalı 23.04.1976 doğumlu olup tarafların müşterek çocuğu TAYYİBE EKŞİOĞLU bulunmaktadır. +3- Taraflar arasındaki 2022/75 Esas sayılı dosya ve UYAP 89571660454 numaralı evrak incelendiğinde davalının 22 PVK 148 plakalı aracı müvekkile devretmediği görülmüştür. +4- Nafaka ödemelerinin TR78 0001 0057 8068 1392 1854 77 IBAN numaralı hesaba yapılmasını talep ederiz. + +HUKUKİ NEDENLER : TMK m.166, 4721 sayılı Kanun ve ilgili mevzuat. +SONUÇ VE İSTEM : Yukarıda açıklanan nedenlerle davanın kabulüne karar verilmesini saygılarımla arz ve talep ederim. 15.02.2025 + +Davacı Vekili +Av. Günden Akçay diff --git a/samples/dilekce_03.udf b/samples/dilekce_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..91dd8ca0563bbfdac5cf7c46168edf3871aa17f3 Binary files /dev/null and b/samples/dilekce_03.udf differ diff --git a/samples/dilekce_04.docx b/samples/dilekce_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..f249b1955b35930a19c889ffc00b9386d9b09e5f Binary files /dev/null and b/samples/dilekce_04.docx differ diff --git a/samples/dilekce_04.pdf b/samples/dilekce_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..88d503592146faa1566ae98ca5d32314b6cc7991 Binary files /dev/null and b/samples/dilekce_04.pdf differ diff --git a/samples/dilekce_04.txt b/samples/dilekce_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..152246b84e6d3ef77474bc21f4b9a339759d6feb --- /dev/null +++ b/samples/dilekce_04.txt @@ -0,0 +1,20 @@ +ANKARA 12. ASLİYE HUKUK MAHKEMESİ'NE + +DAVACI : Ada DAĞDEVİREN (T.C. Kimlik No: 47909437902) + Sahrayıcedit Mahallesi, Talatpaşa Caddesi No: 178 Kat: 8 Çankaya/ANKARA +VEKİLİ : Av. Ada YEŞİLBAĞ - İstanbul Barosu Sicil No: 34507 + Tel: 0505 262 82 52 E-posta: ada.yesilbag@yandex.com.tr +DAVALI : Üzer Soydaş (TCKN: 78907226786) +Adres : Alsancak Mh. İnönü Cd. 1453 Apt. No:47 D:5 19586 Seyhan/Adana +KONU : Boşanma ve 739.026,00 TL maddi tazminat talebimizdir. + +AÇIKLAMALAR : +1- Müvekkil Ada DAĞDEVİREN ile davalı Üzer Soydaş 10.09.2020 tarihinde evlenmiştir. Davalı 15.11.1952 doğumlu olup tarafların müşterek çocuğu Elvin Turgutlu bulunmaktadır. +3- Taraflar arasındaki 2019/24 Esas sayılı dosya ve UYAP 84057064968 numaralı evrak incelendiğinde davalının 50 JP 2830 plakalı aracı müvekkile devretmediği görülmüştür. +4- Nafaka ödemelerinin TR65 0001 5004 3442 1018 9510 36 IBAN numaralı hesaba yapılmasını talep ederiz. + +HUKUKİ NEDENLER : TMK m.166, 4721 sayılı Kanun ve ilgili mevzuat. +SONUÇ VE İSTEM : Yukarıda açıklanan nedenlerle davanın kabulüne karar verilmesini saygılarımla arz ve talep ederim. 15.01.2019 + +Davacı Vekili +Av. Ada YEŞİLBAĞ diff --git a/samples/dilekce_04.udf b/samples/dilekce_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..5831a864d455f0a210c9c8426920ea49a8b9f909 Binary files /dev/null and b/samples/dilekce_04.udf differ diff --git a/samples/dilekce_05.docx b/samples/dilekce_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..c9e25a242d98908e72a166c7a04a966d3e97ed2c Binary files /dev/null and b/samples/dilekce_05.docx differ diff --git a/samples/dilekce_05.pdf b/samples/dilekce_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8c45482a846ed94030e325a1aa998d8ec54acd87 Binary files /dev/null and b/samples/dilekce_05.pdf differ diff --git a/samples/dilekce_05.png b/samples/dilekce_05.png new file mode 100644 index 0000000000000000000000000000000000000000..d8a9e639f25d1d08559bf16885d6345cf0698175 --- /dev/null +++ b/samples/dilekce_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20fc55ed5bb4ad9c323246696ce9f5a2dd7605bd35a7b34117a49df2143a4073 +size 239199 diff --git a/samples/dilekce_05.scan.pdf b/samples/dilekce_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ec6f1d751fb8caec3c5d3b4c7c5394dc4eaa501a --- /dev/null +++ b/samples/dilekce_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d28e7f47520a440ec3e560f67dad167ecfe4370dcf50f12ba22dc6eb6c871dae +size 262133 diff --git a/samples/dilekce_05.txt b/samples/dilekce_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..17debe8605c18bd415b115ef5d0ddfc12a60ec9e --- /dev/null +++ b/samples/dilekce_05.txt @@ -0,0 +1,20 @@ +İSTANBUL 18. İCRA DAİRESİ'NE + +DAVACI : Alin Yeşilbağ (T.C. Kimlik No: 35498781858) + Esentepe Mh. Ziya Gökalp Cd. Begonya Apt. No:44 D:32 51848 Seyhan/Adana +VEKİLİ : Av. Ulakbey Yeşilbağ - İstanbul Barosu Sicil No: 34507 + Tel: 0 (232) 333 98 68 E-posta: ulakbey.yesilbag@hotmail.com +DAVALI : Borahan KALKAVAN (TCKN: 64365160472) +Adres : Kavaklıdere Mh. Cinnah Cd. Akasya Apt. No:165 D:38 34251 Seyhan/Adana +KONU : Boşanma ve 626.632,00 TL maddi tazminat talebimizdir. + +AÇIKLAMALAR : +1- Müvekkil Alin Yeşilbağ ile davalı Borahan KALKAVAN 05.01.2021 tarihinde evlenmiştir. Davalı 06.06.1996 doğumlu olup tarafların müşterek çocuğu AKBAŞ ZORLU bulunmaktadır. +3- Taraflar arasındaki 2025/582 Esas sayılı dosya ve UYAP 77498514062 numaralı evrak incelendiğinde davalının 26 O 76223 plakalı aracı müvekkile devretmediği görülmüştür. +4- Nafaka ödemelerinin TR45 0004 6037 5950 1018 1921 67 IBAN numaralı hesaba yapılmasını talep ederiz. + +HUKUKİ NEDENLER : TMK m.166, 4721 sayılı Kanun ve ilgili mevzuat. +SONUÇ VE İSTEM : Yukarıda açıklanan nedenlerle davanın kabulüne karar verilmesini saygılarımla arz ve talep ederim. 05.10.2023 + +Davacı Vekili +Av. Ulakbey Yeşilbağ diff --git a/samples/dilekce_05.udf b/samples/dilekce_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..efc5631a09abc8f069927e1f113191283413221d Binary files /dev/null and b/samples/dilekce_05.udf differ diff --git a/samples/epikriz_01.pdf b/samples/epikriz_01.pdf index c808b61e5b26348999bd65a620f7bdd47b1cd0aa..e968793bc28bbdd956a174c7f20c5f679a3d6bb5 100644 --- a/samples/epikriz_01.pdf +++ b/samples/epikriz_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6aedf9430339b02813ac9d3cbf4cf91ce036b69376806851aa5e692c1acdebf3 -size 789985 +oid sha256:ce4fe47e4dfd528adb9ade88ab616295badbb777f4157b7947d2d2fea2954200 +size 40093 diff --git a/samples/epikriz_01.scan.pdf b/samples/epikriz_01.scan.pdf index 6f1c0327f8dddea90c18dd9817177dbde828e9bd..98212d773205d4cf94afc3a303d48bb80a5f36e6 100644 --- a/samples/epikriz_01.scan.pdf +++ b/samples/epikriz_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c32ddc9e456e6ae923451c52a86df340c4433c7689249d771260bcc0556ed150 -size 183639 +oid sha256:7bbd52e1758bd7f18cfe20140f748c328b3e417bf35872fb76d3686118914b89 +size 183500 diff --git a/samples/epikriz_02.docx b/samples/epikriz_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..9f9c201ed91c5d53115d4808d4ccb80003a8a4ad Binary files /dev/null and b/samples/epikriz_02.docx differ diff --git a/samples/epikriz_02.pdf b/samples/epikriz_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9a3480c214349555851fbfaf7a24ed3975d3cdf8 Binary files /dev/null and b/samples/epikriz_02.pdf differ diff --git a/samples/epikriz_02.txt b/samples/epikriz_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..9aceb402f9d3bff256f149ce904f45206b1b0497 --- /dev/null +++ b/samples/epikriz_02.txt @@ -0,0 +1,9 @@ +EPİKRİZ RAPORU + +Hasta: Lavin Karadereli Protokol: 842189 +Doğum Tarihi: 21.03.1973 Kimlik: 96375833374 +Refakatçi: Ilgın Dağdeviren (eşi) - +90 533 386 72 13 + +Hasta depresyon tedavisi nedeniyle yatırılmış, yatışı süresince takip edilmiştir. Taburcu edilerek poliklinik kontrolüne çağrılmıştır. + +Sorumlu Hekim: Dr. Alin Bayraktaroğlu diff --git a/samples/epikriz_02.udf b/samples/epikriz_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..22c8854a4750257efd2ca5e3f3b26aafc401f275 Binary files /dev/null and b/samples/epikriz_02.udf differ diff --git a/samples/epikriz_03.docx b/samples/epikriz_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..4e7e1b25f3b57f0a4ca804c6a4eedf1f1728cf40 Binary files /dev/null and b/samples/epikriz_03.docx differ diff --git a/samples/epikriz_03.pdf b/samples/epikriz_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e9a6e09f3e42c67e70a3708c8450f94f7d3544be Binary files /dev/null and b/samples/epikriz_03.pdf differ diff --git a/samples/epikriz_03.png b/samples/epikriz_03.png new file mode 100644 index 0000000000000000000000000000000000000000..b20ff40247173f281ccffd58cd4dc0556728f1ef --- /dev/null +++ b/samples/epikriz_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:699832cfde613e270c558cb7eca31db883e7fdab2d9ff416a65a810958fc1e2b +size 114901 diff --git a/samples/epikriz_03.scan.pdf b/samples/epikriz_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b3c496724d98ada6ea61481c91c0a32c4cc75468 --- /dev/null +++ b/samples/epikriz_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf33591b1cc01597eda48036b228edcca1ef2ae4b3b56dfd83f2f9ea5b23339c +size 183022 diff --git a/samples/epikriz_03.txt b/samples/epikriz_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..df720315da537a4150346387da4f85f279daf882 --- /dev/null +++ b/samples/epikriz_03.txt @@ -0,0 +1,9 @@ +EPİKRİZ RAPORU + +Hasta: Döner Ağaoğlu Protokol: 791348 +Doğum Tarihi: 20.12.1962 Kimlik: 20724879324 +Refakatçi: ALİN KILIÇARSLAN (eşi) - (212) 713-77-26 + +Hasta depresyon tedavisi nedeniyle yatırılmış, yatışı süresince takip edilmiştir. Taburcu edilerek poliklinik kontrolüne çağrılmıştır. + +Sorumlu Hekim: Dr. Nazlim YILDIRIM diff --git a/samples/epikriz_03.udf b/samples/epikriz_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..d9823e10411d80d50c7b13c2f943c159f66ad5b3 Binary files /dev/null and b/samples/epikriz_03.udf differ diff --git a/samples/epikriz_04.docx b/samples/epikriz_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..b5add1b9c1a38412c364bdfe182c80b0647bb695 Binary files /dev/null and b/samples/epikriz_04.docx differ diff --git a/samples/epikriz_04.pdf b/samples/epikriz_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..eccec9dba8b632ed8e93298ce6436e10f26e98ea Binary files /dev/null and b/samples/epikriz_04.pdf differ diff --git a/samples/epikriz_04.txt b/samples/epikriz_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..c16970dbb4d7402cdce82973b3a07e8d6424156b --- /dev/null +++ b/samples/epikriz_04.txt @@ -0,0 +1,9 @@ +EPİKRİZ RAPORU + +Hasta: Lavin ÇAKMAKÇI Protokol: 500544 +Doğum Tarihi: 12.01.1973 Kimlik: 41654705002 +Refakatçi: Masal Hacısalihoğlu (eşi) - 0533 729 55 47 + +Hasta psikiyatrik tedavi nedeniyle yatırılmış, yatışı süresince takip edilmiştir. Taburcu edilerek poliklinik kontrolüne çağrılmıştır. + +Sorumlu Hekim: Dr. Masal Turgutlu diff --git a/samples/epikriz_04.udf b/samples/epikriz_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..d05d1001d29f44ce5d49dd8f7536b8abdcb07327 Binary files /dev/null and b/samples/epikriz_04.udf differ diff --git a/samples/epikriz_05.docx b/samples/epikriz_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..bab9e88f9caff82ee2443495c3c6b368dc2bfe5d Binary files /dev/null and b/samples/epikriz_05.docx differ diff --git a/samples/epikriz_05.pdf b/samples/epikriz_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0c5eccd47e567b494c6f2afe2a8ec41ef23ac347 Binary files /dev/null and b/samples/epikriz_05.pdf differ diff --git a/samples/epikriz_05.png b/samples/epikriz_05.png new file mode 100644 index 0000000000000000000000000000000000000000..1b9089fa027bfe593f664890c02ce4a55c24deb8 --- /dev/null +++ b/samples/epikriz_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c26c92de376235f64f4e6a357545e796731ff79be4a98ededceab122586e0cfb +size 115954 diff --git a/samples/epikriz_05.scan.pdf b/samples/epikriz_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0721311997a571d737175a3ff40cfd4970f7462d --- /dev/null +++ b/samples/epikriz_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24b23f9db966afbb289f81f97eb8cb5c0d0a1f3903a0d33dbbd1d162b337b13d +size 182839 diff --git a/samples/epikriz_05.txt b/samples/epikriz_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..80b863d212d0654b054f16e53975246d64e2fe24 --- /dev/null +++ b/samples/epikriz_05.txt @@ -0,0 +1,9 @@ +EPİKRİZ RAPORU + +Hasta: Göktürk Tekinalp Protokol: 423657 +Doğum Tarihi: 26.10.1964 Kimlik: 17012612190 +Refakatçi: YİĞİTALP BOZDEMİR (eşi) - 02166991949 + +Hasta kanser tedavisi nedeniyle yatırılmış, yatışı süresince takip edilmiştir. Taburcu edilerek poliklinik kontrolüne çağrılmıştır. + +Sorumlu Hekim: Dr. Alaz EKŞİOĞLU diff --git a/samples/epikriz_05.udf b/samples/epikriz_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..5b54145ca72f08f72d3002a7be3e4c4c1da173e9 Binary files /dev/null and b/samples/epikriz_05.udf differ diff --git a/samples/eposta_01.pdf b/samples/eposta_01.pdf index 5b4c5132b99bde9a1583a1860b242e42865da184..21a3e9186e002ed8c0950279f6a5ba895884e9b7 100644 --- a/samples/eposta_01.pdf +++ b/samples/eposta_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6636573d856cb49f65cfc1ba59859238913f85976ec9f2a929c5295fd2d4c57 -size 791231 +oid sha256:90899439e9d4f7195043058916cb4b939bb1efbb651500fb44b3b566e5ccc5b7 +size 42892 diff --git a/samples/eposta_01.scan.pdf b/samples/eposta_01.scan.pdf index 738c90d4dd39d0c059549f7b095ddbf5a73b3610..e2958c87ac47c04a79516ade1f1d32c2e20f3f0c 100644 --- a/samples/eposta_01.scan.pdf +++ b/samples/eposta_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29afe9d8548ff68cbc7078da060f621edd4d37d08e6534d11f665e2a5f5173b8 -size 209536 +oid sha256:1ea5315ad0bf7b2122af39a090fed75f7b161cf33bccc8742546011de08f4475 +size 209397 diff --git a/samples/eposta_02.docx b/samples/eposta_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..cd3c421d12f06957ce6f3cac2cc7cd92a38ec505 Binary files /dev/null and b/samples/eposta_02.docx differ diff --git a/samples/eposta_02.pdf b/samples/eposta_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c957f916875e64620ee49ae9ea61cb7dc6297019 Binary files /dev/null and b/samples/eposta_02.pdf differ diff --git a/samples/eposta_02.txt b/samples/eposta_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..a8edbb078b5dfe90cf0b8795b2bcdc97d89b4db1 --- /dev/null +++ b/samples/eposta_02.txt @@ -0,0 +1,12 @@ +Kimden: ÜMMÜHAN ARSOY +Kime: ilgin.sozeri@hotmail.com +Tarih: 26.09.2025 +Konu: Dosya hk. + +Merhaba Ilgın Hanım, + +Dün Masal KALKAVAN ile görüştüm, kendisi icra dosyasının kapatılması için ödemeyi bu hafta yapacağını söyledi. Ödeme dekontunu 0533 474 74 58 numarasına WhatsApp'tan iletecekmiş. Adresinin değiştiğini, artık Hürriyet Mahallesi Begonya Sokak No: 141/27 Beşiktaş / İstanbul adresinde oturduğunu belirtti. +Ayrıca annesinin kanser tedavisi sürecinde olduğunu, bu yüzden duruşmaya gelemeyeceğini ekledi. + +İyi çalışmalar, +ÜMMÜHAN ARSOY diff --git a/samples/eposta_02.udf b/samples/eposta_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..7e7952ab382254eeb32bca81271704a5d47a5755 Binary files /dev/null and b/samples/eposta_02.udf differ diff --git a/samples/eposta_03.docx b/samples/eposta_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..450cc7f670fbf11323c2dce304b406d81a3f6123 Binary files /dev/null and b/samples/eposta_03.docx differ diff --git a/samples/eposta_03.pdf b/samples/eposta_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7d860b605580341eeee0f0026aedd2956878cde5 Binary files /dev/null and b/samples/eposta_03.pdf differ diff --git a/samples/eposta_03.png b/samples/eposta_03.png new file mode 100644 index 0000000000000000000000000000000000000000..ac697a00d0ee87a9c2516bfd8f753b13d4ab6a21 --- /dev/null +++ b/samples/eposta_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd1278f6b20643ad493d5daf814dfcc4fddf705654c5fc4d21d36eaa886e6eef +size 143800 diff --git a/samples/eposta_03.scan.pdf b/samples/eposta_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c5eca979e076f2e05e5d4e7c6510728dcf482e93 --- /dev/null +++ b/samples/eposta_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54753d93e3d32cb63874fee9cac8c7f0d45773170800e249cc843fa4904a9ca9 +size 206589 diff --git a/samples/eposta_03.txt b/samples/eposta_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..91aeca5f5d573978376d65092f6407d560ed7897 --- /dev/null +++ b/samples/eposta_03.txt @@ -0,0 +1,12 @@ +Kimden: Cevale Güleryüz +Kime: lavin.yesilbag@gmail.com +Tarih: 14.10.2024 +Konu: Dosya hk. + +Merhaba Lavin Hanım, + +Dün Ada Soydaş ile görüştüm, kendisi icra dosyasının kapatılması için ödemeyi bu hafta yapacağını söyledi. Ödeme dekontunu +90 (542) 309 5364 numarasına WhatsApp'tan iletecekmiş. Adresinin değiştiğini, artık Esentepe Mahallesi, Bağdat Caddesi No: 164 Kat: 8 Çankaya/ANKARA adresinde oturduğunu belirtti. +Ayrıca annesinin tip 2 diyabet tanısı sürecinde olduğunu, bu yüzden duruşmaya gelemeyeceğini ekledi. + +İyi çalışmalar, +Cevale Güleryüz diff --git a/samples/eposta_03.udf b/samples/eposta_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..81f187ac024de69df6ce94193a1df3f4b0d42f19 Binary files /dev/null and b/samples/eposta_03.udf differ diff --git a/samples/eposta_04.docx b/samples/eposta_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..0028acf4262242e1300ab1852a7c48f6012d3895 Binary files /dev/null and b/samples/eposta_04.docx differ diff --git a/samples/eposta_04.pdf b/samples/eposta_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c429f37d1dcb87fd1a238b424b0de494a86ae65e Binary files /dev/null and b/samples/eposta_04.pdf differ diff --git a/samples/eposta_04.txt b/samples/eposta_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..4329d8eec7f4736da7e2bc4e9873d580ec125806 --- /dev/null +++ b/samples/eposta_04.txt @@ -0,0 +1,12 @@ +Kimden: SEFER TEKİNALP +Kime: gokturk.yesilbag@yandex.com.tr +Tarih: 08.01.2021 +Konu: Dosya hk. + +Merhaba Göktürk Hanım, + +Dün YİĞİTALP AĞAOĞLU ile görüştüm, kendisi icra dosyasının kapatılması için ödemeyi bu hafta yapacağını söyledi. Ödeme dekontunu +90 (232) 274 2754 numarasına WhatsApp'tan iletecekmiş. Adresinin değiştiğini, artık Bahçelievler Mah. Fevzi Çakmak Cad. No:18 Daire:31 Beşiktaş/İstanbul adresinde oturduğunu belirtti. +Ayrıca annesinin psikiyatrik tedavi sürecinde olduğunu, bu yüzden duruşmaya gelemeyeceğini ekledi. + +İyi çalışmalar, +SEFER TEKİNALP diff --git a/samples/eposta_04.udf b/samples/eposta_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..e9de1b279dffbfbcdb34599b335dab5c2d118abe Binary files /dev/null and b/samples/eposta_04.udf differ diff --git a/samples/eposta_05.docx b/samples/eposta_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..19545e0a64140a2b503b812c3f36a35dfdd04f7d Binary files /dev/null and b/samples/eposta_05.docx differ diff --git a/samples/eposta_05.pdf b/samples/eposta_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cf8dfb6df20229cf696e04790ef42296197aaa3a Binary files /dev/null and b/samples/eposta_05.pdf differ diff --git a/samples/eposta_05.png b/samples/eposta_05.png new file mode 100644 index 0000000000000000000000000000000000000000..54c5007697ea6f8b7ded6439aa5f22844e54d0aa --- /dev/null +++ b/samples/eposta_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce284ff0d46c9c9f582f88fd023ad7e6f00a4a5d710ab7f2544c343d35269663 +size 147008 diff --git a/samples/eposta_05.scan.pdf b/samples/eposta_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6f57491590763fda74d560e7381a16fa920a415d --- /dev/null +++ b/samples/eposta_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a539dbc2a3515b758a3765d40df8c1bc62fb71a290745fa369ebf44630180223 +size 205854 diff --git a/samples/eposta_05.txt b/samples/eposta_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..a335fda780cee8da6acbbbb41417afe779539c01 --- /dev/null +++ b/samples/eposta_05.txt @@ -0,0 +1,12 @@ +Kimden: Lavin Turgutlu +Kime: ilgin.agaoglu@gmail.com +Tarih: 26.04.2020 +Konu: Dosya hk. + +Merhaba Ilgın Hanım, + +Dün CİHANDİDE ATASEVER ile görüştüm, kendisi icra dosyasının kapatılması için ödemeyi bu hafta yapacağını söyledi. Ödeme dekontunu 0542 543 83 90 numarasına WhatsApp'tan iletecekmiş. Adresinin değiştiğini, artık Bahçelievler Mah. İnönü Cad. No:143 Daire:38 Beşiktaş/İstanbul adresinde oturduğunu belirtti. +Ayrıca annesinin tip 2 diyabet tanısı sürecinde olduğunu, bu yüzden duruşmaya gelemeyeceğini ekledi. + +İyi çalışmalar, +Lavin Turgutlu diff --git a/samples/eposta_05.udf b/samples/eposta_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..d23e03fc37afd6e1bb7e289b581a1e32dd0bdd9b Binary files /dev/null and b/samples/eposta_05.udf differ diff --git a/samples/fatura_01.pdf b/samples/fatura_01.pdf index 23b2928ea971f9ec71ce0fc37d50afdfde073e69..381ad9276f0a308e763ad7395c9dfd427ed8e9c2 100644 --- a/samples/fatura_01.pdf +++ b/samples/fatura_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8929d19097f013bf87dd939a4ab177d52b17def99bffad3cf3e1408d60b064b5 -size 792424 +oid sha256:039574ba4da5e98bdc0c92dbcf05cfb5b12fc6cf7341cfbc848079355fa2e8ec +size 43629 diff --git a/samples/fatura_01.scan.pdf b/samples/fatura_01.scan.pdf index 650b3fe5e79c367d8f2d7e407b2d22c8a17bce40..981dff28aed65b7cf4db6bd601b80e903da4c24e 100644 --- a/samples/fatura_01.scan.pdf +++ b/samples/fatura_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50995fe118787de40173ce272fd2d1ea606e3c16156ff7c548ab81d45b51d364 -size 224118 +oid sha256:4c5711eac41ab0c768bca1e90a9cdf4180c6856cf57745a2b15ccb9786b0e02c +size 223979 diff --git a/samples/fatura_02.docx b/samples/fatura_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..af77cd14a066af2b6061ccaa90042004e4f7c1a4 Binary files /dev/null and b/samples/fatura_02.docx differ diff --git a/samples/fatura_02.pdf b/samples/fatura_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2db93ca8d789ff687d5d2180f5b3a57b40ccdd97 Binary files /dev/null and b/samples/fatura_02.pdf differ diff --git a/samples/fatura_02.txt b/samples/fatura_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..fd639e8b53f3873daa376b9142bd6d4d3acbb01b --- /dev/null +++ b/samples/fatura_02.txt @@ -0,0 +1,23 @@ +e-Arşiv Fatura + +Akdeniz Turizm A.Ş. +Vergi Dairesi: Büyük Mükellefler VKN: 9437242463 +Mersis No: 0123456789000015 Ticaret Sicil No: 123456 + +SAYIN +Safinaz KILIÇARSLAN +Yenişehir Mahallesi Çınar Sokak No: 77/5 Üsküdar / İstanbul +TCKN: 73495653068 +Tel: 05558998641 +E-Posta: safinaz.kilicarslan@outlook.com + +Fatura No: GIB2025234224833 +Fatura Tarihi: 12.09.2024 +ETTN: 8e1a0cc7-bcc3-4287-8602-8b3ebe6e5d4f + +Mal Hizmet Miktar Birim Fiyat KDV Oranı Tutar +Danışmanlık hizmeti 1 722.661,00 TL %20 716.796,00 TL +Toplam: 251.011,00 TL Hesaplanan KDV: 277.598,00 TL Ödenecek Tutar: 28.275,00 TL + +Ödeme için banka hesabımız: Akdeniz Bankası TR IBAN numarası bilgisi faturanın arkasındadır. +Kredi kartı ile yapılan ödeme: 4177 2489 7677 0820 diff --git a/samples/fatura_02.udf b/samples/fatura_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..e793ff2890aab5b5d763136475cd7b121a8ef808 Binary files /dev/null and b/samples/fatura_02.udf differ diff --git a/samples/fatura_03.docx b/samples/fatura_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..ebc00f56ba3580b1bb21adf704fbd81107f545ab Binary files /dev/null and b/samples/fatura_03.docx differ diff --git a/samples/fatura_03.pdf b/samples/fatura_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4dd36561310b34080b978ab5ce5ea116412f93f0 Binary files /dev/null and b/samples/fatura_03.pdf differ diff --git a/samples/fatura_03.png b/samples/fatura_03.png new file mode 100644 index 0000000000000000000000000000000000000000..657a7784678bdeae6c6b9478cc26aae7d8134d61 --- /dev/null +++ b/samples/fatura_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6506b9c72565d9033661664508b78e18648cea9628a745a729af80f18008ad73 +size 174888 diff --git a/samples/fatura_03.scan.pdf b/samples/fatura_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..430de7adb8fa035bc7723dad56bad209ee6afe7a --- /dev/null +++ b/samples/fatura_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b0a7a735d56121c78a294a29ee3923e52fc54a7c06dfbaff624a9863ab402fb +size 220902 diff --git a/samples/fatura_03.txt b/samples/fatura_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..fd60cf0401ef88eb5f8833d72b506d740ae1726c --- /dev/null +++ b/samples/fatura_03.txt @@ -0,0 +1,23 @@ +e-Arşiv Fatura + +Akdeniz Turizm A.Ş. +Vergi Dairesi: Büyük Mükellefler VKN: 1855100952 +Mersis No: 0123456789000015 Ticaret Sicil No: 123456 + +SAYIN +Ilgın Atasever +Kızılay Mahallesi Çınar Sokak No: 55/35 Seyhan / Adana +TCKN: 83583233218 +Tel: 02162328268 +E-Posta: ilgin.atasever@outlook.com + +Fatura No: GIB2022684825641 +Fatura Tarihi: 08.04.2020 +ETTN: 93cd7e6f-aa5c-45e4-98d4-86636dd9de7b + +Mal Hizmet Miktar Birim Fiyat KDV Oranı Tutar +Danışmanlık hizmeti 1 756.430,00 TL %20 191.553,00 TL +Toplam: 571.443,00 TL Hesaplanan KDV: 177.031,00 TL Ödenecek Tutar: 491.243,00 TL + +Ödeme için banka hesabımız: Akdeniz Bankası TR IBAN numarası bilgisi faturanın arkasındadır. +Kredi kartı ile yapılan ödeme: 5274 9896 4495 8631 diff --git a/samples/fatura_03.udf b/samples/fatura_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..273105b5eaa6cb47bdfe8a747770e7f5b86a4f8d Binary files /dev/null and b/samples/fatura_03.udf differ diff --git a/samples/fatura_04.docx b/samples/fatura_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..d6755398fcbbd0a15630c45270b63093943543ac Binary files /dev/null and b/samples/fatura_04.docx differ diff --git a/samples/fatura_04.pdf b/samples/fatura_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bb8c6ae8cf0849855fcff0503ca3a7c24e262bbe Binary files /dev/null and b/samples/fatura_04.pdf differ diff --git a/samples/fatura_04.txt b/samples/fatura_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..438a110dc13617fc365bdcd7b48bbd6b34d942ec --- /dev/null +++ b/samples/fatura_04.txt @@ -0,0 +1,23 @@ +e-Arşiv Fatura + +Ege Yapı Malzemeleri Ltd. Şti. +Vergi Dairesi: Büyük Mükellefler VKN: 3544189296 +Mersis No: 0123456789000015 Ticaret Sicil No: 123456 + +SAYIN +DEĞMEER SEZER +Fatih Sultan Mehmet Mh. Tunalı Hilmi Cd. Begonya Apt. No:100 D:40 41296 Beşiktaş/İstanbul +TCKN: 28537675980 +Tel: +90 542 255 81 58 +E-Posta: degmeer.sezer@outlook.com + +Fatura No: GIB2022103178400 +Fatura Tarihi: 16.04.2023 +ETTN: f87db32f-7c86-45ae-ad92-4e06d10ad589 + +Mal Hizmet Miktar Birim Fiyat KDV Oranı Tutar +Danışmanlık hizmeti 1 242.910,00 TL %20 370.214,00 TL +Toplam: 789.636,00 TL Hesaplanan KDV: 481.689,00 TL Ödenecek Tutar: 33.861,00 TL + +Ödeme için banka hesabımız: Akdeniz Bankası TR IBAN numarası bilgisi faturanın arkasındadır. +Kredi kartı ile yapılan ödeme: 4764 3825 7150 4091 diff --git a/samples/fatura_04.udf b/samples/fatura_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..bde8d9328b40fd0381b3d4906db5ace5c5938b92 Binary files /dev/null and b/samples/fatura_04.udf differ diff --git a/samples/fatura_05.docx b/samples/fatura_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..72b134c344410d94c8654f090770be294ef729e6 Binary files /dev/null and b/samples/fatura_05.docx differ diff --git a/samples/fatura_05.pdf b/samples/fatura_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8b868db0fcbe3aeeff99b20580879e11478b98ad Binary files /dev/null and b/samples/fatura_05.pdf differ diff --git a/samples/fatura_05.png b/samples/fatura_05.png new file mode 100644 index 0000000000000000000000000000000000000000..af62e8b3c1125cb7dbfed8b928ec294b8a453e8e --- /dev/null +++ b/samples/fatura_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b661ae8b1a671953df0e0f561ab8140ea49339636ace03007cf2e18ead01bd2 +size 177261 diff --git a/samples/fatura_05.scan.pdf b/samples/fatura_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3ec1f1f8a8a3e736f6064f07c2f5ad5969ae5bd8 --- /dev/null +++ b/samples/fatura_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f194590addb8e64622471e9cab65444b0042b189dbd8094fcae6ad3ffff35940 +size 223527 diff --git a/samples/fatura_05.txt b/samples/fatura_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..08f82b2b7f2122659908b33e800a5e6eff702ee5 --- /dev/null +++ b/samples/fatura_05.txt @@ -0,0 +1,23 @@ +e-Arşiv Fatura + +Anadolu Gıda Sanayi ve Ticaret A.Ş. +Vergi Dairesi: Büyük Mükellefler VKN: 8781842975 +Mersis No: 0123456789000015 Ticaret Sicil No: 123456 + +SAYIN +Duracan ERGÜL +Cumhuriyet Mah. Talatpaşa Cad. No:146 Daire:6 Seyhan/Adana +TCKN: 43110604560 +Tel: +90 216 261 33 97 +E-Posta: duracan.ergul@hotmail.com + +Fatura No: GIB2026354530831 +Fatura Tarihi: 08.03.2020 +ETTN: d335427f-cd7d-41d4-a993-74b83e88aa32 + +Mal Hizmet Miktar Birim Fiyat KDV Oranı Tutar +Danışmanlık hizmeti 1 443.180,00 TL %20 418.147,00 TL +Toplam: 36.589,00 TL Hesaplanan KDV: 625.973,00 TL Ödenecek Tutar: 607.119,00 TL + +Ödeme için banka hesabımız: Akdeniz Bankası TR IBAN numarası bilgisi faturanın arkasındadır. +Kredi kartı ile yapılan ödeme: 4807 4229 2885 0265 diff --git a/samples/fatura_05.udf b/samples/fatura_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..617826e302a36f0f274123f472e303a205954944 Binary files /dev/null and b/samples/fatura_05.udf differ diff --git a/samples/icra_01.pdf b/samples/icra_01.pdf index 4cee4124309c0f924edced455a23e57005ccef8d..a424a4639bb3b3885c676795158d6b3e790835cc 100644 --- a/samples/icra_01.pdf +++ b/samples/icra_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25e62b8bd55ac8bd171db2aad43527267f9cb8a71cef502a9baf2220951136c5 -size 790254 +oid sha256:8fef8e75dd67db138f705f68d1a60b16f99e3ebcc3c48d5ddab3e7d430eaee29 +size 41186 diff --git a/samples/icra_01.scan.pdf b/samples/icra_01.scan.pdf index ec800ed9be7d1177af27c8c90941873305254e58..2b867504d3dae2943888f522aece4f6da7738ead 100644 --- a/samples/icra_01.scan.pdf +++ b/samples/icra_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:505550c1bf60c6bd4d00ccd90bd665ea108b40f76193db151ffce2150c133635 -size 191364 +oid sha256:7471d70c33d54ddbafd16dcaab7e706107421f7b4e70ea6742ab1cb95cf05f18 +size 191225 diff --git a/samples/icra_02.docx b/samples/icra_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..d33abb0a74fc610cb70daedf6f16e5d011e46c26 Binary files /dev/null and b/samples/icra_02.docx differ diff --git a/samples/icra_02.pdf b/samples/icra_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..07f1d15b6b37a9fa00474eb7dce4d61fe1fc1715 Binary files /dev/null and b/samples/icra_02.pdf differ diff --git a/samples/icra_02.txt b/samples/icra_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..6355f3834f013ecc866698a45d83256e4760d114 --- /dev/null +++ b/samples/icra_02.txt @@ -0,0 +1,9 @@ +TAKİP TALEBİ + +ALACAKLI : Kanak Cömertoğlu, 38629457486 +ALACAKLI VEKİLİ : Av. Rüzgar Yeşilbağ ruzgar.yesilbag@outlook.com +BORÇLU : Misra Soylu + Erenköy Mahallesi 2. Ilgın Sokak No: 113/35 Üsküdar / İstanbul +ALACAK MİKTARI : 331.354,00 TL +ALACAĞIN NEDENİ : Misra Soylu tarafından düzenlenen senet. +Tahsil edilen paranın TR33 0006 4078 6250 8195 7449 22 numaralı hesaba yatırılması talep olunur. diff --git a/samples/icra_02.udf b/samples/icra_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..83bdb2151893117a6b0a73add0e3caf0cac08e98 Binary files /dev/null and b/samples/icra_02.udf differ diff --git a/samples/icra_03.docx b/samples/icra_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..8a60bb45c28ef544009f08f6236e46970add199a Binary files /dev/null and b/samples/icra_03.docx differ diff --git a/samples/icra_03.pdf b/samples/icra_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b840fb2f950c18285232597a1e71d691939273c3 Binary files /dev/null and b/samples/icra_03.pdf differ diff --git a/samples/icra_03.png b/samples/icra_03.png new file mode 100644 index 0000000000000000000000000000000000000000..1cc38327d3a91fb94143ba7fb26ae969503789e3 --- /dev/null +++ b/samples/icra_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:998d5da15eaba3615fee55f3ca3ac1d7c0174ae9b7f1263f8b314afc431295ba +size 127638 diff --git a/samples/icra_03.scan.pdf b/samples/icra_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a4d2bca417f257d647031b30ce6536e591526717 --- /dev/null +++ b/samples/icra_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43cc0066f87f6dd973333bded46205feb1c45bd93aa9fbf5a3fa946c73742012 +size 192147 diff --git a/samples/icra_03.txt b/samples/icra_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..36d6b07310a57a78c3b51d258a21142e432f2ccd --- /dev/null +++ b/samples/icra_03.txt @@ -0,0 +1,9 @@ +TAKİP TALEBİ + +ALACAKLI : Tercan ŞAMA, 62938158642 +ALACAKLI VEKİLİ : Av. TÜRKMEN KARADERELİ turkmen.karadereli@gmail.com +BORÇLU : Göli Uzunöz + Kavaklıdere Mahallesi Yasemin Sokak No: 57/18 Beşiktaş / İstanbul +ALACAK MİKTARI : 561.723,00 TL +ALACAĞIN NEDENİ : Göli Uzunöz tarafından düzenlenen senet. +Tahsil edilen paranın TR71 0001 2003 1925 7003 0881 24 numaralı hesaba yatırılması talep olunur. diff --git a/samples/icra_03.udf b/samples/icra_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..d15053788332c5004a1ecbb755020b76cd2bff24 Binary files /dev/null and b/samples/icra_03.udf differ diff --git a/samples/icra_04.docx b/samples/icra_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..e474f145975cb3126c5c1b42b3d97aa0536d6ffa Binary files /dev/null and b/samples/icra_04.docx differ diff --git a/samples/icra_04.pdf b/samples/icra_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0665e8623e5a9c68032f85f96141cc81b9e13665 Binary files /dev/null and b/samples/icra_04.pdf differ diff --git a/samples/icra_04.txt b/samples/icra_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..d56d042b69efbe7b75f8d954d241cbfe5a1e9a53 --- /dev/null +++ b/samples/icra_04.txt @@ -0,0 +1,9 @@ +TAKİP TALEBİ + +ALACAKLI : Kezban DURDU, 34953688602 +ALACAKLI VEKİLİ : Av. Akalan Aslan akalan.aslan@gmail.com +BORÇLU : Menişan Çakmakçı + Koşuyolu Mah. Ziya Gökalp Cad. No:158 Daire:21 Seyhan/Adana +ALACAK MİKTARI : 221.682,00 TL +ALACAĞIN NEDENİ : Menişan Çakmakçı tarafından düzenlenen senet. +Tahsil edilen paranın TR80 0011 1038 6851 1019 7978 42 numaralı hesaba yatırılması talep olunur. diff --git a/samples/icra_04.udf b/samples/icra_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..03351817e2ccc11784095a31ecfc04814496556a Binary files /dev/null and b/samples/icra_04.udf differ diff --git a/samples/icra_05.docx b/samples/icra_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..e3d955f09457d500f6989068bdd7a3387b2bd8fc Binary files /dev/null and b/samples/icra_05.docx differ diff --git a/samples/icra_05.pdf b/samples/icra_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8411cef262febc0c1129dc5c544fb5fdf25f33dc Binary files /dev/null and b/samples/icra_05.pdf differ diff --git a/samples/icra_05.png b/samples/icra_05.png new file mode 100644 index 0000000000000000000000000000000000000000..1387614895860412707a9a466de1f53e0aeb0b00 --- /dev/null +++ b/samples/icra_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac78c7c27f50f94b4385acb6ca4ed163c975f6b81fe48400adb432b74fe9f4b7 +size 129447 diff --git a/samples/icra_05.scan.pdf b/samples/icra_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3679016b903a098347b6393a5a3c730f063ac6b5 --- /dev/null +++ b/samples/icra_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bf2b1676e15b9820310689b5f5e9585011f872d99130db89dc7d072c32b8d1a +size 192158 diff --git a/samples/icra_05.txt b/samples/icra_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..65bb1c40da214153842cae842e8a156fc6fb92f1 --- /dev/null +++ b/samples/icra_05.txt @@ -0,0 +1,9 @@ +TAKİP TALEBİ + +ALACAKLI : Besey Alemdar, 63865729028 +ALACAKLI VEKİLİ : Av. Nurhayet Demir nurhayet.demir@gmail.com +BORÇLU : Rüzgar Kılıçarslan + Gazi Osman Paşa Mahallesi Çınar Sokak No: 119/22 Muratpaşa / ANTALYA +ALACAK MİKTARI : 870.954,00 TL +ALACAĞIN NEDENİ : Rüzgar Kılıçarslan tarafından düzenlenen senet. +Tahsil edilen paranın TR67 0006 4091 3030 6365 8430 44 numaralı hesaba yatırılması talep olunur. diff --git a/samples/icra_05.udf b/samples/icra_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..862efd4cfd15c6a12d2a5513ae53f93f97493992 Binary files /dev/null and b/samples/icra_05.udf differ diff --git a/samples/ihtarname_01.pdf b/samples/ihtarname_01.pdf index 5c0adee5d431319bb00cab0e45a8aa8cafa45904..3b5502007610d176fb6833d05fefb94576fd3598 100644 --- a/samples/ihtarname_01.pdf +++ b/samples/ihtarname_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42b39e85506a7d0fbdffa5126c85983f236c319ba1b4485b1b4d1ee0d93ed617 -size 791237 +oid sha256:0635abd55f488e60e824ba3e10cdfb979f79aac8746c46effd651a4d73e973e5 +size 41941 diff --git a/samples/ihtarname_01.scan.pdf b/samples/ihtarname_01.scan.pdf index 293d1622c8ae41ceb6a470a08425f291920e8b5f..30b993a1e0701eeb711e8ebced5e81aee7d4dc6d 100644 --- a/samples/ihtarname_01.scan.pdf +++ b/samples/ihtarname_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16946af73a0007918018dcc2854cfa9580ce5fb9e67ad7a6b68727d70182ff14 -size 211691 +oid sha256:36b0b17fcc6a7169b4147688a1cd03ad202f1f0530c0e56f1c4c2c3155646aa4 +size 211552 diff --git a/samples/ihtarname_02.docx b/samples/ihtarname_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..4bafe236f7e76cb1039361d942a8aef310e72816 Binary files /dev/null and b/samples/ihtarname_02.docx differ diff --git a/samples/ihtarname_02.pdf b/samples/ihtarname_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f3465afa5d29846b3ebe9670d9b435cec6ef7189 Binary files /dev/null and b/samples/ihtarname_02.pdf differ diff --git a/samples/ihtarname_02.txt b/samples/ihtarname_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..9e52ead05d36a2dcf7e0ee257f8cae47d543b630 --- /dev/null +++ b/samples/ihtarname_02.txt @@ -0,0 +1,12 @@ +İHTARNAME + +İHTAR EDEN : Feremez Yüksel - TC: 94434339874 + Erenköy Mh. Fevzi Çakmak Cd. Çınar Apt. No:177 D:22 66244 Bornova/İzmir +MUHATAP : Vahittin GÜLERYÜZ + Sahrayıcedit Mh. Gazi Mustafa Kemal Cd. Menekşe Apt. No:144 D:29 26155 Nilüfer/BURSA + +KONU : 578.615,00 TL kira alacağının ödenmesi ihtarıdır. + +Sayın Vahittin GÜLERYÜZ, aramızdaki kira sözleşmesi gereği ödemeniz gereken kira bedellerini ödemediniz. İşbu ihtarnamenin tebliğinden itibaren 30 gün içinde ödemeyi TR63 0006 2073 9909 9244 1013 85 hesabına yapmanızı, aksi halde yasal yollara başvurulacağını ihtar ederim. Bilgi için (216) 378-70-98. +Tarih: 24.11.2020 +İhtar Eden: Feremez Yüksel diff --git a/samples/ihtarname_02.udf b/samples/ihtarname_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..3ccc3c33352810fcc4bb1905ccec763c63861203 Binary files /dev/null and b/samples/ihtarname_02.udf differ diff --git a/samples/ihtarname_03.docx b/samples/ihtarname_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..7f9a15f2237f217dd54ee2cc9f29e77d96c23a6e Binary files /dev/null and b/samples/ihtarname_03.docx differ diff --git a/samples/ihtarname_03.pdf b/samples/ihtarname_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..28f68d22d13826fa509a4ef9aae0c6474acd007c Binary files /dev/null and b/samples/ihtarname_03.pdf differ diff --git a/samples/ihtarname_03.png b/samples/ihtarname_03.png new file mode 100644 index 0000000000000000000000000000000000000000..07e34b70dc9d51b0ecae6c0a5e8cc92141f7d892 --- /dev/null +++ b/samples/ihtarname_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f5bafd1181550537e196da950f7261e4a9d876a669c93e6e5ee3bb3f9f2088c +size 149894 diff --git a/samples/ihtarname_03.scan.pdf b/samples/ihtarname_03.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..93a57700047dc37221914054948e773452401bf7 --- /dev/null +++ b/samples/ihtarname_03.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28d06aea638b9180c49203581a9455860b2392fac47aab1ddb2a695157674889 +size 208509 diff --git a/samples/ihtarname_03.txt b/samples/ihtarname_03.txt new file mode 100644 index 0000000000000000000000000000000000000000..19c2452dc65d6adb0383a2f87a8a476969ec5d68 --- /dev/null +++ b/samples/ihtarname_03.txt @@ -0,0 +1,12 @@ +İHTARNAME + +İHTAR EDEN : Göktürk Ağaoğlu - TC: 39659065474 + Güzelyalı Mahallesi Karanfil Sokak No: 55/7 Seyhan / Adana +MUHATAP : Ada Soydaş + Cumhuriyet Mahallesi 2. Ilgın Sokak No: 32/10 Şahinbey / GAZİANTEP + +KONU : 167.778,00 TL kira alacağının ödenmesi ihtarıdır. + +Sayın Ada Soydaş, aramızdaki kira sözleşmesi gereği ödemeniz gereken kira bedellerini ödemediniz. İşbu ihtarnamenin tebliğinden itibaren 30 gün içinde ödemeyi TR46 0001 5003 2294 9434 3943 91 hesabına yapmanızı, aksi halde yasal yollara başvurulacağını ihtar ederim. Bilgi için (232) 547-55-62. +Tarih: 16.09.2020 +İhtar Eden: Göktürk Ağaoğlu diff --git a/samples/ihtarname_03.udf b/samples/ihtarname_03.udf new file mode 100644 index 0000000000000000000000000000000000000000..b7512ef36c916e2795c95c975cb87f60cd9c4b25 Binary files /dev/null and b/samples/ihtarname_03.udf differ diff --git a/samples/ihtarname_04.docx b/samples/ihtarname_04.docx new file mode 100644 index 0000000000000000000000000000000000000000..4af2782e963f6ea7ffe62f070c5ddc59e6f2ad2e Binary files /dev/null and b/samples/ihtarname_04.docx differ diff --git a/samples/ihtarname_04.pdf b/samples/ihtarname_04.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2593e730beb23277f719fbad5aafeb99a74c1207 Binary files /dev/null and b/samples/ihtarname_04.pdf differ diff --git a/samples/ihtarname_04.txt b/samples/ihtarname_04.txt new file mode 100644 index 0000000000000000000000000000000000000000..7bd0dfb932ec1c2758a28143ece838c36d79d237 --- /dev/null +++ b/samples/ihtarname_04.txt @@ -0,0 +1,12 @@ +İHTARNAME + +İHTAR EDEN : Lavin SOYDAŞ - TC: 10964931340 + Fatih Sultan Mehmet Mh. Talatpaşa Cd. Menekşe Apt. No:57 D:1 36003 Muratpaşa/ANTALYA +MUHATAP : Gülder Tarhan + Gazi Osman Paşa Mahallesi, Cinnah Caddesi No: 98 Kat: 6 Üsküdar/İstanbul + +KONU : 146.504,00 TL kira alacağının ödenmesi ihtarıdır. + +Sayın Gülder Tarhan, aramızdaki kira sözleşmesi gereği ödemeniz gereken kira bedellerini ödemediniz. İşbu ihtarnamenin tebliğinden itibaren 30 gün içinde ödemeyi TR31 0006 7010 3241 8914 1812 77 hesabına yapmanızı, aksi halde yasal yollara başvurulacağını ihtar ederim. Bilgi için +90 232 826 30 66. +Tarih: 20.11.2026 +İhtar Eden: Lavin SOYDAŞ diff --git a/samples/ihtarname_04.udf b/samples/ihtarname_04.udf new file mode 100644 index 0000000000000000000000000000000000000000..8a44c7d2494eee85910853c009fd7d676c1f4811 Binary files /dev/null and b/samples/ihtarname_04.udf differ diff --git a/samples/ihtarname_05.docx b/samples/ihtarname_05.docx new file mode 100644 index 0000000000000000000000000000000000000000..cd47dbaa7847b112d9e8045073d12a29d1937a2e Binary files /dev/null and b/samples/ihtarname_05.docx differ diff --git a/samples/ihtarname_05.pdf b/samples/ihtarname_05.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2813c5363544cd254b26e51ddff84ee76dd6d82e Binary files /dev/null and b/samples/ihtarname_05.pdf differ diff --git a/samples/ihtarname_05.png b/samples/ihtarname_05.png new file mode 100644 index 0000000000000000000000000000000000000000..a3061d0c5f7f06514fada26b091442cd302dc0c5 --- /dev/null +++ b/samples/ihtarname_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fa2730c05f467bb57d5aaa01458f71bd437f460b44c9937c446c7b42c73a11a +size 156873 diff --git a/samples/ihtarname_05.scan.pdf b/samples/ihtarname_05.scan.pdf new file mode 100644 index 0000000000000000000000000000000000000000..442ef304c081843b37f8e5df89a8f0ae9d2937e3 --- /dev/null +++ b/samples/ihtarname_05.scan.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d1eb6cd1000d291d063bedc65a52a523b903522e5fbcdd0e7d7006e42ec44a3 +size 211441 diff --git a/samples/ihtarname_05.txt b/samples/ihtarname_05.txt new file mode 100644 index 0000000000000000000000000000000000000000..e9bae248cad5ef36bef7e424082375a6a18eadcb --- /dev/null +++ b/samples/ihtarname_05.txt @@ -0,0 +1,12 @@ +İHTARNAME + +İHTAR EDEN : Uzbay Yaman - TC: 44312412236 + Hürriyet Mah. Halaskargazi Cad. No:4 Daire:20 Beşiktaş/İstanbul +MUHATAP : Göktürk Keskinkılıç + Bahçelievler Mh. Fevzi Çakmak Cd. Lale Apt. No:18 D:16 18722 Kadıköy/İSTANBUL + +KONU : 546.406,00 TL kira alacağının ödenmesi ihtarıdır. + +Sayın Göktürk Keskinkılıç, aramızdaki kira sözleşmesi gereği ödemeniz gereken kira bedellerini ödemediniz. İşbu ihtarnamenin tebliğinden itibaren 30 gün içinde ödemeyi TR80 0001 5016 0044 4232 7471 96 hesabına yapmanızı, aksi halde yasal yollara başvurulacağını ihtar ederim. Bilgi için 0532 323 31 58. +Tarih: 10.09.2026 +İhtar Eden: Uzbay Yaman diff --git a/samples/ihtarname_05.udf b/samples/ihtarname_05.udf new file mode 100644 index 0000000000000000000000000000000000000000..a156e05970c86ebe834fa4c17e941d664e573ee3 Binary files /dev/null and b/samples/ihtarname_05.udf differ diff --git a/samples/is_sozlesmesi_01.pdf b/samples/is_sozlesmesi_01.pdf index 7002c6d10a66cc43b2f363c7dce2d0154f02bb18..49496ff3937d8b5657052301e5a6c5fc7679a4cf 100644 --- a/samples/is_sozlesmesi_01.pdf +++ b/samples/is_sozlesmesi_01.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3da570704ca9d0fb2a447340e4e3447d5ba13a3dd21a7eba58bbe8bf879d5dd -size 791087 +oid sha256:1cfcc84f961536eac0b4042d0792035eba615dd1b3ef984cc9e22917b9705d28 +size 42823 diff --git a/samples/is_sozlesmesi_01.scan.pdf b/samples/is_sozlesmesi_01.scan.pdf index c10d03821f4cc8b9f6767dabc1cb7281600075f5..5ef8dad377959e187f2497881866667eda3c973c 100644 --- a/samples/is_sozlesmesi_01.scan.pdf +++ b/samples/is_sozlesmesi_01.scan.pdf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52ca3520f1509cbc3f53e13b3afb8686550f1b655cfc74d0c662a126b2d44874 -size 214318 +oid sha256:5ea3e120c1cd7d79346c5c9ec68f13cbd7f94d1f0e58e9e5114e2ad8edbe2993 +size 214179 diff --git a/samples/is_sozlesmesi_02.docx b/samples/is_sozlesmesi_02.docx new file mode 100644 index 0000000000000000000000000000000000000000..a8ccc03545be59099000006b4efafba51c34a5c8 Binary files /dev/null and b/samples/is_sozlesmesi_02.docx differ diff --git a/samples/is_sozlesmesi_02.pdf b/samples/is_sozlesmesi_02.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ed4557288df3be87b0a4dd10e16b3c3d9e60612a Binary files /dev/null and b/samples/is_sozlesmesi_02.pdf differ diff --git a/samples/is_sozlesmesi_02.txt b/samples/is_sozlesmesi_02.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b4e1f26a6548bef6da766cf159f25b46920d03a --- /dev/null +++ b/samples/is_sozlesmesi_02.txt @@ -0,0 +1,13 @@ +BELİRSİZ SÜRELİ İŞ SÖZLEŞMESİ + +MADDE 1 - TARAFLAR +İşveren: Boğaziçi Yazılım Teknolojileri A.Ş., Vergi Dairesi: Kozyatağı V.D. VKN: 7767717474 +İşçi: Oktüremiş Sakarya, T.C. Kimlik No: 88682619840, Doğum Tarihi: 18.10.2002 +İşçinin adresi: Fenerbahçe Mahallesi, İnönü Caddesi No: 148 Kat: 3 Seyhan/Adana +İşçinin cep telefonu: 0 (533) 472 52 68, e-posta adresi: okturemis.sakarya@yandex.com.tr + +MADDE 2 - İşçi 19.03.2025 tarihinde işe başlamıştır. Brüt ücreti 593.749,00 TL olup ücret TR11 0001 0071 2698 0670 9863 51 numaralı hesaba yatırılır. +MADDE 3 - 4857 sayılı İş Kanunu'nun 22. maddesi saklıdır. Deneme süresi 2 aydır. + +İŞVEREN İŞÇİ +Yetkili: ELVİN ATASEVER Oktüremiş Sakarya diff --git a/samples/is_sozlesmesi_02.udf b/samples/is_sozlesmesi_02.udf new file mode 100644 index 0000000000000000000000000000000000000000..677949c46600535c100b32a938352f01afa679d8 Binary files /dev/null and b/samples/is_sozlesmesi_02.udf differ diff --git a/samples/is_sozlesmesi_03.docx b/samples/is_sozlesmesi_03.docx new file mode 100644 index 0000000000000000000000000000000000000000..2c19bf9ac185de91e400f5a9c5c2b6ed16f87ff4 Binary files /dev/null and b/samples/is_sozlesmesi_03.docx differ diff --git a/samples/is_sozlesmesi_03.pdf b/samples/is_sozlesmesi_03.pdf new file mode 100644 index 0000000000000000000000000000000000000000..79618af6c702a67512b1e72e73bb002dad817e99 Binary files /dev/null and b/samples/is_sozlesmesi_03.pdf differ diff --git a/samples/is_sozlesmesi_03.png b/samples/is_sozlesmesi_03.png new file mode 100644 index 0000000000000000000000000000000000000000..1e6cf8bcb9ae172bbe03425993b67be874582217 --- /dev/null +++ b/samples/is_sozlesmesi_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:530499d6497f26ba6aac3992a0c84c82780883fecbb5cefdb8eedaf4e607159e +size 156179