---
title: 'Elektronik Tablolarla Analiz'
source: 'https://academia.sh/tr/kurslar/elektronik-tablolar'
language: tr
updated: '2026-08-17T04:40:03+00:00'
license: 'CC BY-SA 4.0'
lessons: 11
outcomes:
    - 'Formül ve işlevlerle veri dönüştürür ve her dönüşümün sayıyı ne kadar oynattığını yazar'
    - 'Özet tablolarla çok boyutlu özet çıkarır ve kırılım kararının sıralamayı değiştirdiğini gösterir'
    - 'Elektronik tablonun sınırını kaç kararın geri okunabildiğini sayarak belirler'
---

# Elektronik Tablolarla Analiz

Bir hesabı ikinci kez yapan kişinin aynı sayıyı bulup bulamayacağının ölçülmesi: göreli ve mutlak başvurunun kopyalamada ürettiği iki sayı, sayma işlevinin boş hücre, sıfır ve metin olarak yazılmış sayıda üç farklı sonuç vermesi, tarife basamaklarında sınır karşılaştırmasının kaç kaydı yanlış basamağa atması, görünmeyen boşluk ve harf farkı yüzünden eşleşmeyen kayıt, gün sayma kararının iki farklı süre vermesi, sütun eklendiğinde kırılan arama, tekilleştirme ölçütünün kalan satır sayısını değiştirmesi, doldurma ve dışlama kararlarının aynı ortalamaya üç değer vermesi, kırılım değiştiğinde sıralamanın değişmesi, eksen başlangıcının farkı kaç kat büyük gösterdiği ve kaç kararın tablodan geri okunabildiği.

## Temel İşlemler

Hücre düzeyindeki her kararın sayıyı nasıl oynattığının ve tablodan geri okunup okunamadığının sayılması: göreli ve mutlak başvurunun kopyalamada ürettiği fark ve yanlış başvurunun kaç hücreye yayıldığı, sayma işlevinin boş hücre, sıfır ve metin olarak yazılmış sayıda üç ayrı sonucu, iç içe koşullarda sınır karşılaştırmasının yanlış basamağa attığı kayıt, görünmeyen boşluk ve harf farkının engellediği eşleşme, gün sayma kararının verdiği iki süre, sütun eklendiğinde kırılan dikey arama ile kırılmayan eşleşme-dizin ikilisi.

### Çalışma Sayfası Yapısı

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/calisma-sayfasi-yapisi

Tek bir tutar formülü 60 satıra kopyalanıyor ve başvuru kararı değişince toplam 13 427,16 ücret biriminden 3 354,98'e düşüyor: göreli başvuru 59 hücreye yanlış yayılıyor, bunların 52'si sıfır, 7'si bir tutar gibi okunan yanlış sayı. Gömülü sabitle yazılan üçüncü sürüm aynı toplamı veriyor ama birim ücret 8,4'ten 9,1'e çıktığında 60 hücre yerine 0 hücre peşinden geliyor. Sayfadaki 8 parametrenin 6'sı hiçbir formüle girmiyor.

Veri Analitiğine Giriş kursu şu soruyla kapandı: dokuz derste kararların sayıyı oynattığı
gösterildi, ama bütün kararlar elle alındı ve hiçbiri yazılı bir yerde durmuyor; aynı hesabı
ikinci kez yapan biri aynı sayıyı bulacak mı. Elektronik tablo bu soruya verilen ilk cevaptır.
Hesap artık bir kişinin kafasında değil, bir hücrede durur ve hücre yeniden hesaplanabilir.

Bu kurs cevabın ne kadarını tuttuğunu ölçer. Ölçü iki parçalıdır ve her derste iki sayı yan yana
durur. Birincisi önceki kurstan sürer: sayı ve onu üreten karar. İkincisi bu kursun kendi ölçüsüdür:
**kararın kurtarılabilirliği** — tabloya bakan ikinci kişi o kararı kaç adımda geri okuyabiliyor,
kaç karar hiç geri okunamıyor. Bu ders ikisini de bir tek formül üzerinde kurar.

- **TT1.** Çalışma sayfası burada bir **model**dir: hücreler bir eşleme, formüller kısa bir
  metin, aralıklar bir dilim işlevidir. Model bir uygulamayı taklit etmez; kopyalama kuralı da
  değerlendirme kuralı da aşağıda açıkça yazılıdır.
- **TT2.** Veri kümesi Veri Analitiğine Giriş kursunun **bölgesel ölçüm ağıdır**: abone kayıtları,
  sayaç okumaları, saha ziyaretleri, tarife basamakları. Ağ da kayıtlar da **kurgudur**; buradaki
  sayfa 60 abonelik bir saha listesidir ve kendi tohumuyla üretilir.
- **TT3.** Hücre adı sütun harfi ile satır numarasıdır. Bir başvuru **göreli** ise kopyalandığı
  kadar kayar, **mutlak** ise (`$` işaretiyle) kaymaz. Karma başvuruda yalnız işaretlenen parça
  sabit kalır.
- **TT4.** Boş hücre bu modelde sayısal bağlamda `0` okunur. Bu bir karardır ve sonucu doğrudan
  etkiler; sonraki ders boş hücrenin sayılıp sayılmaması sorusunu ayrıca ölçer.
- **TT5.** Bir karar **geri okunabilir** sayılır: ikinci kişi hücrenin formül metnini okuyarak
  kararın nerede tutulduğunu ve değerini bulabiliyorsa. Formülün içine gömülmüş sabit sayı geri
  okunabilir değildir — sayı görülür, ama nereden geldiği görünmez.
- **TT6.** Ücret bir **ücret birimidir**; ölçek modelin ölçeğidir.

#### Sayfa, Hücre, Aralık

Aşağıdaki blok modeli kurar. Üç parçası vardır: hücre eşlemesi, `kaydir` işlevinin uyguladığı
kopyalama kuralı ve `deger` işlevinin uyguladığı değerlendirme. Değerlendirme özyinelemelidir —
bir formül başka bir formüle başvurabilir.

```js
// tablo.mjs — elektronik tablo MODELI: hucreler bir esleme, formul bir metin, aralik bir
// dilim islevidir. Sayfa da kayitlar da kurgudur; hicbir uygulamanin davranisi taklit edilmez.
export const TOHUM = 20260602, SATIR = 60;
const uretec = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const BOLGE = ["kuzey", "guney", "dogu", "bati", "merkez"];

export const REF = /(\$?)([A-Z])(\$?)(\d+)/g;
const coz = (a) => ({ s: a.charCodeAt(a[0] === "$" ? 1 : 0) - 65, r: +a.replace(/\D/g, "") });
const adi = (s, r) => String.fromCharCode(65 + s) + r;
// Kopyalama kurali: gorel basvuru kopyalandigi kadar kayar, mutlak basvuru ($) kaymaz.
export const kaydir = (f, dS, dR) => f.replace(REF, (_, a, s, b, r) =>
  (a ? "$" + s : String.fromCharCode(s.charCodeAt(0) + dS)) + (b ? "$" + r : String(+r + dR)));

const ISLEV = { toplam: (d) => d.reduce((a, b) => a + b, 0) };
const ARALIK = /(\$?[A-Z]\$?\d+):(\$?[A-Z]\$?\d+)/g;
const ac = (a, b) => { const x = coz(a), y = coz(b), l = [];
  for (let s = x.s; s <= y.s; s++) for (let r = x.r; r <= y.r; r++) l.push(adi(s, r));
  return l; };

export function deger(H, ad) {
  const v = H[ad];
  if (v === undefined) return 0;                    // bos hucre bu modelde 0 okunur
  if (typeof v !== "string" || v[0] !== "=") return v;
  const ifade = v.slice(1)
    .replace(ARALIK, (_, a, b) => "[" + ac(a, b).map((c) => deger(H, c)).join(",") + "]")
    .replace(REF, (m) => String(deger(H, m.replace(/\$/g, ""))));
  return Function(...Object.keys(ISLEV), `return (${ifade});`)(...Object.values(ISLEV));
}

export function sayfa() {
  const r = uretec(TOHUM), H = {};
  H.A1 = "abone"; H.B1 = "bolge"; H.C1 = "tuketim"; H.E1 = "tutar";
  for (let i = 0; i < SATIR; i++) {
    const s = i + 2;
    H["A" + s] = "AB-" + String(1041 + i * 7).padStart(4, "0");
    H["B" + s] = BOLGE[Math.floor(r() * 5)];
    H["C" + s] = Math.round((2 + r() * r() * 78) * 10) / 10;
  }
  // Parametre blogu: solda etiket, sagda deger. Tarife basamaklari 10/25/40 m3'tur.
  H.G1 = "parametre"; H.H1 = "deger";
  ["birim ucret", "sabit bedel", "sayac bakim", "kayip payi", "basamak 1", "basamak 2",
    "basamak 3", "indirim"].forEach((e, j) => { H["G" + (j + 2)] = e; });
  [8.4, 25, 3.5, 1.2, 10, 25, 40, 0.9].forEach((v, j) => { H["H" + (j + 2)] = v; });
  return H;
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const H = sayfa();
  const dolu = Object.keys(H).length;
  console.log(`tohum ${TOHUM}: ${SATIR} abone satiri, ${dolu} dolu hucre`);
  console.log("parametre blogu H2:H9 = " + ac("H2", "H9").map((c) => H[c]).join(", ") + "\n");
  const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
    : String(v).padEnd(B[0]))).join("");
  const y = g([8, 10, 10]);
  console.log(y(["hucre", "abone", "tuketim"]));
  for (const s of [2, 3, 4, 60, 61]) console.log(y(["C" + s, H["A" + s], H["C" + s].toFixed(1)]));
  console.log(`\nC2:C61 toplami ${deger({ ...H, X1: "=toplam(C2:C61)" }, "X1").toFixed(1)} m3`);
  console.log("kaydir('=C2*$H$2+H3', 0, 1) -> " + kaydir("=C2*$H$2+H3", 0, 1));
}
```

```
tohum 20260602: 60 abone satiri, 202 dolu hucre
parametre blogu H2:H9 = 8.4, 25, 3.5, 1.2, 10, 25, 40, 0.9

hucre        abone   tuketim
C2         AB-1041       9.8
C3         AB-1048      71.6
C4         AB-1055       6.7
C60        AB-1447       3.0
C61        AB-1454      21.2

C2:C61 toplami 1419.9 m3
kaydir('=C2*$H$2+H3', 0, 1) -> =C3*$H$2+H4
```

Son satır dersin tamamını içeriyor. Aynı formül bir satır aşağı kopyalandığında `$H$2` yerinde
kaldı, `H3` ise `H4` oldu. Kopyalama bir metin işlemidir: hücrenin içindeki formül aynı kalmaz,
kaydırılarak yazılır. Hangi başvurunun kayacağına yazan kişi karar verir ve bu karar tek bir
işaretle verilir.

#### Aynı Hesap, Üç Başvuru Kararı

Hesap tektir: tutar, tüketimin birim ücretle çarpımı artı sabit bedel. Üç yazım da savunulabilir
görünür. Birincisi parametrelere mutlak başvurur, ikincisi göreli başvurur, üçüncüsü sayıları
formülün içine yazar.

```js
// kopyala.mjs — ayni hesap uc basvuru karariyla yazilip 60 satira kopyalanir.
import { sayfa, deger, kaydir, SATIR } from "./tablo.mjs";

const KARAR = [
  { ad: "A: mutlak ($H$2, $H$3)", f: "=C2*$H$2+$H$3" },
  { ad: "B: gorel (H2, H3)", f: "=C2*H2+H3" },
  { ad: "C: sabit gomulu", f: "=C2*8.4+25" },
];
// Bir formul E2'ye yazilip E61'e kadar kopyalanir: her kopyada satir farki kadar kaydirilir.
const doldur = (H, f) => {
  const K = { ...H };
  for (let i = 0; i < SATIR; i++) K["E" + (i + 2)] = kaydir(f, 0, i);
  return K;
};
const sutun = (K) => Array.from({ length: SATIR }, (_, i) => deger(K, "E" + (i + 2)));
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const H = sayfa();
const D = KARAR.map((k) => sutun(doldur(H, k.f)));
const y = g([8, 10, 12, 12, 12]);
console.log(y(["hucre", "tuketim", "A tutar", "B tutar", "C tutar"]));
for (const s of [2, 3, 5, 9, 10, 30, 61]) {
  const i = s - 2;
  console.log(y(["E" + s, H["C" + s].toFixed(1), D[0][i].toFixed(2), D[1][i].toFixed(2),
    D[2][i].toFixed(2)]));
}

const t = (d) => d.reduce((a, b) => a + b, 0);
// Kayan noktali karsilastirma esikle yapilir: tutarlar kurus altinda ayirt edilmez,
// bu yuzden iki deger 0,005 ucret biriminden yakinsa "ayni" sayilir.
const ESIK = 0.005;
const ayni = (a, b) => Math.abs(a - b) < ESIK;
const z = g([26, 12, 8, 10, 8]);
console.log("\n" + z(["karar", "toplam", "yanlis", "sifir", "gorunur"]));
KARAR.forEach((k, a) => {
  const yanlis = D[a].filter((v, i) => !ayni(v, D[0][i])).length;
  const sifir = D[a].filter((v) => ayni(v, 0)).length;
  console.log(z([k.ad, t(D[a]).toFixed(2), yanlis, sifir, yanlis - sifir]));
});
console.log(`\nE2'ye tek formul yazildi, ${SATIR - 1} hucreye kopyalandi.`);
```

```
hucre      tuketim     A tutar     B tutar     C tutar
E2             9.8      107.32      107.32      107.32
E3            71.6      626.44     1793.50      626.44
E5            26.8      250.12       42.16      250.12
E9             5.5       71.20        4.95       71.20
E10           49.1      437.44        0.00      437.44
E30           25.4      238.36        0.00      238.36
E61           21.2      203.08        0.00      203.08

karar                           toplam  yanlis     sifir gorunur
A: mutlak ($H$2, $H$3)        13427.16       0         0       0
B: gorel (H2, H3)              3354.98      59        52       7
C: sabit gomulu               13427.16       0         0       0

E2'ye tek formul yazildi, 59 hucreye kopyalandi.
```

Rapor edilecek sayı 13 427,16 ücret birimidir. B kararı aynı hesaptan 3 354,98 üretti; fark
10 072,18, yani doğru toplamın dörtte üçü. Tek işaret farkı bu kadar oynattı.

Yayılım sütunları daha önemli bir şey yazıyor. Yazılan formül **tek**tir; yanlış olan da tek bir
hücredir. Ama kopyalama o hücreyi 59 hücreye çoğalttı ve 59'unun hepsi yanlış değer üretti. Bunun
52'si sıfır — göze çarpar. Kalan 7'si sıfır değil: `E5` hücresinde 250,12 yerine 42,16 yazıyor,
çünkü kayan başvuru tüketimi birim ücretle değil kayıp payıyla çarptı ve üstüne sabit bedel yerine
birinci tarife basamağının sınırını ekledi. 42,16 düşük tüketimli
bir abonenin faturası gibi okunur ve komşu satırlardan ayırt edilmez. **Bir hatanın görünürlüğü
büyüklüğüyle değil, benzediği şeyle ölçülür.**

C kararı A ile aynı toplamı verdi: 13 427,16. İki sütun hücre hücre eşit. Bir sonuç
karşılaştırması bu iki yazımı ayıramaz.

#### Parametre Değişince Kim Peşinden Gelir

Ayıran şey sonuç değil, sayfanın değişime nasıl tepki verdiğidir. Birim ücret 8,4'ten 9,1'e
çıkarılır ve tek bir hücre güncellenir.

```js
// karar.mjs — parametre degisince kac hucre pesinden gelir, ve sayfadan kac karar geri okunur.
import { sayfa, deger, kaydir, REF, SATIR } from "./tablo.mjs";

const FORMUL = { A: "=C2*$H$2+$H$3", B: "=C2*H2+H3", C: "=C2*8.4+25" };
const doldur = (H, f) => { const K = { ...H };
  for (let i = 0; i < SATIR; i++) K["E" + (i + 2)] = kaydir(f, 0, i);
  K.E62 = "=toplam(E2:E61)"; return K; };
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const H = sayfa(), ZAM = { ...H, H2: 9.1 };   // birim ucret 8,4 -> 9,1
const y = g([6, 12, 12, 10, 10]);
console.log(y(["karar", "toplam", "zam sonrasi", "fark", "pesinden"]));
for (const [ad, f] of Object.entries(FORMUL)) {
  const K = doldur(H, f), Z = doldur(ZAM, f);
  const once = Array.from({ length: SATIR }, (_, i) => deger(K, "E" + (i + 2)));
  const sonra = Array.from({ length: SATIR }, (_, i) => deger(Z, "E" + (i + 2)));
  // Esik 0,005 ucret birimidir: bunun altindaki fark yuvarlamayla ayirt edilemez.
  const oynayan = once.filter((v, i) => Math.abs(v - sonra[i]) >= 0.005).length;
  console.log(y([ad, deger(K, "E62").toFixed(2), deger(Z, "E62").toFixed(2),
    (deger(Z, "E62") - deger(K, "E62")).toFixed(2), oynayan]));
}

// Sayfa taramasi: her formul metninden basvurular ve gomulu sabitler cikarilir.
const tara = (K) => {
  const formuller = Object.entries(K).filter(([, v]) => typeof v === "string" && v[0] === "=");
  const basvurulan = new Set(), sabit = new Map();
  for (const [, v] of formuller) {
    for (const m of v.matchAll(REF)) basvurulan.add(m[0].replace(/\$/g, ""));
    for (const s of v.replace(REF, "").match(/\d+(\.\d+)?/g) ?? [])
      sabit.set(s, (sabit.get(s) ?? 0) + 1);
  }
  const param = Array.from({ length: 8 }, (_, j) => "H" + (j + 2));
  return { formul: formuller.length, kullanilan: param.filter((c) => basvurulan.has(c)),
    olu: param.filter((c) => !basvurulan.has(c)), sabit };
};
const z = g([6, 8, 28, 5, 16]);
console.log("\n" + z(["karar", "formul", "kullanilan", "olu", "gomulu sabit"]));
for (const [ad, f] of Object.entries(FORMUL)) {
  const t = tara(doldur(H, f));
  console.log(z([ad, t.formul, t.kullanilan.join(","), t.olu.length,
    [...t.sabit].map(([s, n]) => `${s}x${n}`).join(" ")]));
}
const t = tara(doldur(H, FORMUL.A));
console.log(`\nParametre blogunda 8 deger var, formuller ${t.kullanilan.length} tanesine ` +
  `basvuruyor; ${t.olu.length} tanesi (${t.olu.join(", ")}) sayfada duruyor ama hicbir ` +
  `hesaba girmiyor.`);
```

```
karar       toplam zam sonrasi      fark  pesinden
A         13427.16    14421.09    993.93        60
B          3354.98     3361.84      6.86         1
C         13427.16    13427.16      0.00         0

karar   formul                  kullanilan  olu    gomulu sabit
A           61                       H2,H3    6                
B           61     H2,H3,H4,H5,H6,H7,H8,H9    0                
C           61                                8    8.4x60 25x60

Parametre blogunda 8 deger var, formuller 2 tanesine basvuruyor; 6 tanesi (H4, H5, H6, H7, H8, H9) sayfada duruyor ama hicbir hesaba girmiyor.
```

Bu dersin ikinci sayısı son sütundadır. A kararında bir hücrenin değişmesi **60** hücreyi
oynattı; C kararında **0** hücreyi. C sayfası zam sonrası da 13 427,16 gösteriyor ve bu sayı artık
yanlıştır — ama sayfada hiçbir işaret yoktur, çünkü hiçbir şey değişmemiştir.

Tarama sütunları farkın nerede olduğunu yazıyor. A'da iki karar formül metninden geri okunur:
`$H$2` yazan biri `H2` hücresine gider, yanındaki `G2` etiketini okur ve birim ücreti bulur. **İki
adım.** C'de aynı karar 60 ayrı hücrede 8,4 olarak tekrar eder; bir sayı görülür, kaynağı
görülmez. Birim ücret değişecekse 60 formülün hepsi tek tek okunmalıdır ve hangi 8,4'ün birim
ücret, hangisinin başka bir şey olduğu ancak tahmin edilebilir. **Geri okuma adımı yoktur.**

Son satır üçüncü bir şey söylüyor. Sayfada 8 parametre var, formüller yalnız 2'sine başvuruyor;
tarife basamakları dahil 6 parametre hiçbir hesaba girmiyor. Bunlar sayfada bir zamanlar alınmış
kararların kalıntısıdır. Bakan kişi hepsini kullanımda sanır. **Sayfada duran bir sayı, kullanılan
bir sayı değildir.**

#### Kararın İki Ölçüsü

Bu ders iki sayı üretti ve ikisi de raporlanmadan tablo teslim edilemez. Birincisi tutar
toplamıdır: 13 427,16 ücret birimi, başvuru kararı değişince 3 354,98. İkincisi kurtarılabilirliktir:
A kararında zam 60 hücreyi buldu, C kararında 0 hücreyi; A'da parametre kararı iki adımda geri
okunuyor, C'de hiç okunamıyor.

İkinci sayı olmadan birincisi eksiktir, çünkü aynı 13 427,16 iki farklı sayfadan çıktı ve o iki
sayfa aynı sayfa değil. Kursun kuralı buradan başlar: **elektronik tablo bir hesap aracıdır, bir
kayıt aracı değildir; hücreye gömülü karar, yazılmamış karardır.**

#### Özet

- Çalışma sayfası bir hücre eşlemesidir; formül bir metindir ve kopyalandığında olduğu gibi değil,
  kaydırılarak yazılır. Göreli başvuru kayar, mutlak başvuru kaymaz.
- Aynı hesap üç başvuru kararıyla yazıldığında toplam 13 427,16 ile 3 354,98 arasında oynadı;
  fark, doğru toplamın dörtte üçüdür.
- Tek bir yanlış başvuru kopyalanarak 59 hücreye yayıldı; 52'si sıfır verdiği için göze çarpar,
  7'si bir tutar gibi okunur. Bir hatanın görünürlüğü büyüklüğüyle değil, benzediği şeyle ölçülür.
- Sonuç karşılaştırması iki yazımı ayırmaz: gömülü sabitle yazılan sayfa aynı toplamı verir, ama
  birim ücret değiştiğinde 60 hücre yerine 0 hücre peşinden gelir.
- Kararın kurtarılabilirliği adım sayısıyla ölçülür: mutlak başvuruda iki adım, gömülü sabitte
  hiç. Sayfadaki 8 parametrenin 6'sı hiçbir formüle girmiyor ve bakan kişi bunu göremiyor.

#### Sonraki Adım

Bu ders bir hesabın hangi hücrelere yayıldığını saydı, ama hesabın hangi hücreleri **saydığını**
sormadı. `toplam(E2:E61)` yazıldığında aralıkta 60 hücre var; sayılan kaç tanesi. Sayaç
okumalarının bir bölümü boştur, bir bölümü sıfırdır, bir bölümü sayı gibi görünen bir metindir ve
üçü hücre içinde birbirine benzer. Sonraki ders aynı sütuna üç sayma işlevi uygular ve üç farklı
sonuç basar; ardından aynı sütunun ortalamasının paydasının hangi karara bağlı olduğunu ölçer.

### Temel İşlevler

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/temel-islevler

60 hücrelik tek bir aralığa dört sayma işlevi uygulanıyor ve dört farklı sayı çıkıyor: 53 dolu hücre, 39 sayı, 29 sıfırdan büyük değer, 7 boş hücre. Aynı sütunun toplamı 708,5 m³; metin olarak yazılmış 14 sayı sessizce atlandığı için gerçek toplam 967,2 m³ ve fark yüzde 26,7. Ortalama paydası dört savunulabilir kararla 11,81 ile 24,43 arasında oynuyor, yani 2,07 kat; altı formülün 1'inde payda hiç görünmüyor, 3'ünde karar gömülü bir sabit, yalnız 1'inde parametre hücresine başvuru var.

Önceki ders bir hesabın hangi hücrelere yayıldığını saydı, ama hesabın hangi hücreleri
**saydığını** sormadı. `toplam(E2:E61)` yazıldığında aralıkta 60 hücre vardır; toplanan kaç
tanesidir. Aralığın boyu bir sayıdır, aralıkta sayılan hücre başka bir sayıdır ve ikisi
birbirine eşit değildir.

Bu ders aynı sayfaya bir sütun ekler: sayaç okuması. Okuma sütununda üç ayrı "hiçbir şey" yan
yana durur. Dört sayma işlevi aynı aralığa uygulanınca dört farklı sayı çıkar, ardından o
sayılardan biri bir ortalamanın paydası olur ve rapor edilen değer iki kattan fazla oynar.

- **TT7.** Aralık işlevleri hücrenin **ham** içeriğini görür: boş hücre `null`, metin metin
  kalır. Sayısal bağlamda boş hücrenin `0` okunması (TT4) sürer; iki kural aynı modelde birlikte
  yaşar ve aşağıdaki kodda ayrı işlevlerle yazılıdır.
- **TT8.** Okuma sütununun üç boşluk türü: **boş hücre** — okuma alınamadı; **sıfır** — sayaç
  dönmemiş, yani gerçek bir ölçüm; **metin olarak yazılmış sayı** — dışa aktarımdan ondalık
  ayırıcısı virgülle gelmiş bir dize. Sütun kurgudur ve kendi tohumuyla üretilir.
- **TT9.** Ölçüt bir **metindir** (`">0"` gibi) ve modelde `olc` işlevi uygular. Bir hücrenin
  sayı mı metin mi olduğu **türüyle** belirlenir, görünüşüyle değil.

#### Aralıkta Üç Ayrı Hiçbir Şey

Aşağıdaki blok önceki dersin modelini genişletir. Sayfa üretici aynıdır ve aynı sırada
çağrılır, bu yüzden tüketim sütunu değişmez. Okuma sütunu ayrı bir diziyle üretilir.

```js
// sayim.mjs — elektronik tablo MODELI: 01'deki model, aralik islevleriyle genisletilmis.
// Aralik islevi hucrenin HAM icerigini gorur: bos hucre null, metin metin kalir.
export const TOHUM = 20260602, SATIR = 60;
const uretec = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };

const REF = /(\$?)([A-Z])(\$?)(\d+)/g;
const coz = (a) => ({ s: a.charCodeAt(a[0] === "$" ? 1 : 0) - 65, r: +a.replace(/\D/g, "") });
const adi = (s, r) => String.fromCharCode(65 + s) + r;
const ARALIK = /(\$?[A-Z]\$?\d+):(\$?[A-Z]\$?\d+)/g;
const ac = (a, b) => { const x = coz(a), y = coz(b), l = [];
  for (let s = x.s; s <= y.s; s++) for (let r = x.r; r <= y.r; r++) l.push(adi(s, r));
  return l; };

// Olcut metni: ">0" gibi bir esik ya da duz bir deger. Metin hucre metin olarak kalir.
const olc = (o, v) => { const m = /^([<>]=?|=)?(.*)$/.exec(o), s = +m[2];
  if (typeof v !== "number") return m[1] === undefined && v === m[2];
  return m[1] === ">" ? v > s : m[1] === "<" ? v < s : m[1] === ">=" ? v >= s
    : m[1] === "<=" ? v <= s : v === s; };

const say1 = (d) => d.filter((v) => v !== null && v !== "").length;
const sayi = (d) => d.filter((v) => typeof v === "number");
const ISLEV = {
  toplam: (d) => sayi(d).reduce((a, b) => a + b, 0),   // metin ve bos hucre atlanir
  say: say1,                                            // dolu hucre: metin de sayilir
  sayisay: (d) => sayi(d).length,                       // yalniz sayi turunde hucre
  bossay: (d) => d.length - say1(d),
  ortalama: (d) => sayi(d).reduce((a, b) => a + b, 0) / sayi(d).length,
  kosullusay: (d, o) => d.filter((v) => olc(o, v)).length,
  kosullutoplam: (d, o, t) => t.filter((_, i) => olc(o, d[i]))
    .filter((v) => typeof v === "number").reduce((a, b) => a + b, 0),
};

export function ham(H, ad) {
  const v = H[ad];
  if (v === undefined) return null;                   // bos hucre aralikta null gorunur
  return typeof v === "string" && v[0] === "=" ? deger(H, ad) : v;
}
export function deger(H, ad) {
  const v = H[ad];
  if (v === undefined) return 0;                      // sayisal baglamda bos hucre 0 (TT4)
  if (typeof v !== "string" || v[0] !== "=") return v;
  const ifade = v.slice(1)
    .replace(ARALIK, (_, a, b) => "[" + ac(a, b).map((c) => JSON.stringify(ham(H, c))).join(",") + "]")
    .replace(REF, (m) => JSON.stringify(deger(H, m.replace(/\$/g, ""))));
  return Function(...Object.keys(ISLEV), `return (${ifade});`)(...Object.values(ISLEV));
}

// Sayfa 01'deki sayfadir: A/B/C sutunlari ayni tohumla ayni sirada uretilir, bu yuzden
// tuketim degerleri (C2:C61 toplami 1419,9 m3) degismez. Okuma sutunu D ayri bir dizidir.
export function sayfa() {
  const r = uretec(TOHUM), H = {};
  H.A1 = "abone"; H.C1 = "tuketim"; H.D1 = "okuma";
  for (let i = 0; i < SATIR; i++) {
    const s = i + 2;
    H["A" + s] = "AB-" + String(1041 + i * 7).padStart(4, "0");
    r();                                    // 01'deki bolge sutunu: uretec ayni sirada calisir
    H["C" + s] = Math.round((2 + r() * r() * 78) * 10) / 10;
  }
  // Okuma sutunu: uc ayri "hicbir sey" bir arada durur.
  const o = uretec(TOHUM + 1);
  for (let i = 0; i < SATIR; i++) {
    const s = i + 2, t = H["C" + s], p = o();
    if (p < 0.14) continue;                            // bos hucre: okuma alinamadi
    if (p < 0.27) H["D" + s] = 0;                      // sayac donmemis: gercek sifir
    else if (p < 0.44) H["D" + s] = String(t).replace(".", ",");  // metin olarak yazilmis sayi
    else H["D" + s] = t;
  }
  // Parametre blogu 01'deki blogun aynisi: tarife basamaklari H6:H8'dedir.
  ["birim ucret", "sabit bedel", "sayac bakim", "kayip payi", "basamak 1", "basamak 2",
    "basamak 3", "indirim"].forEach((e, j) => { H["G" + (j + 2)] = e; });
  [8.4, 25, 3.5, 1.2, 10, 25, 40, 0.9].forEach((v, j) => { H["H" + (j + 2)] = v; });
  return H;
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const H = sayfa();
  const tur = (v) => v === undefined ? "bos" : typeof v === "number" ? "sayi" : "metin";
  const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
    : String(v).padEnd(B[0]))).join("");
  const y = g([8, 10, 10, 8]);
  console.log(y(["hucre", "tuketim", "okuma", "tur"]));
  for (const s of [2, 3, 5, 9, 61]) console.log(y(["D" + s, H["C" + s].toFixed(1),
    H["D" + s] === undefined ? "" : H["D" + s], tur(H["D" + s])]));
  const d = Array.from({ length: SATIR }, (_, i) => H["D" + (i + 2)]);
  const n = (f) => d.filter(f).length;
  console.log(`\nD2:D61 aralik boyu ${SATIR}; bos ${n((v) => v === undefined)}, ` +
    `sifir ${n((v) => v === 0)}, metin ${n((v) => typeof v === "string")}, ` +
    `sifirdan buyuk sayi ${n((v) => typeof v === "number" && v > 0)}`);
}
```

```
hucre      tuketim     okuma     tur
D2             9.8       9,8   metin
D3            71.6         0    sayi
D5            26.8      26.8    sayi
D9             5.5               bos
D61           21.2      21.2    sayi

D2:D61 aralik boyu 60; bos 7, sifir 10, metin 14, sifirdan buyuk sayi 29
```

Son sütun modelin verdiği bir kolaylıktır; gerçek bir sayfada tür yazmaz. `D2` hücresinde
`9,8`, `D5` hücresinde `26.8` görünür ve ikisi de bir okuma gibi okunur. `D9` ile `D3`
arasındaki fark daha incedir: biri boş, öteki sıfırdır. Oysa `D3` bir ölçümdür — sayaç
dönmemiştir — `D9` bir ölçümün yokluğudur.

#### Aynı Aralık, Dört Sayma İşlevi

"Bu dönem kaç okuma var" sorusunun tek cevabı yoktur. Aşağıdaki blok aynı aralığı dört ayrı
sayma işlevine verir, ardından metin hücrelerin toplama ne yaptığını ölçer.

```js
// islev.mjs — ayni aralik uc sayma islevine verilir, uc farkli sonuc doner.
import { sayfa, deger, SATIR } from "./sayim.mjs";

const H = sayfa();
const FORMUL = [
  ["=say(D2:D61)", "dolu hucre (metin dahil)"],
  ["=sayisay(D2:D61)", "sayi turunde hucre"],
  ["=kosullusay(D2:D61,\">0\")", "sifirdan buyuk sayi"],
  ["=bossay(D2:D61)", "bos hucre"],
];
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");
const y = g([28, 8, 28]);
console.log(`D2:D61 araliginda ${SATIR} hucre var.\n`);
console.log(y(["formul", "sonuc", "ne saydi"]));
for (const [f, ne] of FORMUL) console.log(y([f, deger({ ...H, X1: f }, "X1"), ne]));

// Metin olarak yazilmis sayilar toplama girmiyor. Ayni sutun sayiya cevrilirse ne olurdu.
const T = { ...H };
for (let i = 0; i < SATIR; i++) { const c = "D" + (i + 2), v = T[c];
  if (typeof v === "string") T[c] = +v.replace(",", "."); }
const ham = deger({ ...H, X1: "=toplam(D2:D61)" }, "X1");
const cev = deger({ ...T, X1: "=toplam(D2:D61)" }, "X1");
console.log(`\ntoplam(D2:D61) ham sutunda ${ham.toFixed(1)} m3, metinler sayiya ` +
  `cevrilince ${cev.toFixed(1)} m3; fark ${(cev - ham).toFixed(1)} m3 ` +
  `(${(100 * (cev - ham) / cev).toFixed(1)} yuzde).`);
console.log(`01'de C2:C61 toplami 1419,9 m3 idi; ayni satirlarin okuma sutunu ` +
  `${ham.toFixed(1)} m3 gosteriyor.`);
```

```
D2:D61 araliginda 60 hucre var.

formul                         sonuc                    ne saydi
=say(D2:D61)                      53    dolu hucre (metin dahil)
=sayisay(D2:D61)                  39          sayi turunde hucre
=kosullusay(D2:D61,">0")          29         sifirdan buyuk sayi
=bossay(D2:D61)                    7                   bos hucre

toplam(D2:D61) ham sutunda 708.5 m3, metinler sayiya cevrilince 967.2 m3; fark 258.7 m3 (26.7 yuzde).
01'de C2:C61 toplami 1419,9 m3 idi; ayni satirlarin okuma sutunu 708.5 m3 gosteriyor.
```

Dördü de doğrudur ve dördü farklı bir soruyu cevaplar. 53, bir şey yazılmış hücrelerin
sayısıdır. 39, sayı türünde olanların sayısıdır. 29, sıfırdan büyük bir okuma taşıyanların
sayısıdır. 7, hiç okunmamış aboneleri verir. Rapora "bu dönem okuma sayısı" diye girecek değer
bu dördünden biridir ve hangisi olduğu **sorunun tanımına** bağlıdır, işlevin adına değil.

Toplam satırı daha sessiz bir şey yapıyor. Toplama işlevi metin hücreleri atlar ve bunu
söylemez; hata vermez, uyarı üretmez, yalnız 14 değeri hesaba katmaz. Sütunun gerçek toplamı
967,2 m³ iken sayfada 708,5 m³ yazar. Fark 258,7 m³, yani gerçek toplamın yüzde 26,7'si.
Bir hata gösterilmiş olsaydı görülürdü; **atlama görülmez.**

Önceki ders aynı satırların tüketim toplamını 1419,9 m³ bulmuştu. Okuma sütunu dolu olduğu her
satırda tüketimle **aynı** değeri taşır; yani 708,5 ile 1419,9 arasındaki fark bir ölçüm farkı
değil, bir **sayma** farkıdır ve bunu söyleyen bir hücre yoktur.

#### Ortalamanın Paydası Nerede Duruyor

Toplam bir karara bağlıydı; ortalama iki karara bağlıdır, çünkü paydası ayrıca seçilir.
Aşağıdaki blok aynı payı dört paydayla böler, ardından bir eşiğin iki yazımını karşılaştırır ve
altı formülü kararın nerede durduğuna göre tarar.

```js
// payda.mjs — ayni ortalama dort paydayla; ve olcut/payda kararlarinin geri okunabilirligi.
import { sayfa, deger } from "./sayim.mjs";

const H = sayfa();
const oku = (f) => deger({ ...H, X1: f }, "X1");
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const ORT = [
  ["=ortalama(D2:D61)", "sayi turunde hucre"],
  ["=toplam(D2:D61)/say(D2:D61)", "dolu hucre"],
  ["=toplam(D2:D61)/kosullusay(D2:D61,\">0\")", "sifirdan buyuk"],
  ["=toplam(D2:D61)/60", "aralik boyu"],
];
const y = g([42, 10, 8, 20]), pay = oku("=toplam(D2:D61)");
console.log(y(["formul", "ortalama", "payda", "payda nedir"]));
for (const [f, ne] of ORT) console.log(y([f, oku(f).toFixed(2), Math.round(pay / oku(f)), ne]));
const v = ORT.map(([f]) => oku(f));
console.log(`\nEn kucuk ${Math.min(...v).toFixed(2)}, en buyuk ${Math.max(...v).toFixed(2)} m3; ` +
  `oran ${(Math.max(...v) / Math.min(...v)).toFixed(2)} kat.`);

// Ayni kosullu toplam iki yazimla: esik olcut metnine gomulu, ya da parametre hucresinden.
const A = "=kosullutoplam(C2:C61,\">25\",C2:C61)";
const B = "=kosullutoplam(C2:C61,\">\"+H7,C2:C61)";
const Z = { ...H, H7: 30 };                             // ikinci basamak 25 -> 30 m3
const z = g([44, 12, 12, 10]);
console.log("\n" + z(["formul", "toplam", "esik 30", "pesinden"]));
for (const f of [A, B]) {
  const once = deger({ ...H, X1: f }, "X1"), sonra = deger({ ...Z, X1: f }, "X1");
  // Esik 0,05 m3'tur: okumalar bir ondalikli tutuluyor, altindaki fark ayirt edilemez.
  console.log(z([f, once.toFixed(1), sonra.toFixed(1), Math.abs(once - sonra) >= 0.05 ? 1 : 0]));
}

// Tarama: her formul metni aralik adlari disindaki sabitler ve basvurular icin okunur.
const TARA = [...ORT.map(([f]) => f), A, B];
const s = g([44, 8, 8, 8]);
console.log("\n" + s(["formul", "gizli", "sabit", "basvuru"]));
const N = { gizli: 0, sabit: 0, basvuru: 0 };
for (const f of TARA) {
  const kalan = f.replace(/[A-Z]\d+:[A-Z]\d+/g, "");        // aralik adlari sayilmaz
  const gizli = f.includes("=ortalama(");                   // payda formul metninde yok
  const basvuru = (kalan.match(/[A-Z]\d+/g) ?? []).length;
  const sabit = (kalan.replace(/[A-Z]\d+/g, "").match(/\d+(\.\d+)?/g) ?? []).length;
  N.gizli += +gizli; N.sabit += +!!sabit; N.basvuru += +!!basvuru;
  console.log(s([f, gizli ? "evet" : "hayir", sabit, basvuru]));
}
console.log(`\n${TARA.length} formulun ${N.gizli} tanesinde payda hic gorunmuyor, ${N.sabit} ` +
  `tanesinde karar gomulu bir sabit, ${N.basvuru} tanesinde parametre hucresine basvuru, ` +
  `${TARA.length - N.gizli - N.sabit - N.basvuru} tanesinde bir islev adi.`);
```

```
formul                                      ortalama   payda         payda nedir
=ortalama(D2:D61)                              18.17      39  sayi turunde hucre
=toplam(D2:D61)/say(D2:D61)                    13.37      53          dolu hucre
=toplam(D2:D61)/kosullusay(D2:D61,">0")        24.43      29      sifirdan buyuk
=toplam(D2:D61)/60                             11.81      60         aralik boyu

En kucuk 11.81, en buyuk 24.43 m3; oran 2.07 kat.

formul                                            toplam     esik 30  pesinden
=kosullutoplam(C2:C61,">25",C2:C61)               1010.6      1010.6         0
=kosullutoplam(C2:C61,">"+H7,C2:C61)              1010.6       846.3         1

formul                                         gizli   sabit basvuru
=ortalama(D2:D61)                               evet       0       0
=toplam(D2:D61)/say(D2:D61)                    hayir       0       0
=toplam(D2:D61)/kosullusay(D2:D61,">0")        hayir       1       0
=toplam(D2:D61)/60                             hayir       1       0
=kosullutoplam(C2:C61,">25",C2:C61)            hayir       1       0
=kosullutoplam(C2:C61,">"+H7,C2:C61)           hayir       0       1

6 formulun 1 tanesinde payda hic gorunmuyor, 3 tanesinde karar gomulu bir sabit, 1 tanesinde parametre hucresine basvuru, 1 tanesinde bir islev adi.
```

Ortalama okuma 11,81 m³ ile 24,43 m³ arasında oynuyor: 2,07 kat. Pay hiç değişmedi, yalnız
payda seçildi ve dört değerin hepsi savunulabilir. Rapora girecek olan, kimin ortalamasının
sorulduğuna bağlıdır: okunan abonelerin mi, kayıtlı bütün abonelerin mi.

İkinci sayı üçüncü tabloda. Ortalama işlevi paydayı formül metnine hiç yazmıyor: sayfayı açan
biri 18,17 değerini ve işlevin adını görür, 39 sayısını göremez. Bu karar **geri okunamaz**.
Aynı sayıyı `toplam(D2:D61)/sayisay(D2:D61)` biçiminde yazmak sonucu değiştirmez, ama paydayı
formül metnine taşır. **Aynı sayıyı üreten iki yazımdan biri kararı saklar, öteki yazar.**

Koşullu toplam tablosu aynı ayrımı eşik üzerinde tekrar ediyor. İki formül de 1010,6 m³
veriyor; ölçüt metnine gömülü `">25"` ile parametre hücresine başvuran `">"+H7` sonuçtan ayırt
edilemez. İkinci basamak 30 m³'e çıkarıldığında birincisi 1010,6'da kalıyor, ikincisi 846,3'e
iniyor.

Tarama altı formülü dört öbeğe ayırdı: 1'inde karar hiç görünmüyor, 3'ünde gömülü bir sabit,
1'inde parametre hücresine başvuru, 1'inde karar bir **işlev adının kendisi**. Sonuncusu en ucuz
kurtarılabilirlik biçimidir: `sayisay` yazan formül ne saydığını adıyla söyler.

#### Özet

- Bir aralığın boyu ile sayılan hücre sayısı ayrı şeylerdir: 60 hücrelik aralıkta dört sayma
  işlevi 53, 39, 29 ve 7 verdi; dördü de doğru, dördü farklı soruya cevap.
- Boş hücre, sıfır ve metin olarak yazılmış sayı hücrede birbirine benzer ama üçü ayrı şeydir;
  sıfır bir ölçümdür, boş hücre bir ölçümün yokluğudur.
- Toplama işlevi metin hücreleri atlar ve bunu söylemez: sütun 708,5 m³ gösterirken gerçek
  toplam 967,2 m³, fark yüzde 26,7. Hata görülür, atlama görülmez.
- Aynı payın dört paydası ortalamayı 11,81 ile 24,43 arasında oynattı: 2,07 kat. Ortalamanın
  paydası hesabın değil, tanımın parçasıdır.
- Kurtarılabilirlik yazım biçimine bağlıdır: ortalama işlevi paydayı hiç yazmaz, gömülü eşik
  kaynağını yazmaz, işlev adı kararı kendi adıyla söyler. Altı formülün yalnız 1'i parametre
  hücresine başvuruyor.

#### Sonraki Adım

Bu ders bir aralığın kaç hücresinin sayıldığını ölçtü, ama hep tek bir eşikle: `">0"` ya da
`">25"`. Tarife tek eşikli değildir. Bir kaydın hangi basamağa düştüğü 10, 25 ve 40 m³
sınırlarının üçüne birden bakan iç içe bir koşulla belirlenir ve her katmanda bir karşılaştırma
işareti seçilir. Sonraki ders aynı 60 kaydı iki kez basamaklara dağıtır — bir kez `<` ile, bir
kez `<=` ile — ve sınıra tam oturan kayıtların kaçının basamak değiştirdiğini sayar.

### Mantıksal İşlevler

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/mantiksal-islevler

Aynı iç içe koşul 10/25/40 m³ tarife basamaklarına üç yazımla uygulanıyor: sınır karşılaştırması `<=` yerine `<` olunca 4 kayıt basamak değiştiriyor, koşulların sırası bozulunca 20 kayıt yanlış basamağa düşüyor ve birinci basamak tamamen boşalıyor. Sınır kararı tutar toplamını 26 219,52'den 26 698,32 ücret birimine, yani yüzde 1,83 oynatıyor, ama tek bir faturayı yüzde 35,5 oynatıyor. Aynı toplamı üreten üç yazımdan yalnız birinde basamak bir hücreye yazılı; ötekilerde karar 3 katmanlı bir koşulun içinde ve hiçbir hücrede durmuyor.

Önceki ders bir aralığın kaç hücresinin sayıldığını ölçtü ve her sayımda tek bir eşik kullandı.
Tarife tek eşikli değildir: bir kaydın hangi basamağa düştüğü 10, 25 ve 40 m³ sınırlarına sırayla
bakan iç içe bir koşulla belirlenir. Her katmanda bir karşılaştırma işareti ve bir sıra seçilir;
ikisi de yazan kişinin kararıdır ve sayfada görünmez.

Bu ders aynı 60 kaydı üç ayrı iç içe koşulla basamaklara dağıtır, ardından basamağı tutara
çevirir. Sınırda duran kayıt azdır, ama karşılaştırma işaretinin bütün ağırlığını onlar taşır.

- **TT10.** Tarife **faturalanan tüketime** uygulanır ve faturalanan tüketim tam m³'e
  yuvarlanır. Yuvarlama bir karardır; sınıra tam oturan kayıtları yaratan da odur.
- **TT11.** Tarife tablosu **sayfada durur**: `J` basamak numarası, `K` üst sınır, `L` birim
  ücret çarpanı. Sınırlar ve çarpanlar kurgudur. Aynı sınır sayıları önceki derslerin parametre
  bloğunda da bulunur; bu tekrarın bedeli aşağıda ölçülür.
- **TT12.** `eger` işlevi koşulu değerlendirir ve iki daldan birini döndürür. İç içe yazıldığında
  koşullar **soldan sağa** denenir ve **ilk tutan dal kazanır**. Sıra bu yüzden bir karardır.

#### Sınıra Tam Oturan Kayıtlar

Aşağıdaki blok sayfayı kurar. Üretici önceki derslerdeki sırayla çağrılır; yeni olan `F` sütunu
ile tarife tablosudur.

```js
// tarife.mjs — elektronik tablo MODELI: 01'in modeli, mantiksal islevlerle genisletilmis.
// Tarife tablosu sayfada durur; basamak sinirlari ve carpanlar hucrelerden okunur.
export const TOHUM = 20260602, SATIR = 60;
const uretec = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };

const REF = /(\$?)([A-Z])(\$?)(\d+)/g;
export const kaydir = (f, dS, dR) => f.replace(REF, (_, a, s, b, r) =>
  (a ? "$" + s : String.fromCharCode(s.charCodeAt(0) + dS)) + (b ? "$" + r : String(+r + dR)));

// Bu dersin formullerinde aralik yok; model yalniz hucre basvurusu ve islev cagrisi cozer.
const ISLEV = {
  eger: (k, d, y) => (k ? d : y),         // kosul once degerlendirilir, ilk tutan dal kazanir
  yuvarla: (x) => Math.round(x),
  enk: (a, b) => Math.min(a, b),
  enb: (a, b) => Math.max(a, b),
};

export function deger(H, ad) {
  const v = H[ad];
  if (v === undefined) return 0;
  if (typeof v !== "string" || v[0] !== "=") return v;
  const ifade = v.slice(1).replace(REF, (m) => JSON.stringify(deger(H, m.replace(/\$/g, ""))));
  return Function(...Object.keys(ISLEV), `return (${ifade});`)(...Object.values(ISLEV));
}

export function sayfa() {
  const r = uretec(TOHUM), H = {};
  H.A1 = "abone"; H.C1 = "tuketim"; H.F1 = "faturalanan";
  for (let i = 0; i < SATIR; i++) {
    const s = i + 2;
    H["A" + s] = "AB-" + String(1041 + i * 7).padStart(4, "0");
    r();                                    // 01'deki bolge sutunu: uretec ayni sirada calisir
    H["C" + s] = Math.round((2 + r() * r() * 78) * 10) / 10;
    H["F" + s] = "=yuvarla(C" + s + ")";    // faturalanan tuketim tam m3'e yuvarlanir
  }
  // 01'deki parametre blogu aynen durur: birim ucret H2, sabit bedel H3, sinirlar H6:H8.
  ["birim ucret", "sabit bedel", "sayac bakim", "kayip payi", "basamak 1", "basamak 2",
    "basamak 3", "indirim"].forEach((e, j) => { H["G" + (j + 2)] = e; });
  [8.4, 25, 3.5, 1.2, 10, 25, 40, 0.9].forEach((v, j) => { H["H" + (j + 2)] = v; });
  // Tarife tablosu: J basamak, K ust sinir (m3), L birim ucret carpani.
  H.J1 = "basamak"; H.K1 = "ust sinir"; H.L1 = "carpan";
  [1, 2, 3, 4].forEach((b, j) => { H["J" + (j + 2)] = b; });
  [10, 25, 40, ""].forEach((k, j) => { H["K" + (j + 2)] = k; });
  [1, 1.4, 1.9, 2.6].forEach((c, j) => { H["L" + (j + 2)] = c; });
  return H;
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const H = sayfa();
  const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
    : String(v).padEnd(B[0]))).join("");
  const y = g([8, 10, 12, 14]);
  console.log(y(["hucre", "tuketim", "faturalanan", "sinirda mi"]));
  const F = Array.from({ length: SATIR }, (_, i) => deger(H, "F" + (i + 2)));
  const SINIR = [10, 25, 40];
  for (const s of [2, 3, 30, 36, 45, 61]) console.log(y(["F" + s, H["C" + s].toFixed(1),
    deger(H, "F" + s), SINIR.includes(deger(H, "F" + s)) ? "evet" : "hayir"]));
  const n = (f) => F.filter(f).length;
  console.log(`\nFaturalanan tuketim dagilimi — 1. basamak ${n((v) => v <= 10)}, ` +
    `2. basamak ${n((v) => v > 10 && v <= 25)}, 3. basamak ${n((v) => v > 25 && v <= 40)}, ` +
    `4. basamak ${n((v) => v > 40)}`);
  console.log(`Sinira tam oturan kayit: ` +
    SINIR.map((v) => `${v} m3: ${n((x) => x === v)}`).join(", ") +
    ` — toplam ${n((v) => SINIR.includes(v))}`);
}
```

```
hucre      tuketim faturalanan    sinirda mi
F2             9.8          10          evet
F3            71.6          72         hayir
F30           25.4          25          evet
F36           25.0          25          evet
F45           39.9          40          evet
F61           21.2          21         hayir

Faturalanan tuketim dagilimi — 1. basamak 20, 2. basamak 18, 3. basamak 9, 4. basamak 13
Sinira tam oturan kayit: 10 m3: 1, 25 m3: 2, 40 m3: 1 — toplam 4
```

Dört kayıt sınıra tam oturuyor ve dördü oraya farklı yoldan geldi: `F2` yukarı yuvarlandı,
`F30` aşağı, `F36` zaten 25'ti, `F45` yukarı. Yuvarlama olmasaydı hiçbiri sınırda olmayacaktı;
bir sınır sorusunu **yaratan** şey, sınırla ilgisi olmayan bir başka karardır.

#### Aynı Koşul, Üç Yazım

Basamak numarası üç iç içe `eger` ile atanır. Birincisi sınırı dahil eder, ikincisi etmez,
üçüncüsü eder ama koşulları yanlış sırada dener.

```js
// basamak.mjs — ayni ic ice kosul uc yazimla: <=, < ve sirasi bozulmus hali.
import { sayfa, deger, kaydir, SATIR } from "./tarife.mjs";

const KOSUL = {
  A: "=eger(F2<=$K$2,$J$2,eger(F2<=$K$3,$J$3,eger(F2<=$K$4,$J$4,$J$5)))",
  B: "=eger(F2<$K$2,$J$2,eger(F2<$K$3,$J$3,eger(F2<$K$4,$J$4,$J$5)))",
  C: "=eger(F2<=$K$3,$J$3,eger(F2<=$K$2,$J$2,eger(F2<=$K$4,$J$4,$J$5)))",
};
const H = sayfa();
const sutun = (f) => { const K = { ...H };
  for (let i = 0; i < SATIR; i++) K["M" + (i + 2)] = kaydir(f, 0, i);
  return Array.from({ length: SATIR }, (_, i) => deger(K, "M" + (i + 2))); };
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const D = Object.fromEntries(Object.entries(KOSUL).map(([a, f]) => [a, sutun(f)]));
const y = g([8, 12, 8, 8, 12]);
console.log(y(["hucre", "faturalanan", "A <=", "B <", "C sirasiz"]));
for (const s of [2, 3, 30, 36, 45, 61]) { const i = s - 2;
  console.log(y(["M" + s, deger(H, "F" + s), D.A[i], D.B[i], D.C[i]])); }

const z = g([12, 10, 10, 10, 10, 14]);
console.log("\n" + z(["yazim", "1. bas.", "2. bas.", "3. bas.", "4. bas.", "A'dan farkli"]));
for (const [a, s] of Object.entries(D)) console.log(z([a, ...[1, 2, 3, 4].map((b) =>
  s.filter((v) => v === b).length), s.filter((v, i) => v !== D.A[i]).length]));
console.log(`\nIc ice kosul derinligi ${(KOSUL.A.match(/eger\(/g) ?? []).length}; ` +
  `sinirlar K2:K4 hucrelerinden okunuyor.`);
```

```
hucre    faturalanan    A <=     B <   C sirasiz
M2                10       1       2           2
M3                72       4       4           4
M30               25       2       3           2
M36               25       2       3           2
M45               40       3       4           3
M61               21       2       2           2

yazim          1. bas.   2. bas.   3. bas.   4. bas.  A'dan farkli
A                   20        18         9        13             0
B                   19        17        10        14             4
C                    0        38         9        13            20

Ic ice kosul derinligi 3; sinirlar K2:K4 hucrelerinden okunuyor.
```

`B` yazımı `A`'dan tam **4** kayıtta ayrılıyor ve o dördü sınırdaki dört kayıttır; kalan 56
kayıt için `<` ile `<=` aynı şeyi söyler. Farkın küçük görünmesi bir güvence değildir: fark her
zaman sınırdaki kayıt sayısı kadardır ve o sayı veriye bağlıdır.

`C` yazımı başka bir hata sınıfını gösteriyor. Sınırlar, çarpanlar ve işaretler doğru; yalnız
25 sınırı 10 sınırından önce denendi. İlk tutan dal kazandığı için 10 m³'ün altındaki her kayıt
ilk katmanda "25'in altında" bulunup ikinci basamağa yazıldı. Birinci basamak **tamamen
boşaldı**: 20 kayıt yanlış basamağa düştü. Sayfada ne hata değeri var ne boş hücre, yalnız hiç
görünmeyen bir basamak numarası.

#### Basamaktan Tutara

Basamak tek başına rapor edilmez, tutara çevrilir. Aşağıdaki blok üç tutar yazımını
karşılaştırır, sınır farkını fatura düzeyinde basar ve aynı toplamı üreten üç yazımı kararın
nerede durduğuna göre tarar.

```js
// tutar.mjs — basamak karari tutara cevrilir: duz ve kademeli hesap, iki sinir yazimi.
import { sayfa, deger, kaydir, SATIR } from "./tarife.mjs";

const CARPAN = (o) => `eger(F2${o}$K$2,$L$2,eger(F2${o}$K$3,$L$3,eger(F2${o}$K$4,$L$4,$L$5)))`;
const TUTAR = {
  "duz <=": "=$H$2*F2*" + CARPAN("<=") + "+$H$3",
  "duz <": "=$H$2*F2*" + CARPAN("<") + "+$H$3",
  kademeli: "=$H$2*(enk(F2,$K$2)*$L$2+enb(0,enk(F2,$K$3)-$K$2)*$L$3" +
    "+enb(0,enk(F2,$K$4)-$K$3)*$L$4+enb(0,F2-$K$4)*$L$5)+$H$3",
};
const H = sayfa();
const sutun = (f) => { const K = { ...H };
  for (let i = 0; i < SATIR; i++) K["N" + (i + 2)] = kaydir(f, 0, i);
  return Array.from({ length: SATIR }, (_, i) => deger(K, "N" + (i + 2))); };
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const D = Object.fromEntries(Object.entries(TUTAR).map(([a, f]) => [a, sutun(f)]));
const t = (d) => d.reduce((a, b) => a + b, 0);
// Esik 0,005 ucret birimidir: tutarlar kurus altinda ayirt edilmez.
const ESIK = 0.005;
const y = g([12, 12, 12, 12]);
console.log(y(["yazim", "toplam", "duz <='den", "oynayan"]));
for (const [a, d] of Object.entries(D)) console.log(y([a, t(d).toFixed(2),
  (t(d) - t(D["duz <="])).toFixed(2), d.filter((v, i) => Math.abs(v - D["duz <="][i]) >= ESIK).length]));

const z = g([10, 12, 12, 12, 10]);
console.log("\n" + z(["hucre", "faturalanan", "duz <=", "duz <", "fark %"]));
let enBuyuk = 0;
for (let i = 0; i < SATIR; i++) {
  const a = D["duz <="][i], b = D["duz <"][i];
  if (Math.abs(a - b) < ESIK) continue;
  enBuyuk = Math.max(enBuyuk, 100 * Math.abs(b - a) / a);
  console.log(z(["N" + (i + 2), deger(H, "F" + (i + 2)), a.toFixed(2), b.toFixed(2),
    (100 * (b - a) / a).toFixed(1)]));
}
console.log(`\nSinir karari toplami yuzde ` +
  `${(100 * Math.abs(t(D["duz <"]) - t(D["duz <="])) / t(D["duz <="])).toFixed(2)} oynatti, ` +
  `tek bir faturayi en cok yuzde ${enBuyuk.toFixed(1)} oynatti.`);
console.log(`Kademeli hesap sinir yazimindan etkilenmez: kosul yok, dilim var.`);

// Geri okuma taramasi: ayni tutari uc yazim uretiyor, kararin yeri farkli.
const CARP = (o, K, L) => `eger(F2${o}${K[0]},${L[0]},eger(F2${o}${K[1]},${L[1]},` +
  `eger(F2${o}${K[2]},${L[2]},${L[3]})))`;
const HUC = ["$K$2", "$K$3", "$K$4"], CL = ["$L$2", "$L$3", "$L$4", "$L$5"];
const YAZIM = {
  tek: { N: "=$H$2*F2*" + CARP("<=", HUC, CL) + "+$H$3" },
  "iki sutun": { M: "=" + CARP("<=", HUC, CL), N: "=$H$2*F2*M2+$H$3" },
  gomulu: { N: "=$H$2*F2*" + CARP("<=", [10, 25, 40], [1, 1.4, 1.9, 2.6]) + "+$H$3" },
};
const kur = (S, y) => { const K = { ...S };
  for (let i = 0; i < SATIR; i++) for (const [c, f] of Object.entries(y)) K[c + (i + 2)] = kaydir(f, 0, i);
  return K; };
const Z = { ...H, K3: 30 };                              // ikinci sinir 25 -> 30 m3
const s = g([12, 12, 10, 8, 10]);
console.log("\n" + s(["yazim", "toplam", "derinlik", "adim", "pesinden"]));
for (const [ad, y] of Object.entries(YAZIM)) {
  const K = kur(H, y), W = kur(Z, y);
  const N = Array.from({ length: SATIR }, (_, i) => deger(K, "N" + (i + 2)));
  const N2 = Array.from({ length: SATIR }, (_, i) => deger(W, "N" + (i + 2)));
  // Basamagi geri okumak icin okunacak hucre: tutar formulunde kosul varsa karar hicbir
  // hucrede yazili degildir, uc karsilastirma yeniden degerlendirilir.
  const metin = Object.values(y).join("");
  console.log(s([ad, N.reduce((a, b) => a + b, 0).toFixed(2), (metin.match(/eger\(/g) ?? []).length,
    /eger\(/.test(y.N) ? 0 : 1, N.filter((v, i) => Math.abs(v - N2[i]) >= ESIK).length]));
}
const iki = [10, 25, 40].filter((v) => Object.values(H).filter((x) => x === v).length > 1);
console.log(`\nBasamak sinirlari sayfada iki yerde duruyor (H6:H8 ve K2:K4): ` +
  `${iki.join(", ")} m3. K3 degisince H7 degismez.`);
```

```
yazim             toplam  duz <='den     oynayan
duz <=          26219.52        0.00           0
duz <           26698.32      478.80           4
kademeli        19507.92    -6711.60          40

hucre      faturalanan      duz <=       duz <    fark %
N2                  10      109.00      142.60      30.8
N30                 25      319.00      424.00      32.9
N36                 25      319.00      424.00      32.9
N45                 40      663.40      898.60      35.5

Sinir karari toplami yuzde 1.83 oynatti, tek bir faturayi en cok yuzde 35.5 oynatti.
Kademeli hesap sinir yazimindan etkilenmez: kosul yok, dilim var.

yazim             toplam  derinlik    adim  pesinden
tek             26219.52         3       0         6
iki sutun       26219.52         3       1         6
gomulu          26219.52         3       0         0

Basamak sinirlari sayfada iki yerde duruyor (H6:H8 ve K2:K4): 10, 25, 40 m3. K3 degisince H7 degismez.
```

Birinci tablo dersin birinci sayısını veriyor: 26 219,52 ücret birimi. Sınır karşılaştırması
değişince 26 698,32 oluyor, fark 478,80 — yüzde 1,83, bir kontrol toplamının gözünden kaçacak
kadar küçük. İkinci tablo aynı farkı kayıt düzeyinde gösteriyor: dört abonenin faturası yüzde
30,8 ile 35,5 arasında değişti. **Toplamda küçük olan bir karar, tek tek kayıtlarda küçük
değildir.**

Kademeli hesap üçüncü bir sayı veriyor: 19 507,92, düz hesaptan 6 711,60 daha az ve 60 kaydın
40'ında farklı. İkisi de savunulabilir: biri tüketimin tamamını ulaşılan basamağın çarpanıyla
ücretlendirir, öteki her dilimi kendi çarpanıyla. Kademeli yazımda hiç koşul yoktur; dilimler
`enk` ve `enb` ile kesilir, bu yüzden sınır işareti kararı o hesapta hiç sorulmaz. **Bir kararın
maliyeti, yanındaki kararın ne olduğuna bağlıdır.**

Son tablo dersin ikinci sayısıdır. Üç yazım da aynı toplamı üretiyor ve üçünde de koşul
derinliği 3. Ayrım son iki sütunda: `iki sutun` yazımında çarpan `M` hücresine yazılır ve bir
kaydın basamağını öğrenmek için **1 hücre** okunur; ötekilerde basamak hiçbir hücrede durmaz —
**0 adım**, okunacak yer yok. Sınır 30 m³'e çıkarıldığında hücreye başvuran iki yazımda 6 tutar
peşinden geldi, gömülü yazımda 0; sonuncusu 26 219,52'yi göstermeye devam eder ve artık
yanlıştır.

Kapanış satırı sayfanın çelişkisini yazıyor: 10, 25 ve 40 hem parametre bloğunda hem tarife
tablosunda duruyor. Formüller tarife tablosuna başvurur, ama sayfaya bakan biri parametre
bloğunu güncelleyebilir ve hiçbir hesap kıpırdamaz. **Aynı kararın iki kopyası, bir karar
değildir.**

#### Özet

- İç içe koşulda katmanlar soldan sağa denenir ve ilk tutan dal kazanır; sıra bir karardır.
  Sıra bozulunca 20 kayıt yanlış basamağa düştü ve birinci basamak tamamen boşaldı.
- Sınır karşılaştırması yalnız sınırda duran kayıtları etkiler: `<=` yerine `<` yazmak 4 kaydı
  oynattı. O dördünü sınıra oturtan şey sınır değil, yuvarlama kararıdır.
- Aynı karar toplamı yüzde 1,83 oynattı ama dört faturayı yüzde 30,8 ile 35,5 arasında oynattı.
  Toplama bakan bir kontrol bu farkı göremez.
- Aynı toplamı üreten üç yazımdan yalnız birinde basamak bir hücrede yazılıdır; ötekilerde
  karar 3 katmanlı koşulun içindedir ve sayfada okunacak yeri yoktur.
- Aynı sınır sayfada iki yerde durduğunda biri değişip öteki kalabilir; ikinci kopya kararı
  güçlendirmez, belirsizleştirir.

#### Sonraki Adım

Buraya kadar her karşılaştırma sayısaldı. Sayfanın öteki yarısı metindir: abone kodu, ad,
bölge. Sonraki adımda saha ziyareti kayıtları abone kayıtlarıyla abone kodu üzerinden
eşleştirilir. İki tablodaki kodlar gözle bakıldığında aynıdır, ama biri sonunda görünmeyen bir
boşluk taşır, biri büyük harfle yazılmıştır. Sonraki ders bu kayıtların kaçının eşleşmediğini
sayar, kırpma ve harf dönüşümü kararlarının kaç eşleşmeyi geri getirdiğini ölçer ve bu
kararların sayfada nerede durduğunu gösterir.

### Metin İşlevleri

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/metin-islevleri

48 saha ziyareti kaydı 60 abonelik listeyle abone kodu üzerinden eşleştiriliyor ve ham hâlinde yalnız 8'i tutuyor. Kırpma 24'e, kırpmayla birlikte büyük harfe çevirme 32'ye, her tür boşluğu silmek 48'e çıkarıyor; rapor edilen ziyaret tüketimi bu kararlarla 232,1 m³ ile 1204,8 m³ arasında oynuyor. Kodu yeniden kuran beşinci karar aynı 48'i verirken başka bir önekle gelen kaydı sessizce yanlış aboneye bağlıyor. Dil duyarsız harf dönüşümü teknisyen adlarının 15'ini tutturamıyor ve aynı sonucu üreten üç yazımdan yalnız birinde 40 düzeltme kararı hücre hücre geri okunabiliyor.

Buraya kadar her karşılaştırma sayısaldı. Sayfanın öteki yarısı metindir ve metinde eşitlik
sayıdaki kadar açık değildir: `AB-1041` ile `AB-1041 ` ekranda aynı görünür, aynı uzunlukta
görünmez ve karşılaştırıldığında eşit çıkmaz. Bu ders saha ziyareti kayıtlarını abone
listesiyle abone kodu üzerinden eşleştirir ve kaç kaydın **birleşmediğini** sayar.

Ölçü iki yerde okunur. Birincisi eşleşen kayıt sayısı ve onunla birlikte rapora giren tüketim
değeridir. İkincisi temizleme kararının nerede durduğudur: bir yardımcı sütunda mı, eşleştirme
formülünün içinde mi, yoksa hiçbir yerde mi — çünkü elle düzeltilmiş bir hücre sonucu doğru
gösterir ve kararı yok eder.

- **TT13.** Ziyaret tablosundaki abone kodları altı biçimde gelir: temiz, sonunda boşluk,
  başında boşluk, küçük harfli, sonunda **görünmeyen boşluk**, ortasında boşluk. Bozulmalar
  kurgudur ve kendi tohumuyla sırayla dağıtılır. Bozulmanın türü sayfada **yazmaz**; aşağıdaki
  `tur` işlevi onu ancak inceleyerek sınıflandırır.
- **TT14.** `kirp` işlevi yalnız **normal boşluğu** siler; görünmeyen boşluk ile iç boşluk
  kırpılmadan kalır. `temizle` her tür boşluğu siler. İkisi arasındaki fark bu dersin en pahalı
  kararıdır.
- **TT15.** `buyuk` dil duyarsız bir harf dönüşümüdür; `buyuktr` Türkçenin i harfi kuralını
  uygular. Ekip adları kurgudur ve yarısı küçük harfle girilmiştir.

#### Gözle Aynı, Karşılaştırmada Farklı

Aşağıdaki blok iki tabloyu kurar: soldaki 60 abonelik liste, sağdaki 48 ziyaret kaydı. Çıktıda
boşluklar görünür karakterlere çevrilir — normal boşluk alt çizgi, görünmeyen boşluk tilde.

```js
// metin.mjs — elektronik tablo MODELI: 01'in modeli, metin islevleriyle genisletilmis.
// Saha ziyaretleri tablosundaki abone kodlari bozuk gelir; bozulmalar kurgudur.
export const TOHUM = 20260602, SATIR = 60, ZIYARET = 48;
const uretec = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
export const GB = "\u00a0";           // gorunmeyen bosluk: hucrede normal boslukla ayni

const REF = /(\$?)([A-Z])(\$?)(\d+)/g;
export const kaydir = (f, dS, dR) => f.replace(REF, (_, a, s, b, r) =>
  (a ? "$" + s : String.fromCharCode(s.charCodeAt(0) + dS)) + (b ? "$" + r : String(+r + dR)));

// Metin islevleri. buyuk dil duyarsizdir; buyuktr Turkce i/I kuralini uygular.
const ISLEV = {
  kirp: (s) => String(s).replace(/^ +| +$/g, ""),     // yalniz normal bosluk kirpilir
  buyuk: (s) => String(s).toUpperCase(),
  buyuktr: (s) => String(s).replace(/i/g, "İ").replace(/ı/g, "I").toUpperCase(),
  temizle: (s) => String(s).replace(/\s/g, ""),      // \s gorunmeyen boslugu da kapsar
  birlestir: (...p) => p.map(String).join(""),
  sagdan: (s, n) => String(s).slice(-n),             // sondan n karakter: kodun sayi kismi
  uzunluk: (s) => String(s).length,
};
export function deger(H, ad) {
  const v = H[ad];
  if (v === undefined) return 0;
  if (typeof v !== "string" || v[0] !== "=") return v;
  const ifade = v.slice(1).replace(REF, (m) => JSON.stringify(deger(H, m.replace(/\$/g, ""))));
  return Function(...Object.keys(ISLEV), `return (${ifade});`)(...Object.values(ISLEV));
}

// Alti bozulma turu; sayfada gozle ayirt edilemezler.
export const BOZ = [["temiz", (k) => k], ["sondaki bosluk", (k) => k + " "],
  ["bastaki bosluk", (k) => " " + k], ["kucuk harf", (k) => k.toLowerCase()],
  ["gorunmeyen bosluk", (k) => k + GB], ["ic bosluk", (k) => k.replace("-", "- ")]];
export const EKIP = ["İsmet", "İlknur", "Irmak", "Ilgaz", "Sedef"];

export function sayfa() {
  const r = uretec(TOHUM), H = {};
  H.A1 = "abone"; H.C1 = "tuketim"; H.P1 = "ziyaret kodu"; H.Q1 = "teknisyen";
  for (let i = 0; i < SATIR; i++) {
    const s = i + 2;
    H["A" + s] = "AB-" + String(1041 + i * 7).padStart(4, "0");
    r();                                    // 01'deki bolge sutunu: uretec ayni sirada calisir
    H["C" + s] = Math.round((2 + r() * r() * 78) * 10) / 10;
  }
  // Ziyaret tablosu: ZIYARET ayri abone, her bozulma turu sirayla uygulanir.
  const z = uretec(TOHUM + 2);
  const sira = Array.from({ length: SATIR }, (_, i) => i);
  for (let i = SATIR - 1; i > 0; i--) { const j = Math.floor(z() * (i + 1));
    [sira[i], sira[j]] = [sira[j], sira[i]]; }
  for (let i = 0; i < ZIYARET; i++) {
    const s = i + 2, k = H["A" + (2 + sira[i])];
    H["P" + s] = BOZ[i % BOZ.length][1](k);
    // Yarisi kucuk harfle girilmis; kucuk harfe cevirme Turkce kurala gore yapilir.
    const t = EKIP[Math.floor(z() * EKIP.length)];
    H["Q" + s] = z() < 0.5 ? t.replace(/İ/g, "i").replace(/I/g, "ı").toLowerCase() : t;
  }
  return H;
}

// Bozulmanin turu sayfada yazmaz; ancak inceleyerek siniflandirilir.
export const tur = (k) => k !== k.replace(/^ +| +$/g, "") ? "kirpilir bosluk"
  : k.includes(GB) ? "gorunmeyen bosluk" : k.includes("- ") ? "ic bosluk"
  : k !== k.toUpperCase() ? "kucuk harf" : "temiz";

if (import.meta.url === `file://${process.argv[1]}`) {
  const H = sayfa();
  const gor = (s) => String(s).replace(new RegExp(GB, "g"), "~").replace(/ /g, "_");
  const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
    : String(v).padEnd(B[0]))).join("");
  const y = g([8, 14, 8, 20]);
  console.log(y(["hucre", "ziyaret kodu", "uzunluk", "bozulma"]));
  for (const s of [2, 3, 4, 5, 6, 7]) console.log(y(["P" + s, gor(H["P" + s]),
    deger({ ...H, X1: "=uzunluk(P" + s + ")" }, "X1"), tur(H["P" + s])]));
  const P = Array.from({ length: ZIYARET }, (_, i) => H["P" + (i + 2)]);
  const K = new Set(Array.from({ length: SATIR }, (_, i) => H["A" + (i + 2)]));
  const sayim = {};
  for (const k of P) sayim[tur(k)] = (sayim[tur(k)] ?? 0) + 1;
  console.log(`\n${ZIYARET} ziyaret kaydi — ` +
    Object.entries(sayim).map(([a, n]) => `${a} ${n}`).join(", "));
  console.log(`Kodu abone tablosunda oldugu gibi bulunan kayit: ${P.filter((k) => K.has(k)).length}`);
}
```

```
hucre     ziyaret kodu uzunluk             bozulma
P2             AB-1286       7               temiz
P3            AB-1083_       8     kirpilir bosluk
P4            _AB-1167       8     kirpilir bosluk
P5             ab-1090       7          kucuk harf
P6            AB-1097~       8   gorunmeyen bosluk
P7            AB-_1048       8           ic bosluk

48 ziyaret kaydi — temiz 8, kirpilir bosluk 16, kucuk harf 8, gorunmeyen bosluk 8, ic bosluk 8
Kodu abone tablosunda oldugu gibi bulunan kayit: 8
```

Uzunluk sütunu dersin tek görünür kanıtıdır. `P3` ile `P6` sekiz karakterdir ve ikisinde de
sekizinci karakter bir boşluktur; ama biri kırpılır, öteki kırpılmaz. Sayfada ikisi de yedi
karakterlik bir kod gibi görünür. **Bir metin hücresinin gösterdiği şey, içerdiği şey
değildir.**

Ham hâlinde 48 kaydın yalnız 8'i abone listesinde bulunuyor. Kalan 40 kayıt kaybolmuş
abonelere ait değil; hepsi listede duran abonelere ait ve hiçbiri eşleşmiyor.

#### Dört Temizleme Kararı

Temizleme tek bir işlem değil, sıralı kararlardır ve her karar bir grup kaydı geri getirir.
Aşağıdaki blok dördünü yardımcı bir `R` sütununda uygular ve her aşamada eşleşen kayıt sayısıyla
rapora girecek tüketimi basar.

```js
// eslesme.mjs — dort temizleme karari, dort eslesme sayisi ve dort rapor edilen tuketim.
import { sayfa, deger, kaydir, SATIR, ZIYARET } from "./metin.mjs";

const KARAR = [
  ["ham", "=P2"],
  ["kirpma", "=kirp(P2)"],
  ["kirpma + buyuk harf", "=buyuk(kirp(P2))"],
  ["her bosluk + buyuk harf", "=buyuk(temizle(P2))"],
  ["yeniden kurma", '=birlestir("AB-",sagdan(temizle(P2),4))'],
];
const H = sayfa();
const TUKETIM = new Map(Array.from({ length: SATIR }, (_, i) =>
  [H["A" + (i + 2)], H["C" + (i + 2)]]));
const anahtar = (f) => { const K = { ...H };
  for (let i = 0; i < ZIYARET; i++) K["R" + (i + 2)] = kaydir(f, 0, i);
  return Array.from({ length: ZIYARET }, (_, i) => deger(K, "R" + (i + 2))); };
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const y = g([26, 10, 12, 14, 12]);
console.log(y(["R sutunu formulu", "eslesen", "eslesmeyen", "tuketim (m3)", "kurtarilan"]));
let onceki = 0;
for (const [ad, f] of KARAR) {
  const R = anahtar(f), e = R.filter((k) => TUKETIM.has(k));
  const t = e.reduce((a, k) => a + TUKETIM.get(k), 0);
  console.log(y([ad, e.length, ZIYARET - e.length, t.toFixed(1), e.length - onceki]));
  onceki = e.length;
}
const tam = Array.from({ length: SATIR }, (_, i) => H["C" + (i + 2)]).reduce((a, b) => a + b, 0);
console.log(`\n60 abonenin toplam tuketimi ${tam.toFixed(1)} m3; ziyaret edilen tuketim ` +
  `${anahtar(KARAR[3][1]).reduce((a, k) => a + (TUKETIM.get(k) ?? 0), 0).toFixed(1)} m3.`);

// Yeniden kurma kodu okumaz, yeniden yazar: baska bir onekle gelen kayit da eslesir.
const yad = { ...H, P2: "XY-1041" };
for (const [ad, f] of KARAR.slice(3)) {
  const K = { ...yad, R2: f };
  console.log(`${ad}: XY-1041 -> ${deger(K, "R2")} (${TUKETIM.has(deger(K, "R2")) ? "eslesti" : "eslesmedi"})`);
}
```

```
R sutunu formulu             eslesen  eslesmeyen  tuketim (m3)  kurtarilan
ham                                8          40         232.1           8
kirpma                            24          24         527.6          16
kirpma + buyuk harf               32          16         673.7           8
her bosluk + buyuk harf           48           0        1204.8          16
yeniden kurma                     48           0        1204.8           0

60 abonenin toplam tuketimi 1419.9 m3; ziyaret edilen tuketim 1204.8 m3.
her bosluk + buyuk harf: XY-1041 -> XY-1041 (eslesmedi)
yeniden kurma: XY-1041 -> AB-1041 (eslesti)
```

Rapora girecek sayı bu tablonun dördüncü sütununda ve dört değeri var: 232,1, 527,6, 673,7,
1204,8 m³. En küçüğü ile en büyüğü arasında beş kattan fazla fark var ve dördü de aynı veriden,
aynı soruyla, aynı gün çıktı. "Ziyaret edilen abonelerin toplam tüketimi" cümlesi tek başına bu
dört sayıdan hangisinin kastedildiğini söylemiyor.

Kırpma tek başına 16 kaydı geri getirdi ve orada durmak kolaydır: 24 eşleşme makul görünür,
kalan 24 kayıt da "listede olmayan aboneler" diye açıklanabilir. Oysa o 24 kaydın hepsi
listededir; 8'i görünmeyen boşluk, 8'i iç boşluk taşıyor ve ikisi de kırpmadan sağ çıkıyor.
**Bir temizlemenin yeterli olup olmadığı, temizlenmiş sayıya bakarak anlaşılamaz.**

Beşinci satır aynı sayıyı üçüncü bir yoldan veriyor ve en tehlikelisi odur. Yeniden kurma kodu
okumaz, **yeniden yazar**: sondan dört karakteri alıp başına sabit bir önek ekler. Son iki satır
bunun bedelini gösteriyor. Başka bir önekle gelen `XY-1041` kaydı dördüncü kararda eşleşmez,
beşinci kararda sessizce `AB-1041` olur ve ilgisiz bir aboneye bağlanır. Eşleşme sayısı aynı
kalır, hata görünmez.

#### Harf Dönüşümü ve Kararın Yeri

Harf dönüşümü de tek bir işlem değildir. Aşağıdaki blok teknisyen adlarını iki dönüşümle
katlayıp ekip listesiyle karşılaştırır, ardından temizleme kararının sayfada nerede durduğunu
üç yazım üzerinde tarar.

```js
// karar.mjs — harf donusumunun dile bagliligi, ve temizleme kararinin sayfada nerede durdugu.
import { sayfa, deger, kaydir, EKIP, ZIYARET, GB } from "./metin.mjs";

const H = sayfa();
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");
const sutun = (f, n) => { const K = { ...H };
  for (let i = 0; i < n; i++) K["S" + (i + 2)] = kaydir(f, 0, i);
  return Array.from({ length: n }, (_, i) => deger(K, "S" + (i + 2))); };

// Ekip listesi ayni islevle katlanir; eslesme iki katlanmis metnin esitligidir.
const y = g([30, 10, 12, 22]);
console.log(y(["S sutunu formulu", "eslesen", "eslesmeyen", "tutmayan ad"]));
for (const [ad, f] of [["dil duyarsiz", "=buyuk(Q2)"], ["Turkce kurali", "=buyuktr(Q2)"]]) {
  const S = sutun(f, ZIYARET);
  const liste = EKIP.map((t) => deger({ X1: "=" + f.slice(1).replace("Q2", JSON.stringify(t)) }, "X1"));
  const e = S.filter((v) => liste.includes(v)).length;
  const tut = [...new Set(S.filter((v) => !liste.includes(v)))].join(" ");
  console.log(y([ad + " (" + f + ")", e, ZIYARET - e, tut || "-"]));
}

// Ayni sonucu ureten uc yazim: karar yardimci sutunda, formulde ya da hic yok.
const YAZIM = [
  ["yardimci sutun", 'R sutunu =buyuk(temizle(P2))', ZIYARET, true],
  ["formule gomulu", "eslestirme formulu icinde", 0, true],
  ["elle duzeltme", "P sutunu uzerine yazilmis", 0, false],
];
const P = Array.from({ length: ZIYARET }, (_, i) => H["P" + (i + 2)]);
const bozuk = P.filter((k) => k !== k.replace(/^ +| +$/g, "") || k.includes(GB)
  || k.includes("- ") || k !== k.toUpperCase()).length;
const z = g([16, 30, 8, 10, 12]);
console.log("\n" + z(["yazim", "karar nerede", "hucre", "ham veri", "geri okuma"]));
for (const [ad, yer, hucre, ham] of YAZIM)
  console.log(z([ad, yer, hucre, ham ? "duruyor" : "silindi", hucre ? bozuk : 0]));
console.log(`\n${ZIYARET} ziyaret kaydinin ${bozuk} tanesi bozuk. Yardimci sutunda bu ${bozuk} ` +
  `karar tek tek okunur; elle duzeltmede hicbiri okunmaz ve ham kod da kalmaz.`);
```

```
S sutunu formulu                 eslesen  eslesmeyen           tutmayan ad
dil duyarsiz (=buyuk(Q2))             33          15          ISMET ILKNUR
Turkce kurali (=buyuktr(Q2))          48           0                     -

yazim                             karar nerede   hucre  ham veri  geri okuma
yardimci sutun    R sutunu =buyuk(temizle(P2))      48   duruyor          40
formule gomulu       eslestirme formulu icinde       0   duruyor           0
elle duzeltme        P sutunu uzerine yazilmis       0   silindi           0

48 ziyaret kaydinin 40 tanesi bozuk. Yardimci sutunda bu 40 karar tek tek okunur; elle duzeltmede hicbiri okunmaz ve ham kod da kalmaz.
```

Birinci tablo harf dönüşümünün bir metin işlemi değil, bir **dil** işlemi olduğunu yazıyor. Dil
duyarsız dönüşüm 48 adın 33'ünü tutturdu; tutmayan 15'i iki ada ait. Küçük harfle girilen
"ismet" ve "ilknur" adlarının baş harfi, dil duyarsız dönüşümde noktasız bir I'ya çevriliyor ve
listedeki noktalı biçimle eşleşmiyor. Türkçe kuralını uygulayan dönüşümde 48 adın hepsi tutuyor.
Aynı işlevin adı ikisinde de "büyük harfe çevir"dir; yaptıkları iş aynı değildir.

İkinci tablo dersin ikinci sayısını veriyor. Üç yazımın da eşleşme sonucu aynıdır: 48 kayıt
eşleşir. Ayrım son üç sütundadır. Yardımcı sütun yazımında karar 48 hücrede durur; temizlenmiş
kod görülebilir, ham kod da yerinde kalır ve bozuk gelen 40 kaydın her biri için "ne yapıldı"
sorusu hücreye bakılarak cevaplanır. Formüle gömülü yazımda sonuç aynıdır ama temizlenmiş kod
sayfada hiçbir yerde görünmez; hangi ziyaretin hangi aboneye bağlandığı yalnız formül metni
okunarak tahmin edilebilir.

Üçüncü satır en pahalı olanıdır ve en yaygın olanıdır. Kodlar elle düzeltilip `P` sütununun
üzerine yazıldığında sayfa kusursuz görünür: bütün kodlar temizdir, bütün eşleşmeler tutar,
hiçbir yardımcı sütun yoktur. Geri okunabilen karar sayısı **sıfırdır** ve ham veri de artık
yoktur. İkinci kez bakan biri 40 kaydın düzeltildiğini göremez, hangi kuralın uygulandığını
bilemez ve düzeltmenin doğru olup olmadığını denetleyemez. **Sonucu düzelten bir işlem, kararı
yok ediyorsa veriyi de yok eder.**

#### Özet

- Metin hücresinin gösterdiği şey içerdiği şey değildir: aynı görünen iki kod farklı uzunlukta
  olabilir ve karşılaştırmada eşit çıkmaz.
- 48 ziyaret kaydının ham hâlinde 8'i eşleşti; kırpma 24'e, harf dönüşümü 32'ye, her tür boşluğun
  silinmesi 48'e çıkardı. Rapor edilen tüketim 232,1 ile 1204,8 m³ arasında oynadı.
- Sıradan kırpma yalnız normal boşluğu siler; görünmeyen boşluk ve iç boşluk taşıyan 16 kayıt
  kırpmadan sağ çıkar ve eşleşmemeye devam eder.
- Bir temizlemenin yettiği, temizlenmiş sayıya bakarak anlaşılmaz; ancak eşleşmeyen kayıtların
  tek tek incelenmesiyle anlaşılır.
- Harf dönüşümü dile bağlıdır: dil duyarsız dönüşüm 48 adın 15'ini tutturamadı, Türkçe kuralını
  uygulayan dönüşüm hepsini tutturdu.
- Aynı eşleşmeyi üreten üç yazımdan yalnız birinde 40 düzeltme kararı hücre hücre okunur; elle
  düzeltmede hem karar hem ham veri kaybolur.

#### Sonraki Adım

Eşleştirme kurulduğuna göre ziyaret kayıtlarının tarihleri de kullanılabilir hâle geldi. Bir
sonraki adım basit görünen bir soruyu soruyor: bir arıza kaydının açılışıyla kapanışı arasında
kaç gün geçti. Tarih hücrede bir sayıdır ve gösterilen biçim o sayının kendisi değildir; iki
tarihin farkı bir çıkarma işlemidir, ama uçların dahil edilip edilmeyeceği bir karardır. Sonraki
ders aynı kayıt kümesinin ortalama çözüm süresini iki uç kararıyla hesaplar, bir ay sonrasının
ay sonunda ne anlama geldiğini ölçer ve metin olarak yazılmış tarihlerin kaçının iki farklı güne
okunabildiğini sayar.

### Tarih İşlevleri

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/tarih-islevleri

Aynı 54 destek talebinin ortalama çözüm süresi üç gün sayma kararıyla 6,33, 7,33 ve 5,20 gün çıkıyor; uçlar hariç sayımda 6 talep sıfır gün sürmüş görünüyor, iş günü sayımında iki talep takvimde iki gün sürdüğü hâlde sıfır iş günü gösteriyor. Bir ay sonrasının ay sonunda ne demek olduğu 60 abonenin 6'sında ayrışıyor: kırpma kuralı üç aboneyi aynı güne toplayıp 28, 29 ve 30 günlük üç dönem üretiyor, taşma kuralı hepsine 31 gün veriyor ama Şubat okumasını Mart'a kaydırıyor. 54 açılış tarihinin 19'u metin olarak yazıldığında iki ayrı güne okunabiliyor ve en büyük sapma 323 gün.

Bir arıza kaydının açılışıyla kapanışı arasında kaç gün geçtiği basit bir çıkarma gibi görünür.
Değildir: çıkarma iki uç arasındaki **aralığı** verir, sorulan şey ise çoğu zaman **kapsanan gün
sayısıdır** ve ikisi bir gün farklıdır. Bu ders tarihin hücrede ne olduğunu kurar, aynı talep
kümesinin ortalama çözüm süresini üç kararla hesaplar ve "bir ay sonra" ifadesinin ay sonunda
iki ayrı tarih verdiğini gösterir.

- **TT16.** Tarih hücrede bir **sayıdır**: bir başlangıç gününden itibaren gün sayısı; bu modelde
  seri 1, 1 Ocak 2026'dır. Biçim yalnızca bir **görünüştür**.
- **TT17.** Destek talepleri ve okuma günleri kurgudur. Okuma günü ayın 1'i ile 31'i arasında
  dağıtılmıştır; ay sonu sorusunu yaratan budur.
- **TT18.** Hafta sonu iki gündür, tatil günü tanımlanmamıştır. `isgunu` iki ucu da dahil ederek
  hafta içi günleri sayar; tatil listesi eklenirse sayı yine değişir.

#### Hücrede Duran Şey Bir Sayıdır

Aşağıdaki blok takvim çevrimini, okuma günlerini ve destek taleplerini kurar.

```js
// takvim.mjs — elektronik tablo MODELI: tarih hucrede bir SAYIDIR, bicim yalnizca gorunustur.
// Seri numarasi 1 = 1 Ocak 2026. Destek talepleri ve okuma gunleri kurgudur.
export const TOHUM = 20260602, TALEP = 54, SATIR = 60;
const uretec = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };

// Takvim cevrimi 2026-2027 icindir; iki yil da artik yil degildir, ikisi de 365 gundur.
const AY = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const ONCE = AY.map((_, i) => AY.slice(0, i).reduce((a, b) => a + b, 0));
export const seri = (y, a, g) => (y - 2026) * 365 + ONCE[a - 1] + g;   // seri 1 = 1 Ocak 2026
export const tarihi = (s) => { let k = s - 1, y = 2026, a = 0;
  while (k >= 365) { k -= 365; y++; }               // ay sinirini asan gun sonraki aya tasar
  while (k >= AY[a]) k -= AY[a++];
  return [y, a + 1, k + 1]; };

const REF = /(\$?)([A-Z])(\$?)(\d+)/g;
export const kaydir = (f, dS, dR) => f.replace(REF, (_, a, s, b, r) =>
  (a ? "$" + s : String.fromCharCode(s.charCodeAt(0) + dS)) + (b ? "$" + r : String(+r + dR)));

const iki = (n) => String(n).padStart(2, "0");
const ISLEV = {
  tarih: (y, a, g) => seri(y, a, g),
  ay: (s) => tarihi(s)[1], gun: (s) => tarihi(s)[2],
  bicim: (s) => { const [y, a, g] = tarihi(s); return `${iki(g)}.${iki(a)}.${y}`; },
  aysonu: (s) => { const [y, a] = tarihi(s); return seri(a === 12 ? y + 1 : y,
    a === 12 ? 1 : a + 1, 1) - 1; },                 // sonraki ayin ilk gununden bir onceki
  haftagunu: (s) => ((seri(2026, 1, 1) + s - 1 + 3) % 7) + 1,   // 1 Ocak 2026 persembe
  enk: (a, b) => Math.min(a, b),
  isgunu: (b, k) => { let n = 0;
    for (let s = b; s <= k; s++) if (ISLEV.haftagunu(s) <= 5) n++;
    return n; },
};
export function deger(H, ad) {
  const v = H[ad];
  if (v === undefined) return 0;
  if (typeof v !== "string" || v[0] !== "=") return v;
  const ifade = v.slice(1).replace(REF, (m) => JSON.stringify(deger(H, m.replace(/\$/g, ""))));
  return Function(...Object.keys(ISLEV), `return (${ifade});`)(...Object.values(ISLEV));
}

export function sayfa() {
  const H = {}, r = uretec(TOHUM);
  H.A1 = "abone"; H.C1 = "tuketim"; H.T1 = "acilis"; H.U1 = "kapanis"; H.V1 = "okuma gunu";
  for (let i = 0; i < SATIR; i++) {
    const s = i + 2;
    H["A" + s] = "AB-" + String(1041 + i * 7).padStart(4, "0");
    r();                                    // 01'deki bolge sutunu: uretec ayni sirada calisir
    H["C" + s] = Math.round((2 + r() * r() * 78) * 10) / 10;
    H["V" + s] = 1 + (i * 7) % 31;          // okuma gunu ayin 1'i ile 31'i arasinda
  }
  // Destek talepleri: acilis 2026'nin ilk yarisinda, sure 0-20 gun.
  const z = uretec(TOHUM + 3);
  for (let i = 0; i < TALEP; i++) {
    const s = i + 2, b = seri(2026, 1, 1) + Math.floor(z() * 180);
    H["T" + s] = b;
    H["U" + s] = b + Math.floor(z() * z() * 21);
  }
  return H;
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const H = sayfa();
  const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
    : String(v).padEnd(B[0]))).join("");
  const y = g([8, 10, 14, 10]);
  const o = (f) => deger({ ...H, X1: f }, "X1");
  console.log(y(["hucre", "acilis", "bicim(acilis)", "kapanis"]));
  for (const s of [2, 4, 5, 55])
    console.log(y(["T" + s, H["T" + s], o(`=bicim(T${s})`), H["U" + s]]));
  const F = Array.from({ length: TALEP }, (_, i) => H["U" + (i + 2)] - H["T" + (i + 2)]);
  console.log(`\n${TALEP} destek talebi. Ayni gun kapanan (fark 0): ` +
    `${F.filter((v) => v === 0).length}. Ham fark toplami ${F.reduce((a, b) => a + b, 0)} gun.`);
  console.log(`1 Ocak 2026 seri ${seri(2026, 1, 1)}, 31 Ocak 2026 seri ${seri(2026, 1, 31)}, ` +
    `28 Subat 2026 seri ${seri(2026, 2, 28)}; Subat 2026 ayinin son gunu ` +
    `${o("=bicim(aysonu(tarih(2026,2,1)))")}.`);
}
```

```
hucre       acilis bicim(acilis)   kapanis
T2              51    20.02.2026        57
T4              72    13.03.2026        89
T5             148    28.05.2026       159
T55             69    10.03.2026        69

54 destek talebi. Ayni gun kapanan (fark 0): 6. Ham fark toplami 342 gun.
1 Ocak 2026 seri 1, 31 Ocak 2026 seri 31, 28 Subat 2026 seri 59; Subat 2026 ayinin son gunu 28.02.2026.
```

`T2` hücresinde duran şey `20.02.2026` değil, `51`'dir; ikinci sütun ile üçüncü sütun aynı
hücrenin iki okunuşudur. **Bir hücrenin gösterdiği tarih, içindeki sayının bir yorumudur.**

Son satır ay sonu sorusunu kuruyor: Şubat 2026'nın son günü 28'dir, ayın 29, 30 ya da 31'inde
okunan sayaçların Şubat karşılığı yoktur.

#### Üç Gün Sayma Kararı

Aynı 54 talep üç formülle ölçülür: birincisi uçları hariç tutar, ikincisi ikisini de sayar,
üçüncüsü yalnız hafta içi günleri sayar.

```js
// sure.mjs — ayni talep kumesi uc gun sayma karariyla: uclar haric, uclar dahil, is gunu.
import { sayfa, deger, kaydir, TALEP } from "./takvim.mjs";

const KARAR = [
  ["uclar haric", "=U2-T2"],
  ["uclar dahil", "=U2-T2+1"],
  ["is gunu (uclar dahil)", "=isgunu(T2,U2)"],
];
const H = sayfa();
const sutun = (f) => { const K = { ...H };
  for (let i = 0; i < TALEP; i++) K["W" + (i + 2)] = kaydir(f, 0, i);
  return Array.from({ length: TALEP }, (_, i) => deger(K, "W" + (i + 2))); };
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const D = KARAR.map(([, f]) => sutun(f));
const y = g([24, 12, 12, 10]);
console.log(y(["W sutunu formulu", "toplam gun", "ortalama", "sifir"]));
KARAR.forEach(([ad], i) => { const d = D[i], t = d.reduce((a, b) => a + b, 0);
  console.log(y([ad, t, (t / TALEP).toFixed(2), d.filter((v) => v === 0).length])); });
console.log(`\n${TALEP} talep. Ortalama cozum suresi ` +
  KARAR.map(([ad], i) => `${(D[i].reduce((a, b) => a + b, 0) / TALEP).toFixed(2)} (${ad})`)
    .join(", ") + " gun.");

// Herhangi bir kararda sifir cikan talepler: sifir bir sure degil, bir sayma sonucudur.
const z = g([8, 14, 14, 12, 12, 10]);
console.log("\n" + z(["hucre", "acilis", "kapanis", "uclar haric", "uclar dahil", "is gunu"]));
const o = (f) => deger({ ...H, X1: f }, "X1");
for (let i = 0; i < TALEP; i++) if (D[0][i] === 0 || D[2][i] === 0)
  console.log(z(["W" + (i + 2), o(`=bicim(T${i + 2})`), o(`=bicim(U${i + 2})`),
    D[0][i], D[1][i], D[2][i]]));
```

```
W sutunu formulu          toplam gun    ortalama     sifir
uclar haric                      342        6.33         6
uclar dahil                      396        7.33         0
is gunu (uclar dahil)            281        5.20         2

54 talep. Ortalama cozum suresi 6.33 (uclar haric), 7.33 (uclar dahil), 5.20 (is gunu (uclar dahil)) gun.

hucre           acilis       kapanis uclar haric uclar dahil   is gunu
W14         10.06.2026    10.06.2026           0           1         1
W17         14.05.2026    14.05.2026           0           1         1
W26         19.01.2026    19.01.2026           0           1         1
W28         19.05.2026    19.05.2026           0           1         1
W49         06.03.2026    07.03.2026           1           2         0
W50         13.02.2026    14.02.2026           1           2         0
W51         06.05.2026    06.05.2026           0           1         1
W55         10.03.2026    10.03.2026           0           1         1
```

Rapor edilecek sayı ortalama çözüm süresidir ve üç değeri var: 6,33, 7,33 ve 5,20 gün. En
büyüğü ile en küçüğü arasındaki fark en küçük değerin yüzde 41'i kadar. Üçü de savunulabilir;
hangisinin kastedildiğini "ortalama çözüm süresi" ifadesi söylemiyor.

Sıfır sütunu kararın nerede kanadığını gösteriyor. Uçlar hariç sayımda 6 talep **sıfır gün**
sürmüş görünüyor; oysa o talepler açıldıkları gün kapandılar, yani bir gün sürdüler. Sıfırları
"anında çözüldü" diye okuyan bir rapor verinin değil, sayma kararının sonucunu yazar.

Alt tablonun son iki satırı ters yönde bir uç veriyor: `W49` ve `W50` takvimde iki gün sürdü,
tamamen hafta sonuna denk geldiği için **sıfır** iş günü gösteriyor. **Aynı kayıt üç sayma
kuralında üç ayrı süre taşır.**

#### Bir Ay Sonra Ne Zamandır

"Bir ay" sabit bir gün sayısı değildir. Aşağıdaki blok Şubat okumasını iki kuralla hesaplar,
metne çevrilmiş tarihlerin belirsizliğini sayar ve gün sayma kararını dört yazımda tarar.

```js
// ay.mjs — "bir ay sonra" iki kuralla; ve gun sayma kararinin sayfada nerede durdugu.
import { sayfa, deger, kaydir, seri, TALEP, SATIR } from "./takvim.mjs";

const H = sayfa();
const o = (K, f) => deger({ ...K, X9: f }, "X9");
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");
// Ocak okumasi ayin V. gunu; Subat okumasi "bir ay sonra". Iki kural iki tarih verir.
const KURAL = {
  kirpma: "=tarih(2026,2,enk(V2,gun(aysonu(tarih(2026,2,1)))))",
  tasma: "=tarih(2026,2,V2)",
};
const sutun = (K, c, n, f) => { const M = { ...K };
  for (let i = 0; i < n; i++) M[c + (i + 2)] = kaydir(f, 0, i);
  return Array.from({ length: n }, (_, i) => deger(M, c + (i + 2))); };
const OCAK = Array.from({ length: SATIR }, (_, i) => seri(2026, 1, H["V" + (i + 2)]));
const S = Object.fromEntries(Object.entries(KURAL).map(([a, f]) =>
  [a, sutun(H, "Y", SATIR, f)]));

const y = g([10, 8, 14, 10, 14, 10]);
console.log(y(["hucre", "okuma", "kirpma", "donem", "tasma", "donem"]));
for (const s of [2, 6, 15, 24]) { const i = s - 2;
  console.log(y(["V" + s, H["V" + s], o(H, `=bicim(${S.kirpma[i]})`), S.kirpma[i] - OCAK[i],
    o(H, `=bicim(${S.tasma[i]})`), S.tasma[i] - OCAK[i]])); }

const farkli = S.kirpma.filter((s, i) => s !== S.tasma[i]).length;
const kacan = S.tasma.filter((s) => o(H, `=ay(${s})`) !== 2).length;
console.log(`\n${SATIR} abonenin ${farkli} tanesinde iki kural farkli tarih veriyor ` +
  `(okuma gunu 29, 30 ya da 31 olanlar). Tasma kuralinda ${kacan} abonenin ` +
  `"Subat okumasi" Subat ayina dusmuyor.`);

// Metin olarak girilmis tarih: gun ile ay yer degistirebiliyorsa hucre iki gune okunur.
const ikili = Array.from({ length: TALEP }, (_, i) =>
  [H["T" + (i + 2)], o(H, `=ay(T${i + 2})`), o(H, `=gun(T${i + 2})`)])
  .filter(([, a, gn]) => gn <= 12 && gn !== a)       // gun 12'den buyukse yer degistiremez
  .map(([s, a, gn]) => Math.abs(seri(2026, gn, a) - s));
console.log(`${TALEP} acilis tarihinin ${ikili.length} tanesi "GG.AA.YYYY" metni olarak ` +
  `girildiginde iki ayri gune okunabiliyor; en buyuk sapma ${Math.max(...ikili)} gun.`);

// Geri okuma taramasi: gun sayma karari formul metninde nerede duruyor.
const P = { ...H, X1: "uc dahil (1/0)", X2: 1 };    // etiket solda, deger sagda
const YAZIM = [["=U2-T2", "yok"], ["=U2-T2+1", "gomulu sabit"],
  ["=U2-T2+$X$2", "parametre hucresi"], ["=isgunu(T2,U2)", "islev adi"]];
const z = g([16, 20, 10, 10, 10]), top = (d) => d.reduce((x, v) => x + v, 0);
console.log("\n" + z(["formul", "karar nerede", "toplam", "X2=0", "pesinden"]));
for (const [f, yer] of YAZIM) {
  const a = sutun(P, "W", TALEP, f), b = sutun({ ...P, X2: 0 }, "W", TALEP, f);
  console.log(z([f, yer, top(a), top(b), a.filter((v, i) => v !== b[i]).length]));
}
```

```
hucre        okuma        kirpma     donem         tasma     donem
V2               1    01.02.2026        31    01.02.2026        31
V6              29    28.02.2026        30    01.03.2026        31
V15             30    28.02.2026        29    02.03.2026        31
V24             31    28.02.2026        28    03.03.2026        31

60 abonenin 6 tanesinde iki kural farkli tarih veriyor (okuma gunu 29, 30 ya da 31 olanlar). Tasma kuralinda 6 abonenin "Subat okumasi" Subat ayina dusmuyor.
54 acilis tarihinin 19 tanesi "GG.AA.YYYY" metni olarak girildiginde iki ayri gune okunabiliyor; en buyuk sapma 323 gun.

formul                  karar nerede    toplam      X2=0  pesinden
=U2-T2                           yok       342       342         0
=U2-T2+1                gomulu sabit       396       396         0
=U2-T2+$X$2        parametre hucresi       396       342        54
=isgunu(T2,U2)             islev adi       281       281         0
```

Birinci tablo iki kuralın bedelini yan yana koyuyor. Kırpma kuralında ayın 29, 30 ve 31'inde
okunan üç abone 28 Şubat'a toplanıyor ve dönemleri 30, 29 ve 28 gün oluyor: aynı tarihte okunan
üç sayacın dönemi üç ayrı uzunlukta ve sayfada bunu söyleyen bir sütun yok. Taşma kuralında dönem
31 gün, ama Şubat okuması 1, 2 ve 3 Mart'a kayıyor: **6 abonenin Şubat okuması Şubat ayında
değil.**

Metin tarih satırı üçüncü bir belirsizlik ölçüyor. 54 açılışın 19'unda gün numarası 12'den
küçüktür; böyle bir tarih `GG.AA.YYYY` metni olarak yazıldığında ay ile gün yer değiştirebilir
ve hücre iki ayrı güne okunur — en büyük sapma 323 gün. **Metne çevrilmiş bir tarih geri
okunamaz bir karardır.**

Son tablo dersin ikinci sayısıdır. İlk iki yazım kararı sayfada tutmuyor: `=U2-T2` uçların
hariç tutulduğunu söylemez, `=U2-T2+1` bir `1` gösterir ama o `1`'in ne olduğunu söylemez.
Üçüncüde karar `X2` hücresindedir, etiketiyle iki adımda okunur ve değiştirildiğinde 54 hücre
peşinden gelir; dördüncüde karar işlevin adındadır. İlk ikisinin sonucu 342 ile 396; ikisi de
doğru, ikisi de kaynaksız.

#### Özet

- Tarih hücrede bir sayıdır; biçim o sayının bir yorumudur ve içeriğini değiştirmez.
- Aynı 54 talebin ortalama çözüm süresi üç sayma kararıyla 6,33, 7,33 ve 5,20 gün çıktı;
  fark en küçük değerin yüzde 41'i kadar.
- Sıfır bir süre değil, bir sayma sonucudur: uçlar hariç sayımda 6 talep, iş günü sayımında
  hafta sonuna denk gelen 2 talep sıfır gösterdi.
- "Bir ay sonra" ay sonunda iki ayrı tarih verir: kırpma kuralı 28, 29 ve 30 günlük üç dönem
  üretti, taşma kuralı 31 gün verdi ama 6 abonenin Şubat okumasını Mart'a kaydırdı.
- Gün sayma kararı yalnız parametre hücresine ya da işlev adına yazıldığında geri okunur;
  gömülü `+1` sayıyı gösterir, kararı göstermez. Metne çevrilmiş 19 tarih hiç geri okunamaz.

#### Sonraki Adım

Buraya kadar her formül aynı satıra bakıyordu. Oysa önceki ders bir şeyi varsaymıştı: ziyaret
kaydındaki kodun abone tablosunda **bulunması** ve karşılığındaki tüketimin **getirilmesi**. O
işi yapan şey bir arama işlevidir ve arama, kendisine verilen sütun numarasına ya da başlık
adına göre iki ayrı biçimde yazılabilir. İki biçim aynı sonucu verir; ta ki tablonun ortasına
bir sütun eklenene kadar. Sonraki ders bir sütun eklendiğinde kaç aramanın kırıldığını, kaçının
hata verdiğini ve kaçının hata vermeden yanlış değer döndürdüğünü sayar.

### Arama İşlevleri

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/arama-islevleri

Aynı arama dört yazımla kuruluyor ve tablonun ortasına bir sütun eklenince yalnız biri kırılıyor: sütun numarası gömülü sabit olan dikey arama 48 kaydın 48'inde yanlış değer döndürüyor, hiçbirinde hata vermiyor ve toplam 1204,8 m³ yerine 1084,6 m³ gösteriyor. Eklenen sütun makul olmayan bir sayı taşısaydı aynı kırılma 201081,0 m³ ile görünür olurdu. Tablo bölgeye göre yeniden sıralandığında yaklaşık eşleşme 41 kaydı sessizce değiştiriyor ve o kararı taşıyan tek şey formüldeki bir sıfır.

Önceki iki ders bir şeyi varsaymıştı: ziyaret kaydındaki kodun abone tablosunda bulunması ve
karşılığındaki tüketimin getirilmesi. O işi yapan şey bir arama işlevidir ve arama iki soruyu
ayrı cevaplar: **hangi satır**, **hangi sütun**. İkinciye ya bir sayı ya bir başlık adı cevap
verir ve bu cevap dersin tamamıdır. Aynı arama dört biçimde yazılır, dördü de aynı sayıyı
üretir; sonra bir sütun eklenir.

- **TT19.** Bir sütun eklendiğinde **hücre başvuruları kayar**, formül metnindeki **sayı
  sabitleri kaymaz**. Modelin merkezindeki kural budur ve `sutunEkle` işlevinde yazılıdır.
- **TT20.** Tek satırlık ya da tek sütunluk aralık düz dizi, dikdörtgen aralık satır dizisi
  olarak açılır. `dikeyara` anahtarı aralığın **ilk sütununda** arar.
- **TT21.** Ziyaret kodları önceki dersin temizleme kararından geçmiş sayılır; burada ölçülen
  tek şey aramadır.

#### Aramanın İki Yazımı

Aşağıdaki blok beş sütunlu abone tablosunu, ziyaret listesini ve iki arama yazımını kurar.

```js
// arama.mjs — elektronik tablo MODELI: arama islevleri ve sutun ekleme.
// Sutun eklendiginde HUCRE BASVURULARI kayar, formuldeki SAYI SABITLERI kaymaz.
export const TOHUM = 20260602, SATIR = 60, ZIYARET = 48;
const uretec = (t) => { let s = t >>> 0;
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const BOLGE = ["kuzey", "guney", "dogu", "bati", "merkez"];

const REF = /(\$?)([A-Z])(\$?)(\d+)/g;
const coz = (a) => ({ s: a.charCodeAt(a[0] === "$" ? 1 : 0) - 65, r: +a.replace(/\D/g, "") });
const adi = (s, r) => String.fromCharCode(65 + s) + r;
const ARALIK = /(\$?[A-Z]\$?\d+):(\$?[A-Z]\$?\d+)/g;
// Tek satirlik ya da tek sutunluk aralik duz dizi, dikdortgen aralik satir dizisi olarak acilir.
const ac = (a, b, H) => { const x = coz(a), y = coz(b), l = [];
  const tek = x.s === y.s || x.r === y.r;
  for (let r = x.r; r <= y.r; r++) { const s = [];
    for (let c = x.s; c <= y.s; c++) s.push(deger(H, adi(c, r)));
    if (tek) l.push(...s); else l.push(s); }
  return l; };
export const kaydir = (f, dS, dR) => f.replace(REF, (_, a, s, b, r) =>
  (a ? "$" + s : String.fromCharCode(s.charCodeAt(0) + dS)) + (b ? "$" + r : String(+r + dR)));

const bul = (k, d, tur) => {
  if (tur === 0) return d.indexOf(k) + 1;              // tam eslesme: yoksa 0
  let i = 0;                                           // yaklasik eslesme: son k'dan buyuk olmayan
  for (let j = 0; j < d.length; j++) if (d[j] <= k) i = j + 1;
  return i;
};
const ISLEV = {
  eslesme: (k, d, tur) => bul(k, d, tur),
  dizin: (d, s, c) => (c === undefined ? d[s - 1] : d[s - 1][c - 1]),
  dikeyara: (k, T, n, tur) => { const i = bul(k, T.map((r) => r[0]), tur);
    return i ? T[i - 1][n - 1] : "#YOK"; },
};
export function deger(H, ad) {
  const v = H[ad];
  if (v === undefined) return 0;
  if (typeof v !== "string" || v[0] !== "=") return v;
  const ifade = v.slice(1)
    .replace(ARALIK, (_, a, b) => JSON.stringify(ac(a, b, H)))
    .replace(REF, (m) => JSON.stringify(deger(H, m.replace(/\$/g, ""))));
  return Function(...Object.keys(ISLEV), `return (${ifade});`)(...Object.values(ISLEV));
}

// Sutun ekleme: konumdaki ve sagindaki hucreler bir sutun kayar, formullerdeki basvurular da
// kayar. Formul metnindeki SAYI SABITLERI degismez — bu dersin tamami bu farkin uzerindedir.
export function sutunEkle(H, konum, baslik, uret) {
  const k = konum.charCodeAt(0) - 65, Y = {};
  const kay = (f) => f.replace(REF, (_, d1, s, d2, r) => { const c = s.charCodeAt(0) - 65;
    return (d1 ? "$" : "") + adi(c >= k ? c + 1 : c, "") + (d2 ? "$" : "") + r; });
  for (const [ad, v] of Object.entries(H)) {
    const c = ad.charCodeAt(0) - 65, r = ad.slice(1);
    Y[adi(c >= k ? c + 1 : c, r)] =
      typeof v === "string" && v[0] === "=" ? "=" + kay(v.slice(1)) : v;
  }
  Y[konum + "1"] = baslik;
  for (let i = 0; i < SATIR; i++) Y[konum + (i + 2)] = uret(i);
  return Y;
}

export const YAZIM = [
  ["dikey arama, sutun no 3", "=dikeyara(P2,$A$2:$E$61,3,0)"],
  ["dikey arama, basliktan", '=dikeyara(P2,$A$2:$E$61,eslesme("tuketim",$A$1:$E$1,0),0)'],
  ["eslesme-dizin, sutuna", "=dizin($C$2:$C$61,eslesme(P2,$A$2:$A$61,0))"],
  ["eslesme-dizin, basliktan",
    '=dizin($A$2:$E$61,eslesme(P2,$A$2:$A$61,0),eslesme("tuketim",$A$1:$E$1,0))'],
];

export function sayfa() {
  const r = uretec(TOHUM), H = {};
  H.A1 = "abone"; H.B1 = "bolge"; H.C1 = "tuketim"; H.D1 = "okuma"; H.E1 = "tutar";
  for (let i = 0; i < SATIR; i++) {
    const s = i + 2;
    H["A" + s] = "AB-" + String(1041 + i * 7).padStart(4, "0");
    H["B" + s] = BOLGE[Math.floor(r() * 5)];
    const t = Math.round((2 + r() * r() * 78) * 10) / 10;
    H["C" + s] = t; H["D" + s] = Math.round(t); H["E" + s] = Math.round((t * 8.4 + 25) * 100) / 100;
  }
  // Ziyaret tablosu: kodlar temizlenmis (onceki ders), her kayit bir aboneye ait.
  const z = uretec(TOHUM + 2);
  const sira = Array.from({ length: SATIR }, (_, i) => i);
  for (let i = SATIR - 1; i > 0; i--) { const j = Math.floor(z() * (i + 1));
    [sira[i], sira[j]] = [sira[j], sira[i]]; }
  H.P1 = "ziyaret kodu";
  for (let i = 0; i < ZIYARET; i++) H["P" + (i + 2)] = H["A" + (2 + sira[i])];
  return H;
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const H = sayfa();
  const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
    : String(v).padEnd(B[0]))).join("");
  const y = g([8, 12, 10, 10, 12]);
  console.log(y(["hucre", "abone", "bolge", "tuketim", "tutar"]));
  for (const s of [2, 3, 61])
    console.log(y(["A" + s, H["A" + s], H["B" + s], H["C" + s], H["E" + s]]));
  console.log(`\nBasliklar A1:E1 = ${[..."ABCDE"].map((c) => H[c + "1"]).join(", ")}; ` +
    `"tuketim" 3. sutun. Ziyaret kodu P2 = ${H.P2}.`);
  for (const [ad, f] of YAZIM.slice(0, 2))
    console.log(`${ad}\n  ${f}\n  -> ${deger({ ...H, W2: f }, "W2")} m3`);
}
```

```
hucre          abone     bolge   tuketim       tutar
A2           AB-1041     guney       9.8      107.32
A3           AB-1048      dogu      71.6      626.44
A61          AB-1454      dogu      21.2      203.08

Basliklar A1:E1 = abone, bolge, tuketim, okuma, tutar; "tuketim" 3. sutun. Ziyaret kodu P2 = AB-1286.
dikey arama, sutun no 3
  =dikeyara(P2,$A$2:$E$61,3,0)
  -> 28.9 m3
dikey arama, basliktan
  =dikeyara(P2,$A$2:$E$61,eslesme("tuketim",$A$1:$E$1,0),0)
  -> 28.9 m3
```

İki yazım da 28,9 m³ veriyor ve ikisi de aynı işlevi kullanıyor. Tek fark, "tüketim" sütununun
nasıl işaret edildiğidir. Birincisinde o sütun bir sayıdır: `3`. Sayının nereden geldiği formülde
yazmaz; yazan kişi başlıkları saymış ve saydığı sayıyı formüle koymuştur. İkincisinde o sütun bir
addır ve başlık satırında her seferinde yeniden aranır.

Bu noktada ayrım bir üslup farkı gibi görünür; sonuç karşılaştıran bir denetim ikisini ayıramaz.
Ayıran şey sayfanın **değişime** nasıl tepki verdiğidir.

#### Tablonun Ortasına Bir Sütun

Aşağıdaki blok dört yazımı kurar, sonra `B` ile `C` arasına "önceki dönem" sütununu ekler;
değerleri mevcut tüketimle aynı büyüklüktedir.

```js
// kir.mjs — ayni arama dort yazimla; tablonun ortasina bir sutun eklenince ne oluyor.
import { YAZIM, sayfa, deger, kaydir, sutunEkle, ZIYARET } from "./arama.mjs";

const H = sayfa();
const doldur = (K, f) => { const M = { ...K };
  for (let i = 0; i < ZIYARET; i++) M["W" + (i + 2)] = kaydir(f, 0, i);
  return M; };
// B ile C arasina "onceki donem" sutunu eklenir: degerleri tuketimle ayni buyuklukte.
const ONCEKI = (i) => Math.round(H["C" + (i + 2)] * 9) / 10;
const oku = (M, s) => Array.from({ length: ZIYARET }, (_, i) => deger(M, s + (i + 2)));
const say = (d) => d.filter((v) => typeof v === "number").reduce((a, b) => a + b, 0);
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const y = g([26, 14, 14, 10, 8, 10]);
console.log(y(["yazim", "toplam once", "toplam sonra", "degisen", "hata", "sessiz"]));
for (const [ad, f] of YAZIM) {
  const A = oku(doldur(H, f), "W");
  const E = sutunEkle(doldur(H, f), "C", "onceki donem", ONCEKI);
  const B = oku(E, "X");                              // W sutunu ekleme sonrasi X oldu
  const degisen = B.filter((v, i) => v !== A[i]).length;
  const hata = B.filter((v) => typeof v !== "number").length;
  console.log(y([ad, say(A).toFixed(1), say(B).toFixed(1), degisen, hata, degisen - hata]));
}
// Ayni kirilma, eklenen sutun makul olmayan bir sayi tasidiginda gorunur hale gelir.
console.log("");
for (const [ad, uret] of [["onceki donem", ONCEKI], ["sayac no", (i) => 4100 + i * 3]]) {
  const B = oku(sutunEkle(doldur(H, YAZIM[0][1]), "C", ad, uret), "X");
  console.log(`Eklenen sutun "${ad}" ise kirik arama toplami ${say(B).toFixed(1)} m3, ` +
    `ilk deger ${B[0]}.`);
}
```

```
yazim                        toplam once  toplam sonra   degisen    hata    sessiz
dikey arama, sutun no 3           1204.8        1084.6        48       0        48
dikey arama, basliktan            1204.8        1204.8         0       0         0
eslesme-dizin, sutuna             1204.8        1204.8         0       0         0
eslesme-dizin, basliktan          1204.8        1204.8         0       0         0

Eklenen sutun "onceki donem" ise kirik arama toplami 1084.6 m3, ilk deger 26.
Eklenen sutun "sayac no" ise kirik arama toplami 201081.0 m3, ilk deger 4205.
```

Dört yazımdan biri kırıldı: 48 kaydın **48'i** değişti ve hiçbiri hata vermedi. Kırılan yazım
hâlâ bir sayı döndürüyor ve sayı hâlâ bir tüketim büyüklüğünde. Rapor 1204,8 yerine 1084,6 m³
gösteriyor; fark yüzde 10, bir denetim toplamının onaylayacağı kadar.

Kırılmanın nedeni bir işlev değil, bir **sabittir**: sütun numarasını başlıktan okuyan ikinci
yazım aynı işlevi kullanıyor ve kırılmadı. **Bir sütunun kaçıncı olduğu bir olgu değil, bir
andır**; sütun eklenince o an geçer, sayı kalır.

Son iki satır hatanın görünürlüğünü ölçüyor. Eklenen sütun sayaç numarası taşısaydı aynı kırık
formül 201081,0 m³ üretip yakalanacaktı; önceki dönem tüketimi taşıdığında 1084,6 m³ üretiyor ve
yakalanmıyor. **Bir hatanın görünürlüğü büyüklüğüyle değil, benzediği şeyle ölçülür.**

#### Sıralama Değişince

Aramanın ikinci sabiti son değişkendir: tam eşleşme mi, yaklaşık mı. Aşağıdaki blok bu kararı
iki sıralama altında ölçer, sonra dört yazımı tarar.

```js
// sirala.mjs — yaklasik eslesme siralamaya bagimlidir; ve kararin formulden geri okunmasi.
import { YAZIM, sayfa, deger, kaydir, SATIR, ZIYARET } from "./arama.mjs";

const H = sayfa();
// Tablo bolgeye gore yeniden siralanir: satirlar yer degistirir, aralik adlari degismez.
const siralanmis = (K) => {
  const s = Array.from({ length: SATIR }, (_, i) => i + 2)
    .sort((a, b) => (K["B" + a] < K["B" + b] ? -1 : K["B" + a] > K["B" + b] ? 1
      : K["A" + a] < K["A" + b] ? -1 : 1));
  const Y = { ...K };
  s.forEach((eski, j) => { for (const c of "ABCDE") Y[c + (j + 2)] = K[c + eski]; });
  return Y;
};
const doldur = (K, f) => { const M = { ...K };
  for (let i = 0; i < ZIYARET; i++) M["W" + (i + 2)] = kaydir(f, 0, i);
  return M; };
const oku = (M) => Array.from({ length: ZIYARET }, (_, i) => deger(M, "W" + (i + 2)));
const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j])
  : String(v).padEnd(B[0]))).join("");

const S = siralanmis(H);
const DOGRU = oku(doldur(H, "=dikeyara(P2,$A$2:$E$61,3,0)"));
const y = g([20, 16, 10, 10, 8]);
console.log(y(["tablo siralamasi", "son degiskene", "toplam", "yanlis", "hata"]));
for (const [ad, K] of [["abone koduna gore", H], ["bolgeye gore", S]])
  for (const [tur, son] of [["0 tam eslesme", "0"], ["1 yaklasik", "1"]]) {
    const d = oku(doldur(K, `=dikeyara(P2,$A$2:$E$61,3,${son})`));
    const t = d.filter((v) => typeof v === "number").reduce((a, b) => a + b, 0);
    console.log(y([ad, tur, t.toFixed(1), d.filter((v, i) => v !== DOGRU[i]).length,
      d.filter((v) => typeof v !== "number").length]));
  }

// Geri okuma taramasi: formul metnindeki gomulu sayilar ile baslik adlari sayilir.
const yer = (f) => /"[a-z]+"/.test(f) ? "baslik adi"
  : /dizin\(\$[A-Z]\$\d+:\$[A-Z]\$\d+,/.test(f) ? "hucre araligi" : "gomulu sayi";
const z = g([26, 16, 8, 14]);
console.log("\n" + z(["yazim", "sutun karari", "sabit", "eslesme turu"]));
for (const [ad, f] of YAZIM) {
  const kalan = f.replace(/\$?[A-Z]\$?\d+/g, "");     // aralik ve hucre adlari sayilmaz
  const sabit = (kalan.match(/\d+/g) ?? []);
  console.log(z([ad, yer(f), sabit.length, sabit.at(-1) + " (gomulu)"]));
}
```

```
tablo siralamasi       son degiskene    toplam    yanlis    hata
abone koduna gore      0 tam eslesme    1204.8         0       0
abone koduna gore         1 yaklasik    1204.8         0       0
bolgeye gore           0 tam eslesme    1204.8         0       0
bolgeye gore              1 yaklasik     961.8        41       0

yazim                         sutun karari   sabit  eslesme turu
dikey arama, sutun no 3        gomulu sayi       2    0 (gomulu)
dikey arama, basliktan          baslik adi       2    0 (gomulu)
eslesme-dizin, sutuna        hucre araligi       1    0 (gomulu)
eslesme-dizin, basliktan        baslik adi       2    0 (gomulu)
```

Birinci tablonun ilk iki satırı bir tuzak kuruyor: abone koduna göre sıralı bir tabloda tam
eşleşme ile yaklaşık eşleşme **aynı** sonucu verir, yani seçen kişi hiçbir fark görmez. Üçüncü ve
dördüncü satırlar sıralamayı bölgeye çeviriyor — bölge kırılımına bakmak isteyen birinin
yapacağı sıradan bir iş. Tam eşleşme kıpırdamıyor; yaklaşık eşleşme 48 kaydın **41'inde** başka
bir satır döndürüyor, toplam 961,8 m³'e iniyor ve yine **hata yok.**

İkinci tablo kursun ölçüsünü derse uyguluyor. Sütun kararı üç yerde durabiliyor: gömülü bir
sayıda, bir hücre aralığında, bir başlık adında — ve kırılan tek yazım, kararı gömülü bir sayı
olandır. Ama son sütun daha rahatsız edici bir şey söylüyor: **dördünde de eşleşme türü gömülü
bir sabittir.** O `0` bir `1` olsaydı hiçbir hata çıkmayacak, tablo yeniden sıralandığı gün 41
kayıt sessizce değişecekti.

Kursun ilk dersinden beri sürdürülen kural burada tamamlanıyor: **hücreye gömülü karar,
yazılmamış karardır.** Sütun numarası da eşleşme türü de birer karardır ve ikisi de bir sayı
kılığında duruyor.

#### Özet

- Arama iki soruyu ayrı cevaplar — hangi satır, hangi sütun; ikinci cevap bir sayı da bir başlık
  adı da olabilir ve ikisi aynı sonucu üretir.
- Sütun eklendiğinde hücre başvuruları kayar, sayı sabitleri kaymaz: dört yazımdan yalnız biri
  kırıldı, 48 kaydın 48'inde yanlış değer döndürdü ve hiç hata vermedi.
- Toplam 1204,8 yerine 1084,6 m³ oldu; aynı kırılma başka bir sütunla 201081,0 m³ üretip
  görünür olurdu. Kıran şey işlev değil, gömülü sütun numarasıdır.
- Yaklaşık eşleşme sıralamaya bağımlıdır ve tablo sıralı olduğu sürece bunu belli etmez: bölgeye
  göre sıralandıktan sonra 41 kayıt sessizce değişti.
- Dört yazımın dördünde de eşleşme türü gömülü bir sayıdır; kursun kuralı burada tamamlanır —
  hücreye gömülü karar, yazılmamış karardır.

#### Sonraki Adım

Bu konu altı derste hücre düzeyindeki kararları ölçtü: başvuru türü, sayma ölçütü, koşul sınırı,
temizleme kuralı, gün sayma kuralı, arama biçimi. Her derste bir sayı ile onu üreten karar yan
yana duruldu ve kararın kaç adımda geri okunduğu sayıldı. Ama bütün bu ölçümler **tek tek
hücrelere** bakıyordu; hiçbiri tablonun bütününe bakan bir sayı üretmedi.

Daha temel bir soru da hiç sorulmadı: **bu tablo temiz mi.** Altı ders boyunca 60 satırın 60 ayrı
abone olduğu ve her satırın bir kez göründüğü varsayıldı. Sonraki konu iki eksiği birlikte ele
alır: tekilleştirme ölçütü değişince kaç satırın kaldığını ve eksik değer kararlarının aynı
ortalamaya kaç değer verdiğini ölçer, sonra tablonun bütününe bakan özetleri kurar.

## Özet ve Temizlik

Tablonun bütününe bakan sayıların hangi kararlarla üretildiğinin ölçülmesi: tekilleştirme ölçütü değişince kalan satır sayısı ve kaybolan kayıt, doldurma ile dışlamanın aynı ortalamaya verdiği üç değer ve boş hücre ile sıfırın ayrılması, kırılım değiştiğinde iki farklı birinciyi veren özet tablo, ölçeğe uymayan grafik sayısı ile eksen başlangıcının farkı kaç kat büyük gösterdiği, kursta alınan kararlardan kaçının tablodan geri okunabildiği.

### Yinelenen Kayıtların Kaldırılması

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/yinelenen-kayitlarin-kaldirilmasi

Aynı 2.037 satırlık sayfaya beş tekilleştirme ölçütü uygulanır ve beş farklı sayı kalır: 2.005, 1.400, 1.877, 1.965 ve yine 1.877. Ölçüt yalnız abone numarası olduğunda 637 satır silinir ve toplam 42.645,6 m³'ten 29.828,2 m³'e iner, çünkü silinen satırların çoğu yinelenme değil ikinci dönemdir; metin kırpma kararı tek başına 88 satır oynatır. Aynı ölçütle ilk giriş yerine son giriş tutulduğunda satır sayısı 1.877'de sabit kalır ama toplam 200,1 m³ ayrışır. İşlem bitince silinmiş sayfadan dört kararın sıfırı, not düşülmüş sayfadan ikisi iki adımda, izli sayfadan dördü bir adımda geri okunur.

Bir önceki konu tek tek hücreleri doğru hesaplamayı kurdu: başvuru türü, koşullu toplama, sınır
karşılaştırması, metin kırpma, gün sayma ve arama. Hepsinin ortak sınırı şuydu — her formül bir
hücreye bakıyordu ve tablonun bütününe bakan hiçbir sayı üretilmedi. Verinin kendisinin temiz olup
olmadığı da hiç sorulmadı.

Bu ders tablonun bütününe bakan ilk işlemi yapar: yinelenen kayıtları kaldırmak. İşlem tek bir
komut gibi görünür, ama içinde en az üç karar vardır. Hangi sütunlar aynı olursa iki satır aynı
sayılır. Metin karşılaştırılırken görünmeyen boşluk ve harf farkı yok sayılır mı. Aynı sayılan iki
satırdan hangisi kalır. Üçü de rapor edilen sayıyı oynatır ve üçü de işlem bittikten sonra tablodan
okunamaz. Dersin iki sayısı budur: **kalan satır sayısı** ve **kaç kararın geri okunabildiği**.

- **TO1.** Elektronik tablo `node` ile **modellenir**: hücre bir eşlemede durur, formül kendi
  metnini taşıyan küçük bir değerlendiricidir, aralık bir dilim işlevidir. Veri kümesi de
  **kurgudur** — önceki kursun 1.400 abonesi, saha ziyaret kayıtlarına açılmış halidir.
- **TO2.** Yinelenme iki ayrı nedenden doğar: aynı abone ve dönem çiftinin ikinci kez girilmesi
  ve bir abonenin iki dönemde okunması. İkincisi yinelenme değildir; ölçüt bu ikisini ayırt
  etmezse silme işlemi veri siler.
- **TO3.** Sayaç numarası saha ziyaretinde elle girilir, bu yüzden baştaki boşluğu ve küçük harfi
  taşıyabilir. Metin karşılaştırması kırpma ve harf birliği kararına bağlıdır.
- **TO4.** Kararın kurtarılabilirliği sayfa taranarak ölçülür: karar bir hücre formülünde yazılıysa
  **bir adımda** okunur, ayrı bir not hücresinde duruyorsa **iki adım** ister — önce not bulunur,
  sonra hangi sütuna ait olduğu eşleştirilir — hiçbir hücrede yoksa **okunamaz**.

#### Ölçüt Değişince Kaç Satır Kalıyor

```js
// sayfa.mjs — elektronik tablo MODELIDIR: hucreler bir eslemede durur, formul
// kucuk bir degerlendiricidir, aralik bir dilim islevidir. Hicbir uygulama
// taklit edilmez; isler gorev adiyla anilir. Veri kumesi kurgudur.
export const TOHUM = 20260218, ABONE = 1400;
const karistir = (x) => { x = Math.imul(x ^ (x >>> 16), 2246822507);
  x = Math.imul(x ^ (x >>> 13), 3266489909); return (x ^ (x >>> 16)) >>> 0; };
const uretec = (t) => { let s = karistir(t);
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const ayrik = (u, w) => { let t = 0;
  for (let i = 0; i < w.length; i++) { t += w[i]; if (u < t) return i; } return w.length - 1; };
export const BOLGE = [["kuzey", "KZ", 0.28, 21], ["guney", "GN", 0.22, 17],
  ["dogu", "DG", 0.18, 26], ["bati", "BT", 0.14, 14], ["merkez", "MR", 0.18, 23]]
  .map(([ad, kod, pay, taban]) => ({ ad, kod, pay, taban }));

// Elektronik tablo modeli. Hucre adi harf+sayidir; deger ya duz bir degerdir
// ya da bir formuldur. Formul kendi metnini tasir: karar orada yazilidir.
const HARF = (j) => String.fromCharCode(65 + j);
export const formul = (yazi, f) => ({ tur: "formul", yazi, f });
export class Tablo {
  constructor(baslik, satirlar) {
    this.baslik = baslik; this.h = new Map(); this.n = satirlar.length;
    baslik.forEach((b, j) => this.h.set(`${HARF(j)}1`, b));
    satirlar.forEach((s, i) => baslik.forEach((b, j) =>
      this.h.set(`${HARF(j)}${i + 2}`, s[b])));
  }
  oku(ad) { const v = this.h.get(ad); return v && v.tur === "formul" ? v.f(this) : v; }
  yaz(ad, v) { this.h.set(ad, v); return this; }
  harf(b) { return HARF(this.baslik.indexOf(b)); }
  aralik(a) { const [s, b] = a.split(":");
    const j = s.charCodeAt(0) - 65, i0 = +s.slice(1), i1 = +b.slice(1);
    return Array.from({ length: i1 - i0 + 1 }, (_, k) => this.oku(`${HARF(j)}${i0 + k}`)); }
  sutun(b) { return this.aralik(`${this.harf(b)}2:${this.harf(b)}${this.n + 1}`); }
  satirlar() { return Array.from({ length: this.n }, (_, i) =>
    Object.fromEntries(this.baslik.map((b) => [b, this.oku(`${this.harf(b)}${i + 2}`)]))); }
}

// Saha ziyaret kayitlari: abone basina bir ya da iki donem, bir bolumu elle
// giris yuzunden iki kez yazilmis.
const SATIR = [];
for (let i = 0; i < ABONE; i++) {
  const r = uretec(TOHUM + i);
  const b = BOLGE[ayrik(r(), BOLGE.map((x) => x.pay))];
  const taban = Math.max(1.2, b.taban * Math.exp((r() + r() + r() - 1.5) * 0.62));
  const sayac = `${b.kod}-${String(1000 + i).padStart(4, "0")}`;
  const donemler = r() < 0.34 ? ["2026-01", "2026-02"] : ["2026-01"];
  for (const d of donemler) {
    const t = Math.round(taban * (d === "2026-02" ? 0.94 : 1) * 100) / 100;
    SATIR.push({ abone_no: 10001 + i, sayac_no: sayac, bolge: b.ad, donem: d,
      tuketim_m3: t });
    // Cift giris: ayni abone ve donem ikinci kez yazilir. Sayac numarasi bu kez
    // bosluklu ya da kucuk harfli girilmis olabilir; okuma duzeltilmis olabilir.
    if (r() < 0.09) {
      const bicim = ayrik(r(), [0.34, 0.28, 0.38]);
      const duzeltme = r() < 0.45;
      SATIR.push({ abone_no: 10001 + i,
        sayac_no: bicim === 0 ? ` ${sayac}` : bicim === 1 ? sayac.toLowerCase() : sayac,
        bolge: b.ad, donem: d,
        tuketim_m3: duzeltme ? Math.round(t * 1.12 * 100) / 100 : t });
    }
  }
}
export const SAYFA = new Tablo(
  ["abone_no", "sayac_no", "bolge", "donem", "tuketim_m3"], SATIR);

// Tekillestirme olcutu: satirdan bir anahtar metni uretir. Olcut degisince
// ayni sayfadan farkli sayida satir kalir.
export const OLCUT = [
  ["O1 tam satir", (s) => Object.values(s).join("|")],
  ["O2 abone_no", (s) => `${s.abone_no}`],
  ["O3 abone_no + donem", (s) => `${s.abone_no}|${s.donem}`],
  ["O4 sayac_no + donem", (s) => `${s.sayac_no}|${s.donem}`],
  ["O5 sayac_no (kirpilmis) + donem",
    (s) => `${s.sayac_no.trim().toUpperCase()}|${s.donem}`],
];
export const tekille = (satirlar, anahtar, tut = "ilk") => {
  const g = new Map();
  for (const s of satirlar) { const a = anahtar(s);
    if (!g.has(a) || tut === "son") g.set(a, s); }
  return [...g.values()];
};
export const topla = (a) => a.reduce((x, y) => x + y, 0);

if (import.meta.url === `file://${process.argv[1]}`) {
  const satirlar = SAYFA.satirlar();
  console.log(`model (tohum ${TOHUM}): ${ABONE} abone, sayfada ${SAYFA.n} satir; ` +
    `ham toplam ${topla(SAYFA.sutun("tuketim_m3")).toFixed(1)} m3`);
  const y = (h) => String(h[0]).padEnd(32) + h.slice(1).map((v) => String(v).padStart(11)).join("");
  console.log("\n" + y(["tekillestirme olcutu", "kalan", "silinen", "toplam m3"]));
  const kalan = {};
  for (const [ad, f] of OLCUT) {
    const l = tekille(satirlar, f);
    kalan[ad.slice(0, 2)] = l;
    console.log(y([ad, l.length, SAYFA.n - l.length,
      topla(l.map((s) => s.tuketim_m3)).toFixed(1)]));
  }
  console.log(`\nen gevsek olcut ile en siki olcut arasinda ` +
    `${kalan.O1.length - kalan.O2.length} satir fark var; toplam ` +
    `${(topla(kalan.O1.map((s) => s.tuketim_m3)) -
      topla(kalan.O2.map((s) => s.tuketim_m3))).toFixed(1)} m3 oynuyor`);
}
```

```
model (tohum 20260218): 1400 abone, sayfada 2037 satir; ham toplam 42645.6 m3

tekillestirme olcutu                  kalan    silinen  toplam m3
O1 tam satir                           2005         32    41949.2
O2 abone_no                            1400        637    29828.2
O3 abone_no + donem                    1877        160    39147.7
O4 sayac_no + donem                    1965         72    40912.2
O5 sayac_no (kirpilmis) + donem        1877        160    39147.7

en gevsek olcut ile en siki olcut arasinda 605 satir fark var; toplam 12121.0 m3 oynuyor
```

Beş ölçüt beş sayı verdi ve hiçbiri hata üretmedi. **O1 tam satır** en güvenli görünen ölçüttür ve
en az işi yapar: 2.037 satırın yalnız 32'sini siler, çünkü ikinci kez girilmiş bir satırda okuma
düzeltilmişse iki satır birebir aynı değildir. İhtiyat burada sorunun 128 satırını olduğu gibi
bırakır.

**O2 abone_no** ise en temiz sonucu verir — 1.400 satır, abone başına tam bir satır — ve rapor
edilen toplamı 42.645,6 m³'ten 29.828,2 m³'e indirir. Silinen 637 satırın çoğu yinelenme değil,
ikinci dönemin okumasıdır. Sayfaya sonradan bakan biri 1.400 satır görür, sayının abone başına
tekil olduğunu doğrular ve **eksik olanın ne olduğunu göremez**: yanlış ölçüt bir hata mesajı
değil, düzgün görünen bir tablo üretir.

Kalan üç sayı metin kararını ölçüyor. O4 ham sayaç numarasıyla karşılaştırır ve 1.965 satır
bırakır; O5 aynı ölçütü kırpılmış ve harf birliğine getirilmiş metinle uygular ve 1.877'ye iner.
Aradaki **88 satır** tek bir kararın bedelidir. O3 ile O5'in aynı sayıyı vermesi ise ölçütlerin denk
olduğunu göstermez: bu kümede sayaç numarası abone başına tekil olduğu için çakışıyorlar, bir
hanenin ikinci sayacı eklendiğinde ayrışırlar.

#### Aynı Ölçüt, İki Tutma Kararı

```js
// olcut.mjs — ayni sayfa, bu kez iki soru: yinelenen grupta hangi satirin
// tutuldugu ve kararin sayfadan geri okunup okunamadigi.
import { SAYFA, OLCUT, Tablo, formul, tekille, topla } from "./sayfa.mjs";
const satirlar = SAYFA.satirlar();
const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j])
  : String(v).padEnd(w[0]))).join("");

// Ayni olcut, iki tutma karari: gruptaki ilk giris ya da son giris.
const y1 = y([32, 10, 9, 12]);
console.log("ayni olcut, iki tutma karari");
console.log(y1(["olcut", "tutulan", "kalan", "toplam m3"]));
for (const [ad, f] of OLCUT.slice(2, 4)) {
  for (const tut of ["ilk", "son"]) {
    const l = tekille(satirlar, f, tut);
    console.log(y1([ad, tut, l.length, topla(l.map((s) => s.tuketim_m3)).toFixed(1)]));
  }
}
const i3 = tekille(satirlar, OLCUT[2][1], "ilk"), s3 = tekille(satirlar, OLCUT[2][1], "son");
console.log(`O3 icin iki karar da ${i3.length} satir birakir, toplam ` +
  `${(topla(s3.map((s) => s.tuketim_m3)) -
    topla(i3.map((s) => s.tuketim_m3))).toFixed(1)} m3 ayrisir`);

// Tek bir abonenin izi: hangi satiri hangi olcut yutuyor.
const ornek = satirlar.filter((s) => s.abone_no === 10029);
const y2 = y([12, 10, 8, 6, 6, 6, 6, 6]);
console.log(`\nabone 10029 sayfada ${ornek.length} satir tutuyor`);
console.log(y2(["sayac_no", "donem", "m3", ...OLCUT.map((o) => o[0].slice(0, 2))]));
for (const s of ornek) {
  console.log(y2([`"${s.sayac_no}"`, s.donem, s.tuketim_m3,
    ...OLCUT.map(([, f]) => tekille(ornek, f).includes(s) ? "kalir" : "-")]));
}

// Kararin kurtarilabilirligi: formulde yaziliysa bir adim, ayri bir not
// hucresinde duruyorsa iki adim, hicbir hucrede yoksa okunamaz.
const KOD = ["K1 olcut sutunlari", "K2 metin kirpma", "K3 tutulan satir",
  "K4 silinen sayisi"];
const geriOku = (t) => KOD.map((k) => {
  const v = [...t.h.values()], n = k.slice(0, 2);
  const f = v.some((x) => x && x.tur === "formul" && x.yazi.includes(n));
  const not = v.some((x) => typeof x === "string" && x.startsWith("karar ") && x.includes(n));
  return f ? 1 : not ? 2 : null;
});

const silinmis = new Tablo(SAYFA.baslik, i3);
const notlu = new Tablo(SAYFA.baslik, i3)
  .yaz("G1", "karar K1 ve K3: yinelenenler abone ve doneme gore kaldirildi, ilk giris tutuldu");
const anahtar = (s) => `${s.abone_no}|${s.donem}`;
const gorulen = new Map();
const izli = new Tablo([...SAYFA.baslik, "tekil_anahtar", "grup_sirasi"],
  satirlar.map((s) => {
    const a = anahtar(s); gorulen.set(a, (gorulen.get(a) ?? 0) + 1);
    const sira = gorulen.get(a);
    return { ...s,
      tekil_anahtar: formul("K1 abone_no ve donem birlestirilir; K2 sayac_no " +
        "kirpilir ve harf birligine getirilir",
        () => `${s.abone_no}|${s.donem}|${s.sayac_no.trim().toUpperCase()}`),
      grup_sirasi: formul("K3 grupta ilk giris tutulur, sonrakiler isaretlenir",
        () => sira) };
  }));
izli.yaz("H2", formul("K4 grup_sirasi birden buyuk olan satir yinelenendir",
  (t) => t.sutun("grup_sirasi").filter((v) => v > 1).length));

const y3 = y([22, 16, 13, 12]);
console.log("\nkarar sayfadan geri okunabiliyor mu");
console.log(y3(["karar", "silinmis sayfa", "notlu sayfa", "izli sayfa"]));
const sonuc = [silinmis, notlu, izli].map(geriOku);
KOD.forEach((k, j) => console.log(y3([k,
  ...sonuc.map((s) => (s[j] ? `${s[j]} adim` : "okunamaz"))])));
console.log(["silinmis", "notlu", "izli"].map((ad, j) =>
  `${ad} ${sonuc[j].filter(Boolean).length}/${KOD.length}`).join(", ") +
  " karar geri okunabiliyor");
console.log(`izli sayfa ${izli.n} satir ve ${izli.oku("H2")} isaretli satir tutuyor; ` +
  `silinmis sayfa ${silinmis.n} satir tutuyor ve ${SAYFA.n - silinmis.n} satirin ` +
  `nereye gittigini soylemiyor`);
```

```
ayni olcut, iki tutma karari
olcut                              tutulan    kalan   toplam m3
O3 abone_no + donem                    ilk     1877     39147.7
O3 abone_no + donem                    son     1877     39347.8
O4 sayac_no + donem                    ilk     1965     40912.2
O4 sayac_no + donem                    son     1965     41023.3
O3 icin iki karar da 1877 satir birakir, toplam 200.1 m3 ayrisir

abone 10029 sayfada 3 satir tutuyor
sayac_no         donem      m3    O1    O2    O3    O4    O5
"KZ-1028"      2026-01   12.74 kalir kalir kalir kalir kalir
"kz-1028"      2026-01   14.27 kalir     -     - kalir     -
"KZ-1028"      2026-02   11.97 kalir     - kalir kalir kalir

karar sayfadan geri okunabiliyor mu
karar                   silinmis sayfa  notlu sayfa  izli sayfa
K1 olcut sutunlari            okunamaz       2 adim      1 adim
K2 metin kirpma               okunamaz     okunamaz      1 adim
K3 tutulan satir              okunamaz       2 adim      1 adim
K4 silinen sayisi             okunamaz     okunamaz      1 adim
silinmis 0/4, notlu 2/4, izli 4/4 karar geri okunabiliyor
izli sayfa 2037 satir ve 160 isaretli satir tutuyor; silinmis sayfa 1877 satir tutuyor ve 160 satirin nereye gittigini soylemiyor
```

İlk tablo kursun ölçüsünü açıkça gösteriyor. O3 ölçütü altında hangi satırın tutulduğu kararı
**satır sayısını hiç değiştirmez** — iki durumda da 1.877 satır kalır — ama toplam 200,1 m³
ayrışır. Sayfaya bakan biri satır sayısını doğrulayabilir ve doğru sayıyı bulur; toplamın hangi
tutma kararıyla üretildiğini ise doğrulayacak hiçbir şey yoktur. **Satır sayısı bir denetim ölçüsü
değildir.**

İkinci tablo tek bir abonenin üç satırını izliyor. Abone 10029'un ocak okuması iki kez girilmiştir
ve ikinci girişte hem sayaç numarası küçük harflidir hem de okuma 12,74'ten 14,27'ye kaymıştır;
üçüncü satır şubat okumasıdır. O2 ölçütü üç satırdan ikisini siler ve silinenlerden biri gerçek
yinelenme, biri **şubat okumasıdır**. O4 hiçbirini silmez, çünkü küçük harfli kod farklı bir metin
sayılır. O3 ile O5 doğru olanı yapar ve iki satır bırakır. Beş ölçüt aynı kayıt için üç farklı
sonuç veriyor ve hangisinin ne yaptığı yalnız satırlar hâlâ ortadayken görülebilir.

12,74 ile 14,27 arasındaki seçim ayrı bir karardır ve ölçütle ilgisi yoktur: ikinci giriş bir
düzeltme de olabilir, ikinci bir hatalı giriş de. Hangisinin doğru okuma olduğunu veri söylemez;
karar sayfanın dışındaki bir bilgiye dayanmak zorundadır.

#### Karar Nerede Duruyor

Üçüncü tablo dersin ikinci sayısıdır. **Silinmiş sayfa** 1.877 düzgün satır tutar ve dört kararın
**sıfırını** taşır. Tablo doğru görünür, çünkü tablonun yanlış görünmesini sağlayacak hiçbir şey
kalmamıştır.

**Not düşülmüş sayfa** dört karardan ikisini iki adımda geri verir. Notu yazan kişi ölçüt
sütunlarını ve tutma kararını yazmış, metin kırpmayı yazmamıştır — onu bir karar değil, bir temizlik
saymıştır. Oysa ölçülen fark tam da oradadır: kırpma 88 satır oynatır, tutma kararı sıfır satır.
Elle tutulan notlar **en sessiz kararı** kaçırır.

**İzli sayfa** 2.037 satırın hepsini bırakır, 160 satırı işaretler ve dört kararı da bir adımda
verir; kararlar hücrelerde formül olarak durduğu için sayfayı açan ikinci kişi onları okumak üzere
kimseye sormaz. Bedeli iki yerdedir: sayfa küçülmez ve bundan sonraki her özet işleminin işaret
sütununu hesaba katması gerekir. Bu bedel kursun kuralının fiyatıdır — **elektronik tablo bir hesap
aracıdır, bir kayıt aracı değildir**; kayıt ancak fazladan bir sütunla satın alınır.

#### Özet

- Tekilleştirme ölçütü satırdan üretilen bir anahtardır; aynı 2.037 satırlık sayfada beş ölçüt
  2.005, 1.400, 1.877, 1.965 ve 1.877 satır bırakır.
- Yalnız abone numarasına dayanan ölçüt en temiz tabloyu üretir ve yinelenme yerine ikinci dönemi
  siler: toplam 42.645,6 m³'ten 29.828,2 m³'e iner, hiçbir uyarı görünmez.
- Metin kırpma ve harf birliği kararı tek başına 88 satır oynatır.
- Aynı ölçüt altında ilk yerine son girişi tutmak satır sayısını 1.877'de sabit tutar ama toplamı
  200,1 m³ ayırır; satır sayısı bir denetim ölçüsü değildir.
- İşlem bittikten sonra dört karardan silinmiş sayfada sıfırı, not düşülmüş sayfada ikisi iki
  adımda, izli sayfada dördü bir adımda geri okunur.

#### Sonraki Adım

Tekilleştirme kalan satırların hepsinin dolu olduğunu varsaydı. Oysa 1.877 satırın bir bölümünde
tüketim hücresi boştur, bir bölümünde sıfır yazar ve elektronik tablo bu ikisini bazı işlemlerde
ayırır, bazılarında ayırmaz. Aynı sütunun ortalaması boş hücreler dışlandığında bir sayı, sıfır
sayıldığında başka bir sayı, sütun ortalamasıyla doldurulduğunda üçüncü bir sayı verir. Sonraki ders
bu üç kararı yan yana hesaplar ve doldurma bittikten sonra hangi hücrenin başlangıçta boş olduğunun
hâlâ bilinip bilinmediğini ölçer.

### Eksik Veriyle Baş Etme

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/eksik-veriyle-bas-etme

Aynı 1.877 satırlık sayfanın okuma sütununda 116 hücre boş, 112 hücre sıfırdır ve sıfırların 55'i gerçek sıfır tüketim değil, okunmamış bir ziyarettir; boş hücrelerin karşılığı olan 2.719,2 m³ raporlanan toplamın içindedir. Aynı sütunun ortalaması dört kararla 19,23, 18,04, 20,54 ve yine 19,23 m³ verir: en düşükle en yüksek arasında 2,49 m³, doldurma ise ortalamayı hiç oynatmadan toplamı 33.866,8'den 36.097,6 m³'e çıkarır. Boş hücre oranı bölgeler arasında yüzde 2,0 ile yüzde 17,3 arasında değişir ve boşlar sıfır sayılınca bir bölgenin ortalaması 3,94 m³ düşer. İşlem bitince dört karardan doldurulmuş sayfada sıfırı, not düşülmüş sayfada biri iki adımda, izli sayfada dördü bir adımda geri okunur.

Bir önceki ders sayfayı 1.877 satıra indirdi ve kalan satırların hepsinin dolu olduğunu varsaydı.
Varsayım tutmuyor. Okuma sütununda bir bölüm hücre boştur, bir bölümünde sıfır yazar ve elektronik
tablo bu ikisini her işlemde aynı saymaz: toplama işlevi boş hücreyi atlar, sıfırı toplar; ortalama
işlevi boş hücreyi hem bölünenden hem bölenden düşürür, sıfırı yalnız bölünenden.

Bu ders o sütunun ortalamasını hesaplar. Hesap tek satırlıktır, önünde en az üç karar durur: boş
hücre dışlanacak mı sıfır mı sayılacak mı doldurulacak mı, sıfır yazan hücreler gerçek sıfır tüketim
mi, doldurma hangi değerle yapılacak. Üçü de ortalamayı oynatır ve üçü de işlem bitince sayfadan
okunamaz. Dersin iki sayısı budur: **kararın verdiği ortalama** ve **kaç kararın geri okunabildiği**.

- **TO5.** Sayfa bir önceki dersin tekilleştirilmiş 1.877 satırıdır, ama okuma sütunu bu kez **saha
  formundaki haliyle** durur. Elektronik tablo yine `node` ile **modellenir**, veri kümesi
  **kurgudur**.
- **TO6.** Eksiklik üç biçimde görünür: ziyaret okuma üretmemiştir ve hücre **boştur**; sayaç
  mühürlüdür ve tüketim **gerçekten sıfırdır**; okuma alınamamıştır ama forma **sıfır
  yazılmıştır**.
- **TO7.** Toplama ve ortalama işlevleri boş hücreyi atlar; dışlama kararı bu yüzden **toplamı
  oynatmaz, ortalamayı oynatır**.
- **TO8.** Kararın kurtarılabilirliği ölçüsü sürer: karar bir hücre formülünde yazılıysa **bir
  adımda**, ayrı bir not hücresinde **iki adımda** okunur, hiçbir hücrede yoksa **okunamaz**.

#### Aynı Sütun, Dört Ortalama

```js
// eksik.mjs — elektronik tablo MODELIDIR: hucre bir eslemede durur, bos hucre
// hicbir deger tasimaz, formul kendi metnini tasir. Hicbir uygulama taklit
// edilmez. Veri kumesi kurgudur: onceki dersin tekillestirilmis 1877 satirlik
// sayfasi, bu kez okuma sutunu saha formundaki haliyle.
export const TOHUM = 20260218, ABONE = 1400;
const karistir = (x) => { x = Math.imul(x ^ (x >>> 16), 2246822507);
  x = Math.imul(x ^ (x >>> 13), 3266489909); return (x ^ (x >>> 16)) >>> 0; };
const uretec = (t) => { let s = karistir(t);
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const ayrik = (u, w) => { let t = 0;
  for (let i = 0; i < w.length; i++) { t += w[i]; if (u < t) return i; } return w.length - 1; };
// Son alan: ziyaretin okuma uretmeme orani. Bolgeler bu oranda ayrisir.
export const BOLGE = [["kuzey", 0.28, 21, 0.06], ["guney", 0.22, 17, 0.03],
  ["dogu", 0.18, 26, 0.27], ["bati", 0.14, 14, 0.03], ["merkez", 0.18, 23, 0.08]]
  .map(([ad, pay, taban, eksik]) => ({ ad, pay, taban, eksik }));

const HARF = (j) => String.fromCharCode(65 + j);
export const formul = (yazi, f) => ({ tur: "formul", yazi, f });
export class Tablo {
  constructor(baslik, satirlar) {
    this.baslik = baslik; this.h = new Map(); this.n = satirlar.length;
    baslik.forEach((b, j) => this.h.set(`${HARF(j)}1`, b));
    satirlar.forEach((s, i) => baslik.forEach((b, j) =>
      this.h.set(`${HARF(j)}${i + 2}`, s[b])));
  }
  oku(ad) { const v = this.h.get(ad); return v && v.tur === "formul" ? v.f(this) : v; }
  yaz(ad, v) { this.h.set(ad, v); return this; }
  sutun(b) { const j = this.baslik.indexOf(b);
    return Array.from({ length: this.n }, (_, i) => this.oku(`${HARF(j)}${i + 2}`)); }
}
// Toplama ve ortalama islevleri bos hucreyi atlar: ortalama hem boluneni hem
// boleni yalnizca dolu hucrelerden alir. Sifir dolu bir hucredir.
export const sayilar = (a) => a.filter((v) => typeof v === "number");
export const topla = (a) => sayilar(a).reduce((x, y) => x + y, 0);
export const ortala = (a) => { const d = sayilar(a); return d.length ? topla(d) / d.length : 0; };

// Onceki dersin tekil sayfasi: abone basina bir ya da iki donem, abone+donem
// olcutuyle zaten tekillestirilmis. Ziyaret sonucu ayri bir uretec akisindan
// gelir; okumalar degismez. erisilemedi -> hucre bos. kapali -> gercek sifir
// tuketim. form_sifir -> okuma alinamamis ama forma sifir yazilmis.
export const SATIR = [];
for (let i = 0; i < ABONE; i++) {
  const r = uretec(TOHUM + i);
  const b = BOLGE[ayrik(r(), BOLGE.map((x) => x.pay))];
  const taban = Math.max(1.2, b.taban * Math.exp((r() + r() + r() - 1.5) * 0.62));
  for (const d of r() < 0.34 ? ["2026-01", "2026-02"] : ["2026-01"]) {
    const q = uretec(TOHUM + 7000 + SATIR.length), u = q();
    const durum = u < b.eksik ? (q() < 0.72 ? "erisilemedi" : "form_sifir")
      : u < b.eksik + 0.035 ? "kapali" : "okundu";
    const gercek = Math.round(taban * (d === "2026-02" ? 0.94 : 1) * 100) / 100;
    SATIR.push({ abone_no: 10001 + i, bolge: b.ad, donem: d, ziyaret_sonucu: durum,
      okuma_m3: durum === "erisilemedi" ? null : durum === "okundu" ? gercek : 0,
      gercek_m3: gercek });
  }
}
// Ayni sutun, dort dislama ve doldurma karari. Her karar hesaba giren satir
// kumesini ve bolen'i degistirir.
export const sutunOrt = ortala(SATIR.map((s) => s.okuma_m3));
export const KARAR = [
  ["D1 bos dislanir, sifir sayilir", (l) => sayilar(l.map((s) => s.okuma_m3))],
  ["D2 bos sifir sayilir", (l) => l.map((s) => s.okuma_m3 ?? 0)],
  ["D3 bos ve sifir dislanir", (l) => sayilar(l.map((s) => s.okuma_m3)).filter((v) => v > 0)],
  ["D4 bos sutun ortalamasiyla dolar", (l) => l.map((s) => s.okuma_m3 ?? sutunOrt)],
];
export const ESIK = 0.005; // raporlanan iki ondalik basamagin yarisi
export const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j])
  : String(v).padEnd(w[0]))).join("");

if (import.meta.url === `file://${process.argv[1]}`) {
  const say = (d) => SATIR.filter((s) => s.ziyaret_sonucu === d);
  console.log(`model (tohum ${TOHUM}): ${SATIR.length} satir, raporlanan toplam ` +
    `${topla(SATIR.map((s) => s.gercek_m3)).toFixed(1)} m3; ${say("erisilemedi").length} hucre ` +
    `bos, ${say("kapali").length + say("form_sifir").length} hucre sifir yaziyor`);
  console.log(`sifirlarin ${say("kapali").length} tanesi gercek sifir tuketim, ` +
    `${say("form_sifir").length} tanesi okunmamis ziyaret; bos hucrelerin tasidigi ` +
    `${topla(say("erisilemedi").map((s) => s.gercek_m3)).toFixed(1)} m3 toplamin icinde ` +
    `ama hicbir okumaya dayanmiyor`);
  const y1 = y([34, 9, 11, 12]);
  console.log("\n" + y1(["ayni sutun, ayni soru, dort karar", "satir", "ortalama", "toplam m3"]));
  const o = {};
  for (const [ad, f] of KARAR) { const v = f(SATIR); o[ad.slice(0, 2)] = ortala(v);
    console.log(y1([ad, v.length, ortala(v).toFixed(2), topla(v).toFixed(1)])); }
  const [az, cok] = [Math.min(...Object.values(o)), Math.max(...Object.values(o))];
  console.log(`\nen dusuk ${az.toFixed(2)}, en yuksek ${cok.toFixed(2)}, aradaki fark ` +
    `${(cok - az).toFixed(2)} m3; D1 ile D4 ` +
    `${Math.abs(o.D1 - o.D4) < ESIK ? "ayni" : "farkli"} ortalamayi veriyor ` +
    `(esik ${ESIK} m3, raporlanan iki ondalik basamagin yarisi)`);
}
```

```
model (tohum 20260218): 1877 satir, raporlanan toplam 39147.7 m3; 116 hucre bos, 112 hucre sifir yaziyor
sifirlarin 57 tanesi gercek sifir tuketim, 55 tanesi okunmamis ziyaret; bos hucrelerin tasidigi 2719.2 m3 toplamin icinde ama hicbir okumaya dayanmiyor

ayni sutun, ayni soru, dort karar     satir   ortalama   toplam m3
D1 bos dislanir, sifir sayilir         1761      19.23     33866.8
D2 bos sifir sayilir                   1877      18.04     33866.8
D3 bos ve sifir dislanir               1649      20.54     33866.8
D4 bos sutun ortalamasiyla dolar       1877      19.23     36097.6

en dusuk 18.04, en yuksek 20.54, aradaki fark 2.49 m3; D1 ile D4 ayni ortalamayi veriyor (esik 0.005 m3, raporlanan iki ondalik basamagin yarisi)
```

Sayfa 1.877 satır, raporlanan toplam 39.147,7 m³; ikisi de bir önceki dersten. Yeni olan şu: 116
hücrede hiç okuma yoktur ve karşılıkları olan **2.719,2 m³** o toplamın içindedir. Sütunda 112 hücre
sıfır yazar, ama yalnız 57'si gerçek sıfır tüketimdir; kalan **55'i okuma alınamamış bir
ziyarettir**. Sayfada ikisi arasında hiçbir fark yoktur: boş hücre kendini belli eder, sıfır
yazılmış eksiklik **kendini bir ölçüm gibi gösterir**.

Dört kararın sayıları bunu doğruluyor. Ortalama 18,04 ile 20,54 arasında gider; aradaki **2,49 m³**
düşük değerin yaklaşık yüzde 14'üdür. D1 sıfırları birer ölçüm sayar ve boşları dışlar;
D2 boşları da sıfır sayar, bölen 1.761'den 1.877'ye çıkar, ortalama düşer; D3 hiçbir sıfıra
güvenmez ve 1.649 satırla en yükseği verir. Toplam ilk üç kararda **33.866,8 m³'te sabittir**:
dışlama toplamı hiç oynatmaz, çünkü toplama işlevi boş hücreyi zaten atlıyordu.

Dördüncü satır bir önceki dersin beklediğini vermiyor: sütun ortalamasıyla doldurmak üçüncü bir
ortalama üretmez, **D1'in aynısını** verir — ortalamaya eşit değer eklemek ortalamayı değiştirmez.
Karşılaştırma 0,005 m³ eşiğiyle yapılır — sayı iki ondalık basamakla raporlanır ve kayan noktalı
değerler eşitlik işleciyle karşılaştırılmaz. Asıl uyarı buradadır: doldurma ortalamayı
korurken **toplamı 36.097,6 m³'e çıkarır** ve sayfaya 116 uydurma ölçüm ekler. Ortalamanın
değişmemesi doldurmanın zararsız olduğunu değil, ortalamanın doldurmayı göstermediğini kanıtlar.

#### Doldurma Bittikten Sonra Ne Kalıyor

```js
// doldurma.mjs — ayni sayfa, uc soru: doldurma karari bolge kirilimini ne kadar
// oynatiyor, doldurma sayfada iz birakiyor mu, karar sayfadan geri okunuyor mu.
import { SATIR, BOLGE, Tablo, formul, sayilar, ortala, sutunOrt, ESIK, y } from "./eksik.mjs";

// Eksiklik bolgelere esit dagilmadigi icin ayni karar her bolgeyi ayni kadar
// oynatmaz: en cok bos hucre tasiyan bolge en cok oynar.
const bolge = BOLGE.map((b) => {
  const l = SATIR.filter((s) => s.bolge === b.ad);
  return { ad: b.ad, d1: ortala(l.map((s) => s.okuma_m3)),
    d2: ortala(l.map((s) => s.okuma_m3 ?? 0)),
    oran: 100 * l.filter((s) => s.okuma_m3 === null).length / l.length };
}).sort((a, b) => b.oran - a.oran);
const [ust, alt] = [bolge[0], bolge[bolge.length - 1]];
console.log(`bos hucre orani ${alt.ad} bolgesinde yuzde ${alt.oran.toFixed(1)}, ${ust.ad} ` +
  `bolgesinde yuzde ${ust.oran.toFixed(1)}; bos hucreler sifir sayilirsa ${ust.ad} ` +
  `bolgesinin ortalamasi ${ust.d1.toFixed(2)} m3'ten ${ust.d2.toFixed(2)} m3'e iner, ` +
  `${alt.ad} bolgesininki ${alt.d1.toFixed(2)} m3'ten ${alt.d2.toFixed(2)} m3'e`);

// Doldurma sayfada iz birakir mi: doldurulan degeri tasiyan hucreler sayilir.
// Kayan noktali karsilastirma esikle yapilir; esik iki ondalik basamagin yarisi.
const bos = SATIR.filter((s) => s.okuma_m3 === null).length;
const izOrt = SATIR.filter((s) => Math.abs((s.okuma_m3 ?? sutunOrt) - sutunOrt) < ESIK).length;
const izSifir = SATIR.filter((s) => (s.okuma_m3 ?? 0) === 0).length;
console.log(`sutun ortalamasiyla doldurulan ${bos} hucre sayfada ${izOrt} hucrede goruluyor, ` +
  `tespit yuzde ${(100 * bos / izOrt).toFixed(0)}; ayni hucreler sifirla doldurulsaydi ` +
  `${izSifir} sifir hucre arasinda tespit yuzde ${(100 * bos / izSifir).toFixed(0)} olurdu`);

// Kararin kurtarilabilirligi: karar bir hucre formulunde yaziliysa bir adim,
// ayri bir not hucresinde duruyorsa iki adim, hicbir hucrede yoksa okunamaz.
const KOD = ["E1 hangi hucre bostu", "E2 doldurma yontemi", "E3 sifirin anlami",
  "E4 hesaba giren satir"];
const geriOku = (t) => KOD.map((k) => {
  const v = [...t.h.values()], n = k.slice(0, 2);
  const f = v.some((x) => x && x.tur === "formul" && x.yazi.includes(n));
  const not = v.some((x) => typeof x === "string" && x.startsWith("karar ") && x.includes(n));
  return f ? 1 : not ? 2 : null;
});
const dolgu = Math.round(sutunOrt * 100) / 100;
const BAS = ["abone_no", "bolge", "donem", "okuma_m3"];
const temiz = SATIR.map(({ abone_no, bolge, donem, okuma_m3 }) =>
  ({ abone_no, bolge, donem, okuma_m3: okuma_m3 ?? dolgu }));
const doldurulmus = new Tablo(BAS, temiz);
const notlu = new Tablo(BAS, temiz)
  .yaz("F1", "karar E2: bos hucreler sutun ortalamasiyla dolduruldu");
const izli = new Tablo([...BAS, "ziyaret_sonucu", "hesap_m3"], SATIR.map((s) => ({
  abone_no: s.abone_no, bolge: s.bolge, donem: s.donem, okuma_m3: s.okuma_m3,
  ziyaret_sonucu: s.ziyaret_sonucu,
  hesap_m3: formul("E1 ziyaret_sonucu erisilemedi ise okuma hucresi bostur; E2 bos hucre " +
    "sutun ortalamasiyla doldurulur; E3 sifir kapali sayacta gercek tuketim, form_sifir'da " +
    "okunmamis ziyarettir", () => s.okuma_m3 ?? dolgu) })));
izli.yaz("H2", formul("E4 hesaba giren satir dolu okuma hucrelerinden sayilir",
  (t) => sayilar(t.sutun("okuma_m3")).length));

const y3 = y([24, 18, 13, 11]);
console.log("\nkarar sayfadan geri okunabiliyor mu");
console.log(y3(["karar", "doldurulmus sayfa", "notlu sayfa", "izli sayfa"]));
const sonuc = [doldurulmus, notlu, izli].map(geriOku);
KOD.forEach((k, j) => console.log(y3([k,
  ...sonuc.map((s) => (s[j] ? `${s[j]} adim` : "okunamaz"))])));
console.log(["doldurulmus", "notlu", "izli"].map((ad, j) =>
  `${ad} ${sonuc[j].filter(Boolean).length}/${KOD.length}`).join(", ") +
  " karar geri okunabiliyor");
```

```
bos hucre orani bati bolgesinde yuzde 2.0, dogu bolgesinde yuzde 17.3; bos hucreler sifir sayilirsa dogu bolgesinin ortalamasi 22.80 m3'ten 18.86 m3'e iner, bati bolgesininki 13.62 m3'ten 13.35 m3'e
sutun ortalamasiyla doldurulan 116 hucre sayfada 116 hucrede goruluyor, tespit yuzde 100; ayni hucreler sifirla doldurulsaydi 228 sifir hucre arasinda tespit yuzde 51 olurdu

karar sayfadan geri okunabiliyor mu
karar                    doldurulmus sayfa  notlu sayfa izli sayfa
E1 hangi hucre bostu              okunamaz     okunamaz     1 adim
E2 doldurma yontemi               okunamaz       2 adim     1 adim
E3 sifirin anlami                 okunamaz     okunamaz     1 adim
E4 hesaba giren satir             okunamaz     okunamaz     1 adim
doldurulmus 0/4, notlu 1/4, izli 4/4 karar geri okunabiliyor
```

İlk satır eksikliğin nereye yığıldığını söylüyor: boş hücre oranı `bati` bölgesinde yüzde 2,0,
`dogu` bölgesinde yüzde 17,3'tür; ölçüm ağı bir bölgede sistemli olarak çalışmıyor. Aynı karar bu
yüzden her bölgeyi aynı kadar oynatmaz: boşlar sıfır sayıldığında `dogu` bölgesinin ortalaması
22,80'den **18,86'ya** iner — 3,94 m³, tek bir ölçüm değişmeden — `bati` bölgesininki 0,27 m³ düşer.
Sütun düzeyinde küçük görünen karar kırılımda büyür.

İkinci satır doldurmanın iz bırakıp bırakmadığını sayıyor. Sütun ortalamasıyla doldurulan 116 hücre
tam olarak ayırt edilir, çünkü gerçek okumaların hiçbiri 19,23'e düşmez; aynı hücreler sıfırla
doldurulsaydı 228 sıfır arasında tespit yüzde 51'e inerdi. Fark **doldurma değerinin veride ne kadar
seyrek geçtiğinden** gelir, kararın yazılı olup olmadığından değil; bir tahmin, ne kadar isabetli
olursa olsun, bir kayıt değildir.

#### Karar Nerede Duruyor

Son tablo dersin ikinci sayısıdır. **Doldurulmuş sayfa** 1.877 satırın hepsini dolu gösterir ve dört
kararın **sıfırını** taşır. Bir önceki dersteki silinmiş sayfayla aynı sonuçtur, ama ondan kötüdür:
silme satır sayısını düşürür ve bir şeyin eksildiği görülebilir, doldurma satır sayısını hiç
oynatmaz.

**Not düşülmüş sayfa** dört karardan yalnız birini, doldurma yöntemini, iki adımda verir; yöntemi
bilmek hangi hücrelere uygulandığını söylemez. Bir önceki derste not iki karar veriyordu, burada
**biri**: elle tutulan not sayfaya bakılarak yazılır ve doldurma bitince sayfada görünmeyen şey tam
olarak hangi hücrenin doldurulduğudur.

**İzli sayfa** ham okuma sütununu 116 boş hücresiyle bırakır, ziyaret sonucunu ayrı bir sütunda
taşır ve hesabı bir formül sütununda üretir; dört karar da bir adımda okunur. Sayfa 1.761 dolu
okumayı ve 1.877 satırlık hesabı aynı anda tutar; aradaki 116 fark artık bir eksiklik değil,
**yazılmış bir karardır**. Bedeli iki fazladan sütundur: elektronik tablo bir hesap aracıdır, bir
kayıt aracı değildir ve kayıt sütunla satın alınır.

#### Özet

- Okuma sütununda 116 hücre boştur ve karşılıkları olan 2.719,2 m³ raporlanan 39.147,7 m³'ün
  içindedir.
- Sıfır yazan 112 hücrenin 57'si gerçek sıfır tüketim, 55'i okunamamış ziyarettir ve sayfada ikisi
  ayırt edilemez.
- Aynı sütunun ortalaması dört kararla 19,23, 18,04, 20,54 ve yine 19,23 m³ verir; toplam ilk üç
  kararda 33.866,8 m³'te sabit kalır, çünkü toplama işlevi boş hücreyi zaten atlar.
- Doldurma ortalamayı 0,005 m³ eşiğinin altında değiştirmez ama toplamı 36.097,6 m³'e çıkarır; boş
  oranı yüzde 17,3 olan bölge sıfır sayımında 3,94 m³ düşer.
- İşlem bitince dört karardan doldurulmuş sayfada sıfırı, not düşülmüş sayfada biri iki adımda,
  izli sayfada dördü bir adımda geri okunur.

#### Sonraki Adım

Bu iki ders sayfanın kendisini düzeltti; ikisinde de tek bir sütuna bakıldı. Rapor tek sütun
istemez, kırılım ister. Özet tablo bu işi yapar ve kendi kararlarını taşır: hangi alan kırılım
olacak, toplam mı ortalama mı alınacak, filtre özetten önce mi sonra mı uygulanacak, hesaplanan
alan hangi düzeyde üretilecek. Sonraki ders aynı veriden üç kırılım kurar,
üçünün üç farklı "birinci"yi verdiğini gösterir ve özetin kararlarının kaç adımda okunabildiğini
sayar.

### Özet Tablolar

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/ozet-tablolar

Aynı 1.761 satırlık sayfaya beş özet kararı uygulanır ve üç farklı bölge birinci çıkar: toplam tüketimde kuzey 10.905,8 m³, satır başına ortalamada doğu 22,80 m³, abone başına ortalamada merkez 28,39 m³. Bölge ortalaması çapraz özetin hücrelerinden üretildiğinde sıralama tutar ama güney ile batı arasındaki 3,35 m³'lük fark 0,04 m³'e iner. Aynı 20 m³ eşiği özetten sonra uygulandığında üç bölge geçer, özetten önce uygulandığında beşi de geçer ve doğu bölgesinin değeri 22,80'den 29,03 m³'e çıkar. Özet tablo dört kararından ikisini başlık satırında bir adımda verir, filtrenin yerini ve hesaplanan alanın düzeyini hiç vermez.

Bir önceki ders tek bir sütunun ortalamasını sordu ve önünde dört karar buldu. Rapor bu soruyu
sormaz; "hangi bölge birinci" diye sorar. Bu ders o soruyu **özet tabloya** devreder ve özet
tablonun kendi kararlarını sayar.

Sayfa bir önceki dersin D1 kararını almış halidir — boş okumalar dışlanmış, sıfırlar sayılmış,
1.761 satır ve 33.866,8 m³. Buradan sonrası özetin kararlarıdır: hangi alan kırılım olacak, hangi
ölçü alınacak, filtre nereye uygulanacak, hesaplanan alan hangi düzeyde üretilecek. Dersin iki
sayısı budur: **kararın ürettiği birinci** ve **kaç kararın geri okunabildiği**.

- **TO9.** Elektronik tablo yine `node` ile **modellenir**, veri kümesi **kurgudur**. Tarife
  basamağı okumadan türetilir; eşikler 10, 25 ve 40 m³'tür ve karşılaştırma küçüktür ile yapılır.
- **TO10.** Özet tablo en az iki karardan oluşur: **kırılım alanı** ve **ölçü işlevi**. İkisi de
  savunulabilir, ikisi de aynı sayfadan farklı bir sayı üretir.
- **TO11.** Kırılımın **tanesi** ayrı bir karardır: bir satır bir okumadır, bir abone bir ya da iki
  okuma taşır ve iki ölçü aynı sayfayı farklı sıralar.
- **TO12.** Kurtarılabilirlik ölçüsü sürer ve bir yer kazanır: özet tablonun **başlık satırı**
  kırılım alanını ve ölçüyü adıyla yazar, bu yüzden o iki karar **bir adımda** okunur. Filtre ve
  düzey başlıkta görünmez.

#### Aynı Sayfa, Üç Farklı Birinci

```js
// ozet.mjs — elektronik tablo MODELIDIR: hucre bir eslemede durur, formul kendi
// metnini tasir, ozet tablo gruplama ve toplama kararlarinin sonucudur. Hicbir
// uygulama taklit edilmez. Veri kumesi kurgudur: onceki dersin D1 kararini
// almis sayfasi -- bos okumalar dislanmis, sifirlar sayilmis, 1761 satir.
export const TOHUM = 20260218, ABONE = 1400, TARIFE = [10, 25, 40];
const karistir = (x) => { x = Math.imul(x ^ (x >>> 16), 2246822507);
  x = Math.imul(x ^ (x >>> 13), 3266489909); return (x ^ (x >>> 16)) >>> 0; };
const uretec = (t) => { let s = karistir(t);
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const ayrik = (u, w) => { let t = 0;
  for (let i = 0; i < w.length; i++) { t += w[i]; if (u < t) return i; } return w.length - 1; };
export const BOLGE = [["kuzey", 0.28, 21, 0.06], ["guney", 0.22, 17, 0.03],
  ["dogu", 0.18, 26, 0.27], ["bati", 0.14, 14, 0.03], ["merkez", 0.18, 23, 0.08]]
  .map(([ad, pay, taban, eksik]) => ({ ad, pay, taban, eksik }));

const HARF = (j) => String.fromCharCode(65 + j);
export const formul = (yazi, f) => ({ tur: "formul", yazi, f });
export class Tablo {
  constructor(baslik, satirlar) {
    this.baslik = baslik; this.h = new Map(); this.n = satirlar.length;
    baslik.forEach((b, j) => this.h.set(`${HARF(j)}1`, b));
    satirlar.forEach((s, i) => baslik.forEach((b, j) =>
      this.h.set(`${HARF(j)}${i + 2}`, s[b])));
  }
  oku(ad) { const v = this.h.get(ad); return v && v.tur === "formul" ? v.f(this) : v; }
  yaz(ad, v) { this.h.set(ad, v); return this; }
}
export const topla = (a) => a.reduce((x, y) => x + y, 0);
export const ortala = (a) => (a.length ? topla(a) / a.length : 0);

// Onceki iki dersin sayfasi: tekillestirilmis 1877 satirdan okumasi olan 1761
// satir. Tarife basamagi okumadan turetilir; esikler 10, 25 ve 40 m3 ve
// karsilastirma her basamakta kucuktur ile yapilir.
export const SATIR = [];
let sira = 0;
for (let i = 0; i < ABONE; i++) {
  const r = uretec(TOHUM + i);
  const b = BOLGE[ayrik(r(), BOLGE.map((x) => x.pay))];
  const taban = Math.max(1.2, b.taban * Math.exp((r() + r() + r() - 1.5) * 0.62));
  for (const d of r() < 0.34 ? ["2026-01", "2026-02"] : ["2026-01"]) {
    const q = uretec(TOHUM + 7000 + sira++), u = q();
    const durum = u < b.eksik ? (q() < 0.72 ? "erisilemedi" : "form_sifir")
      : u < b.eksik + 0.035 ? "kapali" : "okundu";
    const gercek = Math.round(taban * (d === "2026-02" ? 0.94 : 1) * 100) / 100;
    if (durum === "erisilemedi") continue;
    const okuma = durum === "okundu" ? gercek : 0;
    SATIR.push({ abone_no: 10001 + i, bolge: b.ad, donem: d, okuma_m3: okuma,
      basamak: okuma < TARIFE[0] ? 1 : okuma < TARIFE[1] ? 2 : okuma < TARIFE[2] ? 3 : 4 });
  }
}

// Ozet tablo: bir kirilim alani, bir olcu islevi. Ikisi de karardir.
export const ozetle = (satirlar, kirilim, olcu) => {
  const g = new Map();
  for (const s of satirlar) { const k = kirilim(s);
    if (!g.has(k)) g.set(k, []); g.get(k).push(s); }
  return [...g].map(([k, l]) => ({ k, n: l.length, v: olcu(l) }))
    .sort((a, b) => b.v - a.v);
};
// Abone basina olcu: bir abonenin iki donem okumasi once kendi icinde toplanir.
// Kirilimin "tanesi" satir degil abone olur ve bu da bir karardir.
export const aboneBasina = (l) => { const m = new Map();
  for (const s of l) m.set(s.abone_no, (m.get(s.abone_no) ?? 0) + s.okuma_m3);
  return ortala([...m.values()]); };
export const KIRILIM = [
  ["K1 bolge / toplam tuketim", (l) => topla(l.map((s) => s.okuma_m3)), 1],
  ["K2 bolge / okuma sayisi", (l) => l.length, 0],
  ["K3 bolge / abone sayisi", (l) => new Set(l.map((s) => s.abone_no)).size, 0],
  ["K4 bolge / satir basina ortalama", (l) => ortala(l.map((s) => s.okuma_m3)), 2],
  ["K5 bolge / abone basina ortalama", aboneBasina, 2],
];
export const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j])
  : String(v).padEnd(w[0]))).join("");

if (import.meta.url === `file://${process.argv[1]}`) {
  console.log(`model (tohum ${TOHUM}): sayfa ${SATIR.length} satir, ` +
    `${new Set(SATIR.map((s) => s.abone_no)).size} abone, toplam ` +
    `${topla(SATIR.map((s) => s.okuma_m3)).toFixed(1)} m3`);
  const bad = BOLGE.map((b) => b.ad), y1 = y([32, 9, 8, 8, 8, 9, 9]);
  console.log("\n" + y1(["ayni sayfa, bes ozet karari", ...bad, "birinci"]));
  const birinci = new Set();
  for (const [ad, o, d] of KIRILIM) {
    const t = ozetle(SATIR, (s) => s.bolge, o); birinci.add(t[0].k);
    const m = Object.fromEntries(t.map((x) => [x.k, x.v]));
    console.log(y1([ad, ...bad.map((a) => m[a].toFixed(d)), t[0].k]));
  }
  console.log(`\nbes karar ${birinci.size} farkli bolgeyi birinci yapiyor: ` +
    `${[...birinci].join(", ")}; tek bir okuma degismedi`);
}
```

```
model (tohum 20260218): sayfa 1761 satir, 1345 abone, toplam 33866.8 m3

ayni sayfa, bes ozet karari         kuzey   guney    dogu    bati   merkez  birinci
K1 bolge / toplam tuketim         10905.8  6023.5  5791.5  3963.8   7182.3    kuzey
K2 bolge / okuma sayisi               530     355     254     291      331    kuzey
K3 bolge / abone sayisi               400     276     209     207      253    kuzey
K4 bolge / satir basina ortalama    20.58   16.97   22.80   13.62    21.70     dogu
K5 bolge / abone basina ortalama    27.26   21.82   27.71   19.15    28.39   merkez

bes karar 3 farkli bolgeyi birinci yapiyor: kuzey, dogu, merkez; tek bir okuma degismedi
```

Beş karar da doğrudur, beşi de aynı 1.761 satırı okur, üçü farklı bir bölgeyi birinci yapar.
**K1 toplam tüketim** kuzeyi verir, 10.905,8 m³; nedeni hemen altındadır — kuzey 530 okuma ve 400
abone taşır. Toplam bir düzey ölçüsü değil, bir **hacim** ölçüsüdür ve en kalabalık bölgeyi öne
çıkarır.

**K4 satır başına ortalama** doğuyu verir, 22,80 m³. Doğu sayfada yalnız 254 okuma tutar ve okuma
sayısında sonuncudur, okuma başına tüketimde birincidir. "Tüketimin en yüksek olduğu bölge" cümlesi
böylece kuzey ile doğu arasında değişir ve ikisi de doğrudur: iki ayrı soruyu cevaplarlar.

**K5 abone başına ortalama** üçüncü bir bölgeyi, merkezi verir, 28,39 m³. Buradaki fark ölçüde
değil, kırılımın **tanesindedir**: K4 her satırı bir tane sayar, K5 önce her abonenin dönem
okumalarını kendi içinde toplar. Merkez bölgesinin 331 okuması 253 aboneden, doğununki 254 okuma
209 aboneden gelir ve ikinci dönemi okunmuş abone payı merkezi 0,68 m³ öne geçirir.

Üç cümle de rapora yazılabilir ve üçü de hesap hatasızdır. Aralarındaki fark tek bir karardır ve o
karar üretilen sayının yanında durmaz.

#### Filtre ve Hesaplanan Alan Nerede Duruyor

```js
// kirilim.mjs — ayni sayfa, uc soru: hesaplanan alan hangi duzeyde uretiliyor,
// filtre ozetten once mi sonra mi uygulaniyor, ozet tablonun kararlari sayfadan
// geri okunuyor mu.
import { SATIR, BOLGE, Tablo, formul, ozetle, topla, ortala, y } from "./ozet.mjs";

// Hesaplanan alan iki duzeyde uretilebilir: ham satirlardan ya da bolge ile
// tarife basamaginin caprazindan. Ikincisi her basamak hucresine esit agirlik
// verir: en az okuma tasiyan basamak hucresi en cok tasiyani kadar agirlikli olur.
const duzey = Object.fromEntries(BOLGE.map((b) => {
  const l = SATIR.filter((s) => s.bolge === b.ad);
  return [b.ad, [ortala(l.map((s) => s.okuma_m3)),
    ortala(ozetle(l, (s) => s.basamak,
      (g) => ortala(g.map((s) => s.okuma_m3))).map((h) => h.v))]];
}));
const sira = (j) => Object.entries(duzey).sort((a, b) => b[1][j] - a[1][j]).map((x) => x[0]);
console.log(`bolge ortalamasi ham satirlardan alininca siralama ${sira(0).join(" > ")}, ` +
  `capraz ozetin hucrelerinden alininca ${sira(1).join(" > ")}`);
console.log(`siralama tuttu, araliklar tutmadi: kuzey ${duzey.kuzey[0].toFixed(2)} m3'ten ` +
  `${duzey.kuzey[1].toFixed(2)} m3'e cikar, guney ile bati arasindaki fark ` +
  `${(duzey.guney[0] - duzey.bati[0]).toFixed(2)} m3 iken ` +
  `${(duzey.guney[1] - duzey.bati[1]).toFixed(2)} m3 olur`);

// Ayni filtre, iki yer: ozetten once satirlara, ozetten sonra ozet satirlarina.
const ESIK_M3 = 20;
const y2 = y([18, 7, 38]);
console.log(`\nfiltre "ortalama ${ESIK_M3} m3 ustunde" nereye uygulaniyor`);
console.log(y2(["filtrenin yeri", "sayi", "gecen bolgeler"]));
const sonra = ozetle(SATIR, (s) => s.bolge, (l) => ortala(l.map((s) => s.okuma_m3)))
  .filter((x) => x.v > ESIK_M3);
const once = ozetle(SATIR.filter((s) => s.okuma_m3 > ESIK_M3), (s) => s.bolge,
  (l) => ortala(l.map((s) => s.okuma_m3)));
console.log(y2(["ozetten sonra", sonra.length, sonra.map((x) => x.k).join(" ")]));
console.log(y2(["ozetten once", once.length, once.map((x) => x.k).join(" ")]));
console.log(`ayni esik, ayni sayfa: bir yerde ${sonra.length} bolge gecer, obur yerde ` +
  `${once.length}; ${sonra[0].k} bolgesinin degeri ${sonra[0].v.toFixed(2)} m3 iken ` +
  `${once.find((x) => x.k === sonra[0].k).v.toFixed(2)} m3 olur`);

// Kararin kurtarilabilirligi: karar baslik satirinda ya da bir hucre
// formulunde yaziliysa bir adim, ayri bir not hucresinde iki adim, hicbir
// hucrede yoksa okunamaz.
const KOD = ["G1 kirilim alani", "G2 olcu islevi", "G3 filtrenin yeri",
  "G4 hesaplanan alanin duzeyi"];
const geriOku = (t) => KOD.map((k) => {
  const n = k.slice(0, 2), v = [...t.h.values()];
  if (t.baslik.some((b) => b.includes(n))) return 1;
  if (v.some((x) => x && x.tur === "formul" && x.yazi.includes(n))) return 1;
  if (v.some((x) => typeof x === "string" && x.startsWith("karar ") && x.includes(n))) return 2;
  return null;
});
const BAS = ["G1 kirilim: bolge", "G2 olcu: toplam tuketim_m3", "okuma sayisi"];
const kayit = ozetle(SATIR, (s) => s.bolge, (l) => topla(l.map((s) => s.okuma_m3)))
  .map((x) => ({ [BAS[0]]: x.k, [BAS[1]]: Math.round(x.v * 10) / 10, [BAS[2]]: x.n }));
const ozetSayfa = new Tablo(BAS, kayit);
const notlu = new Tablo(BAS, kayit)
  .yaz("E1", "karar G3: filtre ozetten sonra ozet satirlarina uygulandi");
const izli = new Tablo([...BAS, "kaynak", "duzey"], kayit.map((x) => ({ ...x,
  kaynak: formul("G3 filtre ozetten sonra ozet satirlarina uygulanir; " +
    "ozet oncesi hicbir satir elenmez", () => x[BAS[2]]),
  duzey: formul("G4 hesaplanan alan ham satirlardan uretilir, ozet hucrelerinden degil",
    () => x[BAS[1]] / x[BAS[2]]) })));

const y3 = y([30, 15, 13, 11]);
console.log("\nozet tablonun karari sayfadan geri okunabiliyor mu");
console.log(y3(["karar", "ozet sayfasi", "notlu sayfa", "izli sayfa"]));
const sonuc = [ozetSayfa, notlu, izli].map(geriOku);
KOD.forEach((k, j) => console.log(y3([k,
  ...sonuc.map((s) => (s[j] ? `${s[j]} adim` : "okunamaz"))])));
console.log(["ozet sayfasi", "notlu", "izli"].map((ad, j) =>
  `${ad} ${sonuc[j].filter(Boolean).length}/${KOD.length}`).join(", ") +
  " karar geri okunabiliyor");
```

```
bolge ortalamasi ham satirlardan alininca siralama dogu > merkez > kuzey > guney > bati, capraz ozetin hucrelerinden alininca dogu > merkez > kuzey > guney > bati
siralama tuttu, araliklar tutmadi: kuzey 20.58 m3'ten 23.27 m3'e cikar, guney ile bati arasindaki fark 3.35 m3 iken 0.04 m3 olur

filtre "ortalama 20 m3 ustunde" nereye uygulaniyor
filtrenin yeri       sayi                        gecen bolgeler
ozetten sonra           3                     dogu merkez kuzey
ozetten once            5          dogu merkez kuzey guney bati
ayni esik, ayni sayfa: bir yerde 3 bolge gecer, obur yerde 5; dogu bolgesinin degeri 22.80 m3 iken 29.03 m3 olur

ozet tablonun karari sayfadan geri okunabiliyor mu
karar                            ozet sayfasi  notlu sayfa izli sayfa
G1 kirilim alani                       1 adim       1 adim     1 adim
G2 olcu islevi                         1 adim       1 adim     1 adim
G3 filtrenin yeri                    okunamaz       2 adim     1 adim
G4 hesaplanan alanin duzeyi          okunamaz     okunamaz     1 adim
ozet sayfasi 2/4, notlu 3/4, izli 4/4 karar geri okunabiliyor
```

İlk iki satır hesaplanan alanın hangi düzeyde üretildiğini ölçüyor. Bölge ortalaması ham satırlardan
alınırsa her okuma bir ağırlık taşır; bölge ile tarife basamağının çaprazından alınırsa az kişilik
bir basamak kalabalık bir basamak kadar söz sahibi olur. Sıralama bu kümede tutuyor ama **aralıklar
tutmuyor**: kuzey bölgesinin ortalaması 20,58'den 23,27 m³'e çıkar, güney ile batı arasındaki fark
3,35 m³'ten 0,04 m³'e iner. Sıralamanın korunması bir güvence değildir: o fark seksen katına yakın
küçülmüştür.

Sonraki tablo aynı eşiği iki ayrı yere koyuyor. "Ortalaması 20 m³'ün üstünde olan bölgeler" sorusu
özet satırlarına uygulandığında **üç** bölge geçer; aynı eşik önce ham satırlara uygulanıp özet
sonra alındığında **beşi de** geçer, çünkü ikinci durumda her bölge yalnız kendi yüksek
okumalarından hesaplanır. Doğu bölgesinin değeri 22,80 m³ iken 29,03 m³ olur. Eşik 20 m³'tür ve
tarife basamaklarının ortasında durduğu için seçilmiştir; karşılaştırma eşiğin aşılıp aşılmadığına
bakar, kayan noktalı bir eşitliğe değil.

#### Karar Nerede Duruyor

Son tablo dersin ikinci sayısıdır ve kurs boyunca ilk kez sıfırdan başlamıyor. **Özet sayfası** dört
karardan ikisini bir adımda verir, çünkü başlık satırı kırılım alanını ve ölçüyü adıyla yazar. Özet
tablo, ürettiği sayının yanına kendi tanımının bir bölümünü **kendiliğinden** koyan ilk araçtır.

Taşımadığı iki karar tam da ölçülen ikisidir: filtrenin yeri üç bölge ile beş bölge arasındaki farkı
belirler, hesaplanan alanın düzeyi 3,35 m³'ü 0,04 m³'e indirir, ikisi de başlıkta görünmez. **Not
düşülmüş sayfa** filtreyi yazarak üçe çıkar ve düzeyi yazmaz, çünkü düzey bir karar gibi değil,
hesabın kendisi gibi görünür.

**İzli sayfa** iki kararı da birer formül sütununda taşır ve dördü de bir adımda okunur; bedeli yine
sütundur. Özet tablonun kendiliğinden taşıdığı bölüm en ucuzudur: adlandırması kolay olanı yazar,
sayıyı en çok oynatanı yazmaz.

#### Özet

- Aynı 1.761 satırlık sayfaya beş özet kararı uygulanır ve üç farklı bölge birinci çıkar: toplamda
  kuzey 10.905,8 m³, satır başına ortalamada doğu 22,80 m³, abone başına ortalamada merkez
  28,39 m³.
- Toplam bir hacim ölçüsüdür: doğu bölgesi 254 okumayla okuma sayısında sonuncu, okuma başına
  tüketimde birincidir. Kırılımın tanesi de ayrı bir karardır ve abone başına ölçü merkezi doğunun
  0,68 m³ önüne geçirir.
- Bölge ortalaması çapraz özetin hücrelerinden üretildiğinde sıralama tutar ama güney ile batı
  arasındaki fark 3,35 m³'ten 0,04 m³'e iner.
- Aynı 20 m³ eşiği özetten sonra üç bölge, özetten önce beş bölge geçirir ve doğu bölgesinin değeri
  22,80'den 29,03 m³'e çıkar.
- Özet tablo dört kararından ikisini başlık satırında bir adımda verir; filtrenin yeri notla iki
  adımda, düzey ancak formülle okunur.

#### Sonraki Adım

Özet tablo sayıyı üretti, ama rapora giren şey çoğu zaman sayı değil, sayının resmidir. Grafik özetin
üstüne iki karar daha ekler: hangi grafik türü seçilecek ve eksen nereden başlayacak. İlki bir uyum
sorusudur; ikincisi bir büyütme sorusudur — aynı fark, eksen sıfırdan başlarken bir görünür, en
küçük değere yakın başlarken başka. Sonraki ders bir grafik kümesinin kaçının ölçeğe uymadığını sayar ve eksen başlangıcı
değişince aynı farkın kaç kat büyük göründüğünü ölçer.

### Temel Grafikler

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/temel-grafikler

On grafik önerisinin dördü çizilebilir ama alanın ölçeğine ya da ölçünün toplanabilirliğine uymaz: sınıflayıcı bir eksen üzerine çizgi, sıralayıcı bir eksen üzerine çizgi, etiket olan bir alan üzerine dağılım ve ortalamalardan pasta. Aynı beş bölge ortalaması iki eksen başlangıcıyla çizildiğinde doğu ile batı arasındaki gerçek 1,67'lik oran, eksen sıfırdan başlarken 1,63 uzunluk oranı verir, 13 m³'ten başlarken 13,00 verir; aynı fark 8,0 kat büyük görünür ve tek bir okuma değişmez. Grafik sayfaya yalnız çizdiği aralığı bildirir: dört karardan grafikli sayfada biri, not düşülmüş sayfada ikisi, izli sayfada dördü geri okunur.

Bir önceki ders özet tablonun dört kararını saydı ve ikisinin sayfada hiç durmadığını gösterdi.
Rapora giren şey çoğu zaman o sayı değil, sayının resmidir. Grafik özetin üstüne iki karar daha
ekler: **hangi tür seçilecek** ve **eksen nereden başlayacak**. İlki bir uyum sorusudur, ikincisi
bir büyütme sorusu.

Bu dersin iki sayısı bunlardır: bir grafik kümesinin **kaçının alanın ölçeğine uymadığı** ve eksen
başlangıcı değişince **aynı farkın kaç kat büyük göründüğü**. Yanlarında kursun ikinci ölçüsü durur:
grafiğin kararlarından kaçı sayfadan geri okunabiliyor.

- **TO13.** Elektronik tablo yine `node` ile **modellenir**, veri kümesi **kurgudur**. Grafik bu
  modelde bir **aralık** ile birkaç **ayardan** ibarettir ve ayarlar sayfanın hücrelerinde durmaz.
- **TO14.** Alanların hangi ölçekte olduğu bu müfredatın ilk kursunda belirlendi ve burada
  tekrarlanmaz. Sayılan tek şey **grafik türünün o ölçekle uyup uymadığıdır**.
- **TO15.** Her tür ekseninden ve ölçüsünden bir şey ister: çizgi grafiği **sıralı ve eşit
  aralıklı** bir eksen, pasta ve yığılmış sütun **dilimlerin bütün vermesi**, dağılım grafiği iki
  sayısal eksen. Ortalama ve oran toplanamaz, bu yüzden dilim olamaz.
- **TO16.** Kurtarılabilirlik ölçüsü sürer: karar bir hücre formülünde yazılıysa **bir adımda**,
  ayrı bir not hücresinde **iki adımda** okunur, hiçbir hücrede yoksa **okunamaz**.

#### Kaç Grafik Ölçeğine Uymuyor

```js
// grafik.mjs — elektronik tablo MODELIDIR: hucre bir eslemede durur, formul
// kendi metnini tasir, grafik bir aralik ile birkac ayardan ibarettir. Hicbir
// uygulama taklit edilmez, hicbir arac adi gecmez. Veri kumesi kurgudur:
// onceki dersin ozetlenen 1761 satirlik sayfasi.
export const TOHUM = 20260218, ABONE = 1400, TARIFE = [10, 25, 40];
const karistir = (x) => { x = Math.imul(x ^ (x >>> 16), 2246822507);
  x = Math.imul(x ^ (x >>> 13), 3266489909); return (x ^ (x >>> 16)) >>> 0; };
const uretec = (t) => { let s = karistir(t);
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const ayrik = (u, w) => { let t = 0;
  for (let i = 0; i < w.length; i++) { t += w[i]; if (u < t) return i; } return w.length - 1; };
export const BOLGE = [["kuzey", 0.28, 21, 0.06], ["guney", 0.22, 17, 0.03],
  ["dogu", 0.18, 26, 0.27], ["bati", 0.14, 14, 0.03], ["merkez", 0.18, 23, 0.08]]
  .map(([ad, pay, taban, eksik]) => ({ ad, pay, taban, eksik }));
const HARF = (j) => String.fromCharCode(65 + j);
export const formul = (yazi, f) => ({ tur: "formul", yazi, f });
export class Tablo {
  constructor(baslik, satirlar) {
    this.baslik = baslik; this.h = new Map(); this.n = satirlar.length;
    baslik.forEach((b, j) => this.h.set(`${HARF(j)}1`, b));
    satirlar.forEach((s, i) => baslik.forEach((b, j) =>
      this.h.set(`${HARF(j)}${i + 2}`, s[b])));
  }
  oku(ad) { const v = this.h.get(ad); return v && v.tur === "formul" ? v.f(this) : v; }
  yaz(ad, v) { this.h.set(ad, v); return this; }
}
export const topla = (a) => a.reduce((x, y) => x + y, 0);
export const ortala = (a) => (a.length ? topla(a) / a.length : 0);
export const SATIR = [];
let sira = 0;
for (let i = 0; i < ABONE; i++) {
  const r = uretec(TOHUM + i);
  const b = BOLGE[ayrik(r(), BOLGE.map((x) => x.pay))];
  const taban = Math.max(1.2, b.taban * Math.exp((r() + r() + r() - 1.5) * 0.62));
  for (const d of r() < 0.34 ? ["2026-01", "2026-02"] : ["2026-01"]) {
    const q = uretec(TOHUM + 7000 + sira++), u = q();
    const durum = u < b.eksik ? (q() < 0.72 ? "erisilemedi" : "form_sifir")
      : u < b.eksik + 0.035 ? "kapali" : "okundu";
    if (durum === "erisilemedi") continue;
    const okuma = durum === "okundu"
      ? Math.round(taban * (d === "2026-02" ? 0.94 : 1) * 100) / 100 : 0;
    SATIR.push({ abone_no: 10001 + i, bolge: b.ad, donem: d, okuma_m3: okuma,
      basamak: okuma < TARIFE[0] ? 1 : okuma < TARIFE[1] ? 2 : okuma < TARIFE[2] ? 3 : 4 });
  }
}

// Alanlarin olcekleri onceki kursta belirlendi; burada yalnizca grafik turunun
// o olcekle uyup uymadigi sayilir. Olcek sirasi kapsama sirasidir.
export const OLCEK = { siniflayici: 0, siralayici: 1, aralik: 2, oran: 3 };
export const ALAN = { bolge: "siniflayici", abone_no: "siniflayici",
  basamak: "siralayici", donem: "aralik", okuma_m3: "oran" };
// Her grafik turu ekseninden ve olcusunden bir sey ister.
// eksenEnAz / eksenEnCok: eksen alaninin olcegi bu araligin disindaysa tur uymaz.
// butun: dilimlerin toplaminin bir butun vermesi gerekir.
export const TUR = {
  "sutun": { eksenEnAz: 0, eksenEnCok: 3, butun: false },
  "cizgi": { eksenEnAz: 2, eksenEnCok: 3, butun: false },
  "pasta": { eksenEnAz: 0, eksenEnCok: 1, butun: true },
  "yigilmis sutun": { eksenEnAz: 0, eksenEnCok: 1, butun: true },
  "dagilim": { eksenEnAz: 2, eksenEnCok: 3, butun: false },
};
// Onerilen grafikler: eksen alani, olcu, tur ve olcunun toplanip toplanamadigi.
// Ortalama ve oran toplanamaz: dilimleri toplandiginda bir butun vermez.
export const ONERI = [
  ["bolge / toplam m3", "bolge", "sutun", true],
  ["bolge / toplam m3", "bolge", "pasta", true],
  ["bolge / toplam m3", "bolge", "cizgi", true],
  ["bolge / ortalama m3", "bolge", "sutun", false],
  ["bolge / ortalama m3", "bolge", "pasta", false],
  ["basamak / abone sayisi", "basamak", "sutun", true],
  ["basamak / abone sayisi", "basamak", "cizgi", true],
  ["bolge x basamak / abone", "bolge", "yigilmis sutun", true],
  ["donem / ortalama m3", "donem", "cizgi", false],
  ["abone_no / okuma_m3", "abone_no", "dagilim", false],
];
export const uyum = ([, alan, tur, toplanir]) => {
  const o = OLCEK[ALAN[alan]], t = TUR[tur];
  if (o < t.eksenEnAz || o > t.eksenEnCok) return "eksen olcegi";
  if (t.butun && !toplanir) return "dilimler butun vermiyor";
  return "uygun";
};
export const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j])
  : String(v).padEnd(w[0]))).join("");

if (import.meta.url === `file://${process.argv[1]}`) {
  console.log(`model (tohum ${TOHUM}): sayfa ${SATIR.length} satir, ` +
    `toplam ${topla(SATIR.map((s) => s.okuma_m3)).toFixed(1)} m3`);
  const y1 = y([24, 16, 14, 16, 25]);
  console.log("\n" + y1(["onerilen grafik", "eksen alani", "olcek", "tur", "sonuc"]));
  let uymaz = 0;
  for (const o of ONERI) {
    const s = uyum(o); if (s !== "uygun") uymaz++;
    console.log(y1([o[0], o[1], ALAN[o[1]], o[2], s]));
  }
  console.log(`\n${ONERI.length} grafik onerisinin ${uymaz} tanesi cizilebilir ama ` +
    `alanin olcegine ya da olcunun toplanabilirligine uymuyor; ` +
    `${ONERI.length - uymaz} tanesi uyuyor`);
}
```

```
model (tohum 20260218): sayfa 1761 satir, toplam 33866.8 m3

onerilen grafik              eksen alani         olcek             tur                    sonuc
bolge / toplam m3                  bolge   siniflayici           sutun                    uygun
bolge / toplam m3                  bolge   siniflayici           pasta                    uygun
bolge / toplam m3                  bolge   siniflayici           cizgi             eksen olcegi
bolge / ortalama m3                bolge   siniflayici           sutun                    uygun
bolge / ortalama m3                bolge   siniflayici           pasta  dilimler butun vermiyor
basamak / abone sayisi           basamak    siralayici           sutun                    uygun
basamak / abone sayisi           basamak    siralayici           cizgi             eksen olcegi
bolge x basamak / abone            bolge   siniflayici  yigilmis sutun                    uygun
donem / ortalama m3                donem        aralik           cizgi                    uygun
abone_no / okuma_m3             abone_no   siniflayici         dagilim             eksen olcegi

10 grafik onerisinin 4 tanesi cizilebilir ama alanin olcegine ya da olcunun toplanabilirligine uymuyor; 6 tanesi uyuyor
```

On önerinin onu da çizilebilir ve hiçbiri hata üretmez. Altısı uyar, **dördü uymaz** ve uymama üç
ayrı biçimde ortaya çıkar.

Üç grafik **eksen ölçeğinde** takılır. Bölge alanı sınıflayıcıdır: beş bölge arasında bir sıra
yoktur, dolayısıyla noktaları birleştiren bir çizgi iki bölge arasında bir ara değer olduğunu iddia
eder ve öyle bir değer yoktur. Aynı çizgi, bölgeler sayfada başka bir sırayla dizilseydi bambaşka
bir eğri verirdi. Basamak alanı sıralayıcıdır ve sıra vardır, ama basamaklar arası uzaklık
tanımsızdır; eğimin bir birimi yoktur. Abone numarası bir etikettir ve dağılım grafiğinin yatay
eksenine konduğunda hiçbir eğilim göstermez — numarayla okuma arasındaki bağıntı sıfırdır; yatay
eksende sıralanan tek şey abonelerin kayıt sırasıdır.

Dördüncüsü eksende değil, **ölçüde** takılır. Bölge ortalamalarından pasta çizmek dilimleri
toplanamayan bir ölçüden dilim yapar: beş ortalamanın toplamı hiçbir şeydir ve her dilimin yüzdesi o
hiçbir şeye oranla hesaplanır. Aynı beş bölgenin **toplam** tüketiminden çizilen pasta uyar, çünkü
toplamlar gerçekten bir bütün verir. Ölçek doğru olduğunda bile ölçünün toplanabilirliği ayrı bir
koşuldur.

#### Aynı Beş Sayı, İki Eksen

```js
// eksen.mjs — ayni bes sayi, iki eksen baslangici; sonra grafigin kararlarinin
// sayfadan geri okunup okunamadigi. Cubuklar metin olarak cizilir, cunku
// olculen sey bicim degil uzunluk oranidir.
import { SATIR, BOLGE, Tablo, formul, ortala, y } from "./grafik.mjs";
const GENIS = 26;
const deger = BOLGE.map((b) => ({ ad: b.ad,
  v: ortala(SATIR.filter((s) => s.bolge === b.ad).map((s) => s.okuma_m3)) }));
const enAz = Math.min(...deger.map((d) => d.v)), enCok = Math.max(...deger.map((d) => d.v));
const EKSEN = [0, Math.floor(enAz)];
const cubuk = (v, alt) => "#".repeat(Math.max(0,
  Math.round(GENIS * (v - alt) / (enCok - alt))));
const y1 = y([9, 8]);
for (const alt of EKSEN) {
  console.log(`bolge basina ortalama, dusey eksen ${alt} m3'ten baslarsa`);
  for (const d of deger) console.log(y1([d.ad, d.v.toFixed(2)]) + "  " + cubuk(d.v, alt));
  console.log("");
}
const [a, b] = [deger.find((d) => d.ad === "dogu"), deger.find((d) => d.ad === "bati")];
const oran = (alt) => cubuk(a.v, alt).length / Math.max(1, cubuk(b.v, alt).length);
console.log(`dogu ile bati arasindaki gercek oran ${(a.v / b.v).toFixed(2)}; cubuk orani ` +
  `eksen 0'dan baslarken ${oran(0).toFixed(2)}, eksen ${EKSEN[1]} m3'ten baslarken ` +
  `${oran(EKSEN[1]).toFixed(2)}`);
console.log(`ayni fark ${(oran(EKSEN[1]) / oran(0)).toFixed(1)} kat buyuk gorunuyor ve ` +
  `tek bir okuma degismedi`);

// Kararin kurtarilabilirligi: karar bir hucre formulunde yaziliysa bir adim,
// ayri bir not hucresinde iki adim, hicbir hucrede yoksa okunamaz. Grafigin
// ayari sayfanin hucrelerinde durmaz; grafik nesnesinin icinde durur.
const KOD = ["C1 grafik turu", "C2 eksen baslangici", "C3 cizilen aralik",
  "C4 dilimler butun veriyor mu"];
const geriOku = (t) => KOD.map((k) => {
  const n = k.slice(0, 2), v = [...t.h.values()];
  if (v.some((x) => x && x.tur === "formul" && x.yazi.includes(n))) return 1;
  if (v.some((x) => typeof x === "string" && x.startsWith("karar ") && x.includes(n))) return 2;
  return null;
});
const BAS = ["bolge", "ortalama_m3"];
const kayit = deger.map((d) => ({ bolge: d.ad, ortalama_m3: Math.round(d.v * 100) / 100 }));
// Grafik sayfadan yalnizca cizdigi araligi bildirir; turu, eksen ayari ve
// dilimlerin butun verip vermedigi hicbir hucrede yazmaz.
const grafikli = new Tablo(BAS, kayit)
  .yaz("D1", formul("C3 grafik A2:B6 araligini cizer", () => kayit.length));
const notlu = new Tablo(BAS, kayit)
  .yaz("D1", formul("C3 grafik A2:B6 araligini cizer", () => kayit.length))
  .yaz("D2", `karar C2: dusey eksen ${EKSEN[1]} m3'ten baslatildi`);
const izli = new Tablo(BAS, kayit)
  .yaz("D1", formul("C3 grafik A2:B6 araligini cizer", () => kayit.length))
  .yaz("D2", formul("C1 grafik turu sutundur, cunku eksen alani siniflayici", () => "sutun"))
  .yaz("D3", formul(`C2 dusey eksen ${EKSEN[1]} m3'ten baslar; sifirdan baslasaydi ` +
    `cubuk orani ${oran(0).toFixed(2)} olurdu`, () => EKSEN[1]))
  .yaz("D4", formul("C4 olcu ortalamadir, dilimleri toplanmaz, pasta cizilemez",
    () => false));

const y2 = y([30, 17, 13, 11]);
console.log("\ngrafigin karari sayfadan geri okunabiliyor mu");
console.log(y2(["karar", "grafikli sayfa", "notlu sayfa", "izli sayfa"]));
const sonuc = [grafikli, notlu, izli].map(geriOku);
KOD.forEach((k, j) => console.log(y2([k,
  ...sonuc.map((s) => (s[j] ? `${s[j]} adim` : "okunamaz"))])));
console.log(["grafikli", "notlu", "izli"].map((ad, j) =>
  `${ad} ${sonuc[j].filter(Boolean).length}/${KOD.length}`).join(", ") +
  " karar geri okunabiliyor");
```

```
bolge basina ortalama, dusey eksen 0 m3'ten baslarsa
kuzey       20.58  #######################
guney       16.97  ###################
dogu        22.80  ##########################
bati        13.62  ################
merkez      21.70  #########################

bolge basina ortalama, dusey eksen 13 m3'ten baslarsa
kuzey       20.58  ####################
guney       16.97  ###########
dogu        22.80  ##########################
bati        13.62  ##
merkez      21.70  #######################

dogu ile bati arasindaki gercek oran 1.67; cubuk orani eksen 0'dan baslarken 1.63, eksen 13 m3'ten baslarken 13.00
ayni fark 8.0 kat buyuk gorunuyor ve tek bir okuma degismedi

grafigin karari sayfadan geri okunabiliyor mu
karar                            grafikli sayfa  notlu sayfa izli sayfa
C1 grafik turu                         okunamaz     okunamaz     1 adim
C2 eksen baslangici                    okunamaz       2 adim     1 adim
C3 cizilen aralik                        1 adim       1 adim     1 adim
C4 dilimler butun veriyor mu           okunamaz     okunamaz     1 adim
grafikli 1/4, notlu 2/4, izli 4/4 karar geri okunabiliyor
```

İki çubuk kümesi aynı beş sayıyı çiziyor. Üstteki eksen sıfırdan başlar ve çubuk uzunluklarının
oranı 1,63 çıkar; doğu ile batı arasındaki gerçek oran 1,67'dir, aradaki küçük fark çubukların tam
karakterlere yuvarlanmasından gelir. Alttaki eksen en küçük değerin altındaki tam sayıdan, 13
m³'ten başlar ve aynı iki çubuğun oranı **13,00** olur. Aynı fark **8,0 kat** büyük görünür ve tek
bir okuma değişmemiştir.

Eksen başlangıcını yükseltmek kendiliğinden bir kusur değildir. Beş bölgenin ortalaması 13,62 ile
22,80 arasında sıkışıktır ve sıfırdan başlayan bir eksende aradaki farklar görünmez hale gelir;
eksen kırpıldığında görünür olurlar. Bedeli şudur: **sütun grafiğinde okunan şey uzunluk oranıdır**
ve eksen sıfırdan başlamayınca o oran veriye ait olmaktan çıkar. Çizgi grafiğinde okunan şey
eğimdir, uzunluk değil; orada eksen kırpılabilir. Karar kuralı buradan çıkar: uzunluk okunuyorsa
eksen sıfırdan başlar, kırpılıyorsa nereden başladığı yazılır.

#### Karar Nerede Duruyor

Son tablo dersin ikinci sayısıdır. **Grafikli sayfa** dört karardan yalnız birini verir: çizdiği
aralığı. Grafik hangi hücreleri okuduğunu söyler, o hücrelerden neyi nasıl gösterdiğini söylemez.
Tür, eksen başlangıcı ve dilimlerin bütün verip vermediği grafik nesnesinin içinde durur, sayfanın
hiçbir hücresinde değil; sayfayı açan ikinci kişi grafiği silip yeniden çizerse aynı resmi elde
edeceğinin bir güvencesi yoktur.

**Not düşülmüş sayfa** ikiye çıkar ve eklediği tek karar eksen başlangıcıdır. Bunun bir nedeni var:
eksen ayarı bilinçli olarak değiştirilmiş tek karardır, öbür ikisi seçilmemiş, varsayılan olarak
kalmıştır. **Elle tutulan not değiştirilen kararı yazar, kalan kararı yazmaz** — oysa ortalamalardan
çizilen pasta da tam olarak yazılmamış bir karardır.

**İzli sayfa** dördünü de bir adımda verir, çünkü grafik ayarları sayfaya taşınmıştır: eksen
başlangıcı bir hücrede durur, grafik o hücreyi okur, hücrenin formülü sıfırdan başlansaydı oranın ne
olacağını da yazar. Bedeli üç fazladan hücredir. Kursun kuralı burada da aynı biçimde çıkıyor —
elektronik tablo bir hesap aracıdır, bir kayıt aracı değildir; kayıt hücreyle satın alınır.

#### Özet

- On grafik önerisinin altısı uyar, dördü çizilebilir ama uymaz ve hiçbiri hata üretmez.
- Üç uyumsuzluk eksen ölçeğindedir: sınıflayıcı bir eksen üzerine çizgi ara değer, sıralayıcı bir
  eksen üzerine çizgi birimsiz eğim, etiket olan bir alan üzerine dağılım kayıt sırası gösterir.
- Dördüncüsü ölçüdedir: ortalamaların dilimleri toplanmaz, aynı bölgelerin toplamlarından çizilen
  pasta ise uyar.
- Aynı beş sayının çubuk oranı eksen sıfırdan başlarken 1,63, 13 m³'ten başlarken 13,00 çıkar; aynı
  fark 8,0 kat büyük görünür.
- Grafik sayfaya yalnız çizdiği aralığı bırakır: dört karardan grafikli sayfada biri, not düşülmüş
  sayfada ikisi, izli sayfada dördü geri okunur.

#### Sonraki Adım

Dört ders boyunca her işlem bir karar taşıdı ve kararların çoğu sayfada durmadı. Bu bir kullanım
hatası değil, aracın biçimi: elektronik tablo hesabı hücreye yazar, kararı yazmaz. Sonraki ders bu
biçimin sınırını üç yerde ölçer — sayfa kaç satırda ağırlaşır, aynı sayfayı ikinci kez açan biri
aynı sayıyı bulur mu, tek bir yanlış aralık kaç hücreye yayılır. Kurs orada kapanır ve on bir dersin
kararlarını tek bir tabloda sayar.

### Elektronik Tablonun Sınırları

Source: https://academia.sh/tr/kurslar/elektronik-tablolar/elektronik-tablonun-sinirlari

Kursun on bir dersinde alınan 32 karar taranıyor ve yaygın yazımda yalnız 7'si sayfadan geri okunuyor: 6'sı bir adımda, 1'i iki adımda, 25'i hiçbir hücrede yok. Kararların tamamını yazmak 32 hücre eder, yani 9.385 veri hücreli sayfanın binde 3,4 kadarı. Sayfa büyüdükçe aralık okuyan bir sütun sabit işli sütundan 626 kat pahalıya çıkıyor ve satır iki katına çıkınca dört katına; aynı sırada bir satırlık aralık hatası 60 satırlık sayfada dört özetten ikisinde görünürken 1.877 satırlık sayfada hiçbirinde görünmüyor. İki formül metni 7.508 hücreyi dolduruyor: aracın ucuza yaptığı iş de budur.

Bir önceki ders grafiğin iki kararını saydı ve dördünden üçünün sayfada hiç durmadığını gösterdi.
Aynı sonuç on bir dersin hepsinde çıktı: hesap doğru yapıldı, sayı üretildi, karar kayboldu. Bu ders
o kayboluşu tek bir sayıya indirir ve aracın sınırını üç yerde ölçer — **kaç karar geri
okunabiliyor**, sayfa büyüdükçe ne büyüyor, bir hata ne zaman görünmez olur.

Sınır bir kusur değildir. Elektronik tablo on bir derste hiçbir kurulum istemeden on bir soruyu
cevapladı ve bu dersin son sayısı o ucuzluğu da ölçer. Ölçülen şey aracın kötü olduğu değil,
**hangi işi ucuza, hangi işi hiç yapmadığıdır**.

- **TO23.** Bu ders yeni bir veri kümesi kurmaz. On bir dersin sayıları kaynak derslerden okunur;
  koşan bölüm bu sayıları **döküm olarak** tutar ve üzerinde sayar. Sayfa modeli yine `node` ile
  kurulur ve **model olduğu yazılır**.
- **TO24.** Döküm her dersin **yaygın yazımını** ölçer. Aynı derslerde kurulan izli sürümlerde
  kararların hepsi bir adımda okunur; ölçülen fark yazım biçiminin farkıdır, aracın yeteneğinin
  değil.
- **TO25.** Boyut **süreyle** ölçülmez. Sayılan şey tek bir yeniden hesapta değerlendirilen hücre
  sayısıdır; bu sayı donanımdan bağımsızdır ve sayfanın biçiminden çıkar.
- **TO26.** Bir değişimin **görünür** sayılması için eşik yüzde 1'dir, çünkü bu kursun özetleri
  iki anlamlı basamakla raporlanıyor.

#### Kaç Karar Geri Okunabiliyor

```js
// kapanis.mjs — kursun on bir dersinde alinan kararlarin dokumu. Her satir bir
// dersin kararlarini ve o kararin YAYGIN YAZIMDA sayfadan kac adimda geri
// okundugunu tutar: 1 adim formulde ya da baslik satirinda yazili, 2 adim ayri
// bir not hucresinde, null hicbir hucrede yok. Sayilar kaynak derslerden okundu.
export const DERS = [
  ["temeller/01 calisma sayfasi", ["basvuru turu", "birim ucret kaynagi"], [2, null]],
  ["temeller/02 temel islevler",
    ["sayma isleminin secimi", "ortalamanin paydasi", "metin sayilarin atlanmasi"],
    [1, null, null]],
  ["temeller/03 mantiksal islevler",
    ["sinir karsilastirmasi", "kosullarin sirasi", "basamak esikleri"], [null, null, null]],
  ["temeller/04 metin islevleri",
    ["kirpma duzeyi", "harf donusumunun dili", "duzeltmenin yeri"], [1, 1, null]],
  ["temeller/05 tarih islevleri",
    ["gun sayma kurali", "ay sonu kurali", "tarihin turu"], [null, null, null]],
  ["temeller/06 arama islevleri",
    ["sutun numarasinin turu", "eslesme turu"], [null, null]],
  ["ozet/01 yinelenen kayitlar",
    ["olcut sutunlari", "metin kirpma", "tutulan satir", "silinen sayisi"],
    [null, null, null, null]],
  ["ozet/02 eksik veri",
    ["hangi hucre bostu", "doldurma yontemi", "sifirin anlami", "hesaba giren satir"],
    [null, null, null, null]],
  ["ozet/03 ozet tablolar",
    ["kirilim alani", "olcu islevi", "filtrenin yeri", "hesaplanan alanin duzeyi"],
    [1, 1, null, null]],
  ["ozet/04 temel grafikler",
    ["grafik turu", "eksen baslangici", "cizilen aralik", "dilimler butun veriyor mu"],
    [null, null, 1, null]],
];
// Kursun sayfasi: tekillestirmeden sonra kalan satirlar ve tasidigi sutunlar.
export const SATIR = 1877, SUTUN = 5;
export const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j])
  : String(v).padEnd(w[0]))).join("");

if (import.meta.url === `file://${process.argv[1]}`) {
  const y1 = y([30, 8, 9, 9, 11]);
  console.log(y1(["ders", "karar", "1 adim", "2 adim", "okunamaz"]));
  let t = 0, bir = 0, iki = 0;
  for (const [ad, kararlar, adim] of DERS) {
    const a = adim.filter((x) => x === 1).length, b = adim.filter((x) => x === 2).length;
    t += kararlar.length; bir += a; iki += b;
    console.log(y1([ad, kararlar.length, a, b, kararlar.length - a - b]));
  }
  const okunur = bir + iki;
  console.log(y1(["TOPLAM", t, bir, iki, t - okunur]));
  console.log(`\non bir dersin ilk onunda ${t} karar alindi; yaygin yazimda ${okunur} tanesi ` +
    `sayfadan geri okunuyor, ${t - okunur} tanesi hicbir hucrede yok`);
  console.log(`geri okunanlarin ${bir} tanesi bir adimda, ${iki} tanesi iki adimda okunuyor; ` +
    `oran yuzde ${(100 * okunur / t).toFixed(0)}`);
  // Kararin yazilmasinin bedeli: karar basina bir hucre.
  const veri = SATIR * SUTUN;
  console.log(`${t} kararin tamamini yazmak ${t} hucre eder; sayfanin ${SATIR} satir ve ` +
    `${SUTUN} sutunu ${veri} veri hucresi tutuyor, kaydin bedeli sayfanin binde ` +
    `${(1000 * t / veri).toFixed(1)} kadari`);
  // Kararin gomulu oldugu yerler, konuya gore.
  const konu = (p) => DERS.filter(([a]) => a.startsWith(p))
    .reduce((s, [, k, d]) => [s[0] + k.length, s[1] + d.filter(Boolean).length], [0, 0]);
  const [tt, to] = [konu("temeller"), konu("ozet")];
  console.log(`hucre islevleriyle calisan alti derste ${tt[1]}/${tt[0]} karar okunuyor, ` +
    `sayfanin butunune bakan dort derste ${to[1]}/${to[0]}`);
}
```

```
ders                             karar   1 adim   2 adim   okunamaz
temeller/01 calisma sayfasi          2        0        1          1
temeller/02 temel islevler           3        1        0          2
temeller/03 mantiksal islevler       3        0        0          3
temeller/04 metin islevleri          3        2        0          1
temeller/05 tarih islevleri          3        0        0          3
temeller/06 arama islevleri          2        0        0          2
ozet/01 yinelenen kayitlar           4        0        0          4
ozet/02 eksik veri                   4        0        0          4
ozet/03 ozet tablolar                4        2        0          2
ozet/04 temel grafikler              4        1        0          3
TOPLAM                              32        6        1         25

on bir dersin ilk onunda 32 karar alindi; yaygin yazimda 7 tanesi sayfadan geri okunuyor, 25 tanesi hicbir hucrede yok
geri okunanlarin 6 tanesi bir adimda, 1 tanesi iki adimda okunuyor; oran yuzde 22
32 kararin tamamini yazmak 32 hucre eder; sayfanin 1877 satir ve 5 sutunu 9385 veri hucresi tutuyor, kaydin bedeli sayfanin binde 3.4 kadari
hucre islevleriyle calisan alti derste 4/16 karar okunuyor, sayfanin butunune bakan dort derste 3/16
```

Yedi. Otuz iki karardan yedisi sayfadan geri okunuyor, yirmi beşi hiçbir hücrede durmuyor; oran
**yüzde 22**. Geri okunan yedinin altısı bir adımda okunuyor ve bunun bir nedeni var: o altısında
kararı taşıyan şey ayrı bir açıklama değil, işlevin ya da başlığın **kendi adı**. Kırpma işlevi
kırptığını söyler, harf dönüşümü hangi dilin kuralını uyguladığını söyler, özet tablonun başlığı
kırılım alanını yazar. **Kendi adını taşıyan karar kurtulur.**

Kalan yirmi beşi kaybeden şey ortak bir biçimdir: karar bir sayıya dönüşüp bir formülün içine
girmiştir. Sütun numarası bir sabit, sınır karşılaştırması bir işleç, eşleşme türü bir sıfır,
doldurma değeri bir ondalık sayı olur. Sayı hesabı doğru yapar ve hangi soruya cevap verdiğini
söylemez.

Dökümün alt satırı bir beklentiyi bozuyor. Hücre işlevleriyle çalışan altı derste 16 karardan 4'ü
okunuyor, sayfanın bütününe bakan dört derste 16 karardan 3'ü. Tablonun bütününe geçmek durumu
düzeltmiyor: tekilleştirme ve doldurma kararlarının **hiçbirini** bırakmıyor, çünkü ikisi de
sayfayı değiştirip kendini siliyor.

Son satır bedeli veriyor. Otuz iki kararın tamamını yazmak 32 hücre eder. Sayfanın kendisi 1.877
satır ve 5 sütunla 9.385 veri hücresi tutuyor, yani kaydın bedeli sayfanın **binde 3,4 kadarı**.
Kararların yazılmamasının nedeni maliyet değildir.

#### Sayfa Büyüdükçe Ne Büyüyor

```js
// boyut.mjs — sayfa buyudukce ne buyuyor. Sure olculmez; sayilan sey tek bir
// yeniden hesapta degerlendirilen hucre sayisi ve bir satirlik hatanin ozette
// gorunur olup olmadigidir. MODELDIR: hucre bir eslemede durur.
import { y } from "./kapanis.mjs";
const HARF = (j) => String.fromCharCode(65 + j);
const DEGER = (i) => 10 + ((i * 7) % 40) * 0.5;

// Iki formul turu. Sabit isli formul yalnizca kendi satirini ve bir parametre
// hucresini okur. Aralik okuyan formul her satirda butun sutunu tarar --
// kosullu toplama ve arama islevleri boyle davranir.
const kur = (n, aralikli) => {
  const h = new Map(); let sayac = 0;
  h.set("P1", 8.4);
  for (let i = 0; i < n; i++) h.set(`A${i + 2}`, DEGER(i));
  const oku = (ad) => { sayac++; const v = h.get(ad);
    return typeof v === "function" ? v() : v; };
  for (let i = 0; i < n; i++) h.set(`B${i + 2}`, aralikli
    ? () => { let t = 0;
        for (let j = 0; j < n; j++) { const v = oku(`A${j + 2}`); if (v > 20) t += v; }
        return t; }
    : () => oku(`A${i + 2}`) * oku("P1"));
  return () => { sayac = 0;
    for (let i = 0; i < n; i++) oku(`B${i + 2}`); return sayac; };
};

const y1 = y([10, 16, 20, 12]);
console.log("tek yeniden hesapta degerlendirilen hucre sayisi");
console.log(y1(["satir", "sabit isli sutun", "aralik okuyan sutun", "oran"]));
let onceki = null;
for (const n of [1877, 3754, 7508]) {
  const [a, b] = [kur(n, false)(), kur(n, true)()];
  console.log(y1([n, a, b, (b / a).toFixed(0)]));
  if (onceki) console.log(`  satir iki katina cikinca sabit isli sutun ` +
    `${(a / onceki[0]).toFixed(1)} katina, aralik okuyan sutun ` +
    `${(b / onceki[1]).toFixed(1)} katina cikti`);
  onceki = [a, b];
}
console.log(`iki sutunun her biri tek bir formul metniyle yazildi ve 7508 hucreye ` +
  `kopyalandi; sayfanin ucuza yaptigi is budur`);

// Bir satirlik hata: aralik son satiri disarida birakiyor. Degisimin gorunur
// sayilmasi icin esik yuzde 1'dir, cunku ozetler iki anlamli basamakla okunuyor.
const ESIK = 1.0;
const OZET = [["toplam", (a) => a.reduce((x, z) => x + z, 0)],
  ["ortalama", (a) => a.reduce((x, z) => x + z, 0) / a.length],
  ["en buyuk", (a) => Math.max(...a)], ["sayim", (a) => a.length]];
const y2 = y([10, 12, 12, 12, 12, 14]);
console.log(`\naralik bir satir kisa alinirsa ozet yuzde kac degisir (esik ${ESIK})`);
console.log(y2(["satir", ...OZET.map((o) => o[0]), "gorunur"]));
for (const n of [60, 1877]) {
  const tam = Array.from({ length: n }, (_, i) => DEGER(i)), eksik = tam.slice(0, n - 1);
  const fark = OZET.map(([, f]) => 100 * Math.abs(f(tam) - f(eksik)) / f(tam));
  console.log(y2([n, ...fark.map((v) => v.toFixed(2)),
    `${fark.filter((v) => v > ESIK).length}/${OZET.length}`]));
}
console.log(`ayni hata 60 satirlik sayfada iki ozette gorunur, 1877 satirlik sayfada ` +
  `hicbirinde gorunmez: sayfa buyudukce hatanin kendisi degil gorunurlugu kuculur`);
```

```
tek yeniden hesapta degerlendirilen hucre sayisi
satir     sabit isli sutun aralik okuyan sutun        oran
1877                  5631             3525006         626
3754                 11262            14096270        1252
  satir iki katina cikinca sabit isli sutun 2.0 katina, aralik okuyan sutun 4.0 katina cikti
7508                 22524            56377572        2503
  satir iki katina cikinca sabit isli sutun 2.0 katina, aralik okuyan sutun 4.0 katina cikti
iki sutunun her biri tek bir formul metniyle yazildi ve 7508 hucreye kopyalandi; sayfanin ucuza yaptigi is budur

aralik bir satir kisa alinirsa ozet yuzde kac degisir (esik 1)
satir           toplam    ortalama    en buyuk       sayim       gorunur
60                1.40        0.27        0.00        1.67           2/4
1877              0.04        0.01        0.00        0.05           0/4
ayni hata 60 satirlik sayfada iki ozette gorunur, 1877 satirlik sayfada hicbirinde gorunmez: sayfa buyudukce hatanin kendisi degil gorunurlugu kuculur
```

İlk tablo iki formül türünü yan yana koyuyor. Sabit işli bir sütun her satırda yalnız kendi satırını
ve bir parametre hücresini okur; 1.877 satırda tek bir yeniden hesap 5.631 hücre değerlendirmesi
ister ve satır iki katına çıkınca sayı iki katına çıkar. Aralık okuyan bir sütun — koşullu toplama,
dikey arama, eşleşme–dizin bu türdendir — her satırda bütün sütunu tarar; aynı sayfada 3.525.006
değerlendirme ister, yani **626 kat** ve satır iki katına çıkınca sayı **dört katına** çıkar.

Sınır buradadır ve donanımla ilgisi yoktur: sayfa on kat büyüdüğünde sabit işli sütun on kat,
aralık okuyan sütun yüz kat ağırlaşır. Aynı tablonun son satırı aracın öbür yüzünü gösteriyor: iki
sütunun her biri **tek bir formül metniyle** yazıldı ve 7.508 hücreye kopyalandı. Bir soruyu bir kez
yazıp binlerce satıra yaymak elektronik tablonun ucuza yaptığı iştir.

İkinci tablo boyutun ikinci yüzünü ölçüyor. Bir aralık son satırı dışarıda bırakırsa 60 satırlık bir
sayfada toplam yüzde 1,40, sayım yüzde 1,67 oynar ve dört özetten **ikisi** görünür biçimde değişir.
Aynı hata 1.877 satırlık sayfada toplamı yüzde 0,04 oynatır ve dört özetin **hiçbirinde** görünmez.
Hata küçülmedi, sayfa büyüdü. Her satırın özet üzerindeki payı düştükçe gözle denetim işe yaramaz
hale gelir — oysa o, kararların yazılmadığı bir sayfada elde kalan tek denetimdir.

#### Özet

- Kursun on bir dersinde alınan 32 karardan yaygın yazımda 7'si geri okunuyor, 25'i hiçbir hücrede
  yok; oran yüzde 22.
- Geri okunan yedinin altısı bir adımdadır ve hepsinde kararı taşıyan şey işlevin ya da başlığın
  kendi adıdır; kaybolan yirmi beşi bir sabite ya da bir işlece dönüşmüştür.
- Kararların tamamını yazmak 32 hücre eder, sayfanın 9.385 veri hücresinin binde 3,4 kadarı;
  yazılmamalarının nedeni maliyet değildir.
- Aralık okuyan bir sütun 1.877 satırda sabit işli sütundan 626 kat pahalıdır ve satır iki katına
  çıkınca dört katına çıkar; sabit işli sütun iki katına çıkar.
- Aynı bir satırlık aralık hatası 60 satırlık sayfada dört özetten ikisinde görünür, 1.877 satırlık
  sayfada hiçbirinde görünmez.
- Aynı sayfa iki formül metniyle 7.508 hücreyi doldurur; aracın ucuza yaptığı iş budur.

#### Kurs Kapanışı

| Ders | Sayı | Değişen karar | Fark | Karar geri okunabilir mi |
|---|---|---|---|---|
| temeller/01 çalışma sayfası | 13 427,16 toplam | başvuru türü | 3 354,98, 59 hücre | 1/2, iki adım |
| temeller/02 temel işlevler | 708,5 m³ toplam | metin sayıların sayılması | 967,2 m³, yüzde 26,7 | 1/3, bir adım |
| temeller/03 mantıksal işlevler | 26 219,52 tutar | sınır karşılaştırması | 26 698,32, yüzde 35,5 | 0/3, okunamaz |
| temeller/04 metin işlevleri | 8 eşleşen kayıt | kırpma ve harf dönüşümü | 48 kayıt, 1204,8 m³ | 2/3, bir adım |
| temeller/05 tarih işlevleri | 6,33 gün ortalama | gün sayma kuralı | 5,20 ve 7,33 gün | 0/3, okunamaz |
| temeller/06 arama işlevleri | 1204,8 m³ toplam | gömülü sütun numarası | 1084,6 m³, 48 kayıt | 0/2, okunamaz |
| özet/01 yinelenen kayıtlar | 1.877 satır kalır | tekilleştirme ölçütü | 1.400 satır, 12 121,0 m³ | 0/4, okunamaz |
| özet/02 eksik veri | 19,23 m³ ortalama | boş hücre dışlanır mı | 18,04 ve 20,54 m³ | 0/4, okunamaz |
| özet/03 özet tablolar | kuzey 10.905,8 m³ | kırılımın ölçüsü | doğu ve merkez birinci | 2/4, başlıkta |
| özet/04 temel grafikler | 1,63 çubuk oranı | eksen başlangıcı | 13,00 oran, 8,0 kat | 1/4, aralık |
| özet/05 sınırlar | 7 karar okunur | kararın yazıldığı yer | 25 karar kayıp | 7/32, yüzde 22 |

Kursun kuralı bu tablodan çıkıyor: **elektronik tablo bir hesap aracıdır, bir kayıt aracı değildir;
hücreye gömülü karar, yazılmamış karardır.** On bir derste hiçbir hesap yanlış yapılmadı ve hiçbir
işlem hata vermedi; kaybolan şey hesap değil, hesabın hangi soruya cevap verdiğiydi. Aynı tabloyu
iki kişi açtığında aynı sayıyı bulmasını sağlayan şey tablonun kendisi değil, tablonun **dışında**
tutulan tanımdır. Aracın ucuza yaptığı iş de aynı ölçüyle yazılabilir: tek bir formül metni 7.508
hücreye yayılır, kararların tamamını yazmak ise sayfanın binde 3,4'ü kadar yer tutar.

Bu on bir ders boyunca kararlar hücrelere gömüldü ve çoğu geri okunamadı. Ama daha temel bir soru
hiç sorulmadı: bu veri tabloya **nereden** geldi ve gelirken ne oldu. 1.877 satırın hepsi bir saha
formundan, bir okuma cihazından ya da bir başka sistemin dışa aktarımından geçti; her geçişte bir
alan yeniden adlandırıldı, bir tarih biçimlendi, bir kod kırpıldı. Sonraki kurs, **Veri Toplama ve
Hazırlama**, tablonun başladığı yerden öncesini açar ve verinin tabloya girene kadar kaç elden
geçtiğini sayar.

