Files
ihaleist/src/services/sinirDeger.js
T
hikmettanandClaude Fable 5.1 91b8ea94b8 feat: sinir deger hesaplayici — Tebliğ md. 79/45 formulleri, 2026 R katsayilari
Formuller kaynagindan alindi (mevzuat.gov.tr Kamu Ihale Genel Tebligi,
kullanicinin Chrome'u uzerinden okundu; formuller Teblig'de gorsel oldugu icin
ekran goruntusunden):
- Md. 79.1.2 hizmet (personel dayali olmayan): SD = ((YM + ΣT)/(n+1)) × R,
  YM'nin %60 alti ve YM ustu teklifler haric
- Md. 79.1.1 personel calistirmaya dayali: SD = kar haric YM
- Md. 62/A.1 (RG 9/4/2026): mal aliminda 79 uygulanir
- Md. 45.1.1 yapim: Tort1/σ/Tort2, C = Tort2/YM, K parcali, SD = K×Tort2/(C×N);
  N 1,00 (B/C/D/E gruplari) veya 1,20 (45.1.1.1)
- R katsayilari: KIK Kurul karari 21.01.2026 (.doc'tan okundu): arac kiralama
  0,77, bilgisayar 0,87, yemek 0,77, muhendislik 0,72, sigorta 0,74, tasima
  0,78, diger 0,78

Dogrulama: Teblig'in kendi 45.2 ornegi (YM 100.000, N 1,20, 12 teklif) ile
birebir — Tort1 66.270,00, σ 20.317,05, Tort2 63.237,50, C 0,632, K 0,627,
SD 52.281,00. Yuvarlama kurallari ornekteki gibi (2/3 ondalik).

Arac: ust menude "Sinir Deger"; alim turu, YM, teklif listesi, R/N secimi,
"benim teklifim sinirin altinda mi" uyarisi, adim adim hesap.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 00:16:58 +03:00

106 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Aşırı düşük teklif sınır değeri — Kamu İhale Genel Tebliği'nden.
*
* KAYNAK (mevzuat.gov.tr, 9.5.13354, 2026-09-04'te okundu):
* - Md. 79.1.1 Personel çalıştırılmasına dayalı hizmet: SD = kâr hariç yaklaşık maliyet.
* - Md. 79.1.2 Dayalı olmayan hizmet: SD = ((YM + T1 + … + Tn) / (n + 1)) × R
* n: geçerli teklifler içinde YM'nin %60'ından düşük ve YM'den
* yüksek olanlar DIŞINDAKİ teklif sayısı. (Formül Tebliğ'de
* görsel; ekran görüntüsünden okundu.)
* - Md. 79.1.3 R her yıl 1 Şubat'tan geçerli Kurum'ca ilan edilir.
* - Md. 62/A.1 (Değişik RG 9/4/2026) Mal alımında aşırı düşük değerlendirmesi
* 79 uncu maddeye göre yapılır → mal alımı da bu formülü kullanır.
* - Md. 45.1.1 Yapım: YM'nin %120 üstü ve %40 altı hariç geçerli tekliflerin
* Tort1 ve σ'sı; [Tort1−σ, Tort1+σ] içindekilerin Tort2'si;
* C = Tort2/YM; K: C<0,60 → K=C; 0,60≤C≤1,00 → (3,2C−C²−0,6)/(C+1);
* C>1,00 → (C²−0,8C+1,4)/(C+1); SD = K×Tort2/(C×N).
* N (45.1.1.1): B/C/D/E grubu yapım işleri 1,00; diğer 1,20.
* Yuvarlama (45.2 örneği): Tort1, σ, Tort2 2 ondalık; C, K 3 ondalık.
*
* R KATSAYILARI 2026 (KİK Kurul kararı 21.01.2026, ilanı 1/2/2026+ ihaleler):
*/
export const R_2026 = [
{ ad: 'Araç Kiralama Hizmetleri', r: 0.77 },
{ ad: 'Bilgisayar Sistemlerine Yönelik Hizmetler', r: 0.87 },
{ ad: 'Malzemeli Yemek Hizmetleri', r: 0.77 },
{ ad: 'Mühendislik Hizmetleri', r: 0.72 },
{ ad: 'Sigorta Hizmetleri', r: 0.74 },
{ ad: 'Personel/Öğrenci Taşıma Hizmetleri', r: 0.78 },
{ ad: 'Diğer Hizmetler', r: 0.78 },
];
export const R_KAYNAK = 'KİK Kurul Kararı 21.01.2026 — ilanı 1/2/2026 ve sonrası ihaleler';
const yuvarla = (x, b) => Math.round((x + Number.EPSILON) * 10 ** b) / 10 ** b;
const sayi = (v) => {
if (typeof v === 'number') return v;
const s = String(v ?? '').trim().replace(/\s|TL|₺/gi, '');
if (!s) return NaN;
// "1.500.000,50" (TR) ya da "1500000.50"
const tr = s.includes(',') ? s.replace(/\./g, '').replace(',', '.') : s.replace(/\.(?=\d{3}(\D|$))/g, '');
return Number(tr);
};
export const teklifleriAyir = (metin) =>
String(metin || '').split(/[\n;]+/).map((x) => sayi(x)).filter((x) => Number.isFinite(x) && x > 0);
/** Hizmet (personel dayalı olmayan) ve mal alımı — Md. 79.1.2 */
export function sinirDegerHizmet({ ym, teklifler, r }) {
const YM = sayi(ym); const R = Number(r);
if (!(YM > 0) || !(R > 0)) return { hata: 'Yaklaşık maliyet ve R katsayısı gerekli' };
const gecerli = teklifler.filter((t) => t >= YM * 0.6 && t <= YM);
const disi = teklifler.filter((t) => t < YM * 0.6 || t > YM);
const n = gecerli.length;
const toplam = gecerli.reduce((a, b) => a + b, 0);
const ort = (YM + toplam) / (n + 1);
const sd = yuvarla(ort * R, 2);
return {
tur: 'hizmet', sd, n, r: R, ym: YM,
hesapOrtalama: yuvarla(ort, 2), dahil: gecerli, haric: disi,
adimlar: [
`Geçerli teklifler içinde YM'nin %60'ından düşük (${yuvarla(YM * 0.6, 2).toLocaleString('tr-TR')}) ve YM'den yüksek olanlar çıkarıldı: ${disi.length} teklif hariç, n = ${n}`,
`(YM + ΣT) / (n + 1) = (${YM.toLocaleString('tr-TR')} + ${toplam.toLocaleString('tr-TR')}) / ${n + 1} = ${yuvarla(ort, 2).toLocaleString('tr-TR')}`,
`SD = ${yuvarla(ort, 2).toLocaleString('tr-TR')} × R(${R}) = ${sd.toLocaleString('tr-TR')} TL`,
],
};
}
/** Personel çalıştırılmasına dayalı hizmet — Md. 79.1.1 */
export function sinirDegerPersonel({ karHaricYm }) {
const v = sayi(karHaricYm);
if (!(v > 0)) return { hata: 'Kâr hariç yaklaşık maliyet gerekli' };
return { tur: 'personel', sd: yuvarla(v, 2), adimlar: ['Md. 79.1.1: kâr hariç yaklaşık maliyet tutarı sınır değer kabul edilir.'] };
}
/** Yapım — Md. 45.1.1 */
export function sinirDegerYapim({ ym, teklifler, n: N = 1.2 }) {
const YM = sayi(ym); const Nk = Number(N);
if (!(YM > 0)) return { hata: 'Yaklaşık maliyet gerekli' };
const dahil = teklifler.filter((t) => t >= YM * 0.4 && t <= YM * 1.2);
const haric1 = teklifler.filter((t) => t < YM * 0.4 || t > YM * 1.2);
if (dahil.length < 2) return { hata: 'Aralıkta en az iki geçerli teklif olmalı' };
const tort1 = yuvarla(dahil.reduce((a, b) => a + b, 0) / dahil.length, 2);
const sigma = yuvarla(Math.sqrt(dahil.reduce((a, t) => a + (t - tort1) ** 2, 0) / (dahil.length - 1)), 2);
const alt = yuvarla(tort1 - sigma, 2), ust = yuvarla(tort1 + sigma, 2);
const bant = dahil.filter((t) => t >= alt && t <= ust);
const haric2 = dahil.filter((t) => t < alt || t > ust);
if (!bant.length) return { hata: 'Standart sapma aralığında teklif kalmadı' };
const tort2 = yuvarla(bant.reduce((a, b) => a + b, 0) / bant.length, 2);
const C = yuvarla(tort2 / YM, 3);
let K, kFormul;
if (C < 0.6) { K = C; kFormul = 'C < 0,60 → K = C'; }
else if (C <= 1.0) { K = yuvarla((3.2 * C - C * C - 0.6) / (C + 1), 3); kFormul = '0,60 ≤ C ≤ 1,00 → K = (3,2C − C² − 0,6)/(C + 1)'; }
else { K = yuvarla((C * C - 0.8 * C + 1.4) / (C + 1), 3); kFormul = 'C > 1,00 → K = (C² − 0,8C + 1,4)/(C + 1)'; }
const sd = yuvarla((K * tort2) / (C * Nk), 2);
const f = (x) => x.toLocaleString('tr-TR');
return {
tur: 'yapim', sd, ym: YM, n: Nk, tort1, sigma, alt, ust, tort2, C, K, dahil, bant, haric: [...haric1, ...haric2],
adimlar: [
`YM'nin %120 üstü (${f(YM * 1.2)}) ve %40 altı (${f(YM * 0.4)}) hariç: ${haric1.length} teklif dışarıda, ${dahil.length} teklif ortalamaya girdi`,
`Tort1 = ${f(tort1)} · σ = ${f(sigma)} · aralık [${f(alt)} , ${f(ust)}]`,
`Aralıkta kalan ${bant.length} teklif → Tort2 = ${f(tort2)} (${haric2.length} teklif aralık dışı)`,
`C = Tort2 / YM = ${C}`,
`${kFormul} → K = ${K}`,
`SD = (K × Tort2) / (C × N) = (${K} × ${f(tort2)}) / (${C} × ${Nk}) = ${f(sd)} TL`,
],
};
}