Ana içeriğe geç
Kesit Python
Temel · Bitirme

Bitirme projesi: Anadolu Market sipariş raporu

Temel seviyede öğrendiğin her şeyi birleştir: sipariş listesinden toplam ciro, şehre göre toplam, en çok satan ürün ve biçimli rapor metni üreten, dosyaya yazan küçük bir program.

Uygulamada aç, 22 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

İş isteği

Anadolu Market operasyon müdürü senden şunu istiyor: "Her sabah dünkü siparişlerin özetini istiyorum: toplam ciro, hangi şehre ne kadar sattık, en çok satan ürün hangisi. Düz metin olsun, e-postaya yapıştırayım." Elinde sipariş kayıtları var; bu derste isteği adım adım çalışan bir programa çevireceksin. Alıştırmalar projenin parçalarıdır: her biri bir fonksiyon, sonuncusu hepsini birleştirir.

Veri

Her sipariş bir sözlük: id, musteri, sehir, urun, adet, birim_fiyat. Sipariş tutarı adet × birim_fiyat. Veriyi kodun içinde küçük bir liste olarak tutuyoruz; gerçekte CSV'den gelir, onu da 6. derste öğrendin.

SIPARISLER = [
    {"id": 1001, "musteri": "Ayşe", "sehir": "Ankara", "urun": "Kulaklık", "adet": 2, "birim_fiyat": 899.90},
    {"id": 1002, "musteri": "Mehmet", "sehir": "İzmir", "urun": "Klavye", "adet": 1, "birim_fiyat": 1249.00},
    {"id": 1003, "musteri": "Elif", "sehir": "Ankara", "urun": "Mouse", "adet": 3, "birim_fiyat": 349.50},
    {"id": 1004, "musteri": "Can", "sehir": "İstanbul", "urun": "Kulaklık", "adet": 1, "birim_fiyat": 899.90},
    {"id": 1005, "musteri": "Zeynep", "sehir": "İzmir", "urun": "Mouse", "adet": 2, "birim_fiyat": 349.50},
    {"id": 1006, "musteri": "Burak", "sehir": "Ankara", "urun": "Monitör", "adet": 1, "birim_fiyat": 4599.00},
]
print(len(SIPARISLER), "sipariş")
print(SIPARISLER[0])

Adım 1: Planı fonksiyonlara böl

Büyük işi tek seferde yazmaya kalkma. Önce isteği parçala; her parça tek iş yapan, girdi alıp çıktı döndüren bir fonksiyon olsun:

  1. siparis_tutari(siparis): tek siparişin tutarı.
  2. toplam_ciro(siparisler): tüm tutarların toplamı.
  3. sehre_gore_ciro(siparisler): şehir → toplam tutar sözlüğü.
  4. en_cok_satan(siparisler): adet toplamı en yüksek ürün ve adedi.
  5. rapor_metni(siparisler): yukarıdakileri çağırıp biçimli metin üretir.
  6. raporu_kaydet(metin, yol): metni dosyaya yazar.

Hesap fonksiyonları (1-4) saf: yalnızca veri alır, sonuç döndürür, hiçbir şey yazdırmaz. Sunum (5) ve dosya (6) ayrıdır. Böylece her hesabı tek başına test edebilir, raporun biçimini değiştirirken hesaba dokunmazsın.

Adım 2: Tek siparişin tutarı ve toplam ciro

SIPARISLER = [
    {"id": 1001, "sehir": "Ankara", "urun": "Kulaklık", "adet": 2, "birim_fiyat": 899.90},
    {"id": 1002, "sehir": "İzmir", "urun": "Klavye", "adet": 1, "birim_fiyat": 1249.00},
    {"id": 1003, "sehir": "Ankara", "urun": "Mouse", "adet": 3, "birim_fiyat": 349.50},
]

def siparis_tutari(siparis):
    """adet × birim fiyat, 2 ondalığa yuvarlanmış."""
    return round(siparis["adet"] * siparis["birim_fiyat"], 2)

def toplam_ciro(siparisler):
    toplam = 0.0
    for s in siparisler:
        toplam += siparis_tutari(s)
    return round(toplam, 2)

print(siparis_tutari(SIPARISLER[0]))
print(toplam_ciro(SIPARISLER))

Küçük fonksiyon büyüğünün içinde kullanılır; tutar hesabı değişirse (örneğin indirim eklenirse) yalnız siparis_tutari değişir.

Adım 3: Şehre göre gruplama

Döngüler dersindeki sayaç kalıbının toplama versiyonu: anahtar şehir, değer biriken tutar.

def sehre_gore_ciro(siparisler):
    ciro = {}
    for s in siparisler:
        sehir = s["sehir"]
        ciro[sehir] = round(ciro.get(sehir, 0.0) + siparis_tutari(s), 2)
    return ciro

print(sehre_gore_ciro(SIPARISLER))

Adım 4: En çok satan ürün

Önce ürün → toplam adet sözlüğü kur, sonra en büyüğü bul. max fonksiyonuna key vererek sözlüğü değerine göre tarayabilirsin.

def en_cok_satan(siparisler):
    adetler = {}
    for s in siparisler:
        adetler[s["urun"]] = adetler.get(s["urun"], 0) + s["adet"]
    if not adetler:
        return None, 0
    urun = max(adetler, key=adetler.get)
    return urun, adetler[urun]

print(en_cok_satan(SIPARISLER))
Boş veriHiç sipariş yoksa max boş sözlükte hata verir. Erken dönüşle (None, 0) döndürmek, raporun "Dün sipariş olmadı" diyebilmesini sağlar. Her hesap fonksiyonunda "boş gelirse ne olur?" diye sor.

Adım 5: Bozuk kayıtlara dayanıklılık

Gerçek veride adet metin olarak gelebilir, birim_fiyat eksik olabilir. Hesabı çökertmek yerine bozuk kayıtları ayır ve raporda sayısını göster. Temizleme işini de ayrı bir fonksiyona ver:

def temizle(ham_siparisler):
    """Geçerli siparişleri ve atlanan kayıt sayısını döndürür."""
    temiz, atlanan = [], 0
    for s in ham_siparisler:
        try:
            kayit = dict(s)
            kayit["adet"] = int(s["adet"])
            kayit["birim_fiyat"] = float(s["birim_fiyat"])
            if kayit["adet"] <= 0:
                raise ValueError("adet pozitif olmalı")
            temiz.append(kayit)
        except (KeyError, ValueError, TypeError):
            atlanan += 1
    return temiz, atlanan

ham = [
    {"id": 1, "sehir": "Ankara", "urun": "Mouse", "adet": "3", "birim_fiyat": "349.5"},
    {"id": 2, "sehir": "İzmir", "urun": "Klavye", "adet": "yok", "birim_fiyat": 1249},
    {"id": 3, "sehir": "Bursa", "urun": "Kulaklık", "adet": 1},
]
temiz, atlanan = temizle(ham)
print(len(temiz), "geçerli,", atlanan, "atlandı")
print(temiz[0])

Adım 6: Rapor metni ve dosya

Metin biçimlendirme dersindeki hizalama ve join kalıbı. Rapor fonksiyonu hesap yapmaz; hesap fonksiyonlarını çağırır ve satırları dizer.

from datetime import date

def rapor_metni(siparisler, tarih, atlanan=0):
    toplam = toplam_ciro(siparisler)
    sehirler = sehre_gore_ciro(siparisler)
    urun, adet = en_cok_satan(siparisler)
    satirlar = [
        f"ANADOLU MARKET GÜNLÜK RAPOR - {tarih:%d.%m.%Y}",
        "=" * 40,
        f"Sipariş sayısı : {len(siparisler)}",
        f"Toplam ciro    : {toplam:,.2f} TL",
        "",
        "Şehre göre ciro",
    ]
    for sehir, ciro in sorted(sehirler.items(), key=lambda x: x[1], reverse=True):
        satirlar.append(f"  {sehir:<12}{ciro:>14,.2f} TL")
    satirlar.append("")
    satirlar.append(f"En çok satan   : {urun} ({adet} adet)")
    if atlanan:
        satirlar.append(f"Atlanan kayıt  : {atlanan}")
    return "\n".join(satirlar)

rapor = rapor_metni(SIPARISLER, date(2024, 3, 5), atlanan=1)
print(rapor)

with open("gunluk_rapor.txt", "w", encoding="utf-8") as f:
    f.write(rapor + "\n")
print("\nDosyadan geri okundu, satır sayısı:", len(open("gunluk_rapor.txt", encoding="utf-8").read().splitlines()))

Kontrol listesi

SoruNeden
Her fonksiyon tek iş mi yapıyor?Test ve değişiklik kolaylığı
Hesap fonksiyonları print ediyor mu?Etmemeli; sunumdan ayrı kalmalı
Boş liste verince çöküyor mu?Sipariş olmayan gün de rapor ister
Bozuk kayıt tüm raporu durduruyor mu?Durdurmamalı; sayılıp raporlanmalı
Para 2 ondalıkla ve binlik ayırıcıyla mı?Müdür Excel'den alışkın
Derinleş

Derinleş: aynı iskeletle yeni projeler

Sipariş raporunu bitirdin. Asıl kazanım raporun kendisi değil, iskelet: oku → temizle → hesapla → sun → kaydet. İş hayatında karşına çıkacak küçük otomasyonların çoğu bu beş adımın farklı verilerle tekrarıdır. Bu bölümde iskeleti açıkça adlandırıyor, programı bilgisayarında çalışacak bir dosya düzenine sokuyor ve dört yeni proje varyasyonu veriyoruz. Alıştırmalarda bunlardan birini, Ilgaz Bank gün sonu raporunu, adım adım rehberle sıfırdan inşa edeceksin.

Beş adımlık iskelet

AdımSipariş raporuBanka gün sonuStok uyarısıİK mesai özeti
OkuCSV → sözlük listesihareket CSV metnistok sözlüğügiriş-çıkış kayıtları
Temizletemizlehatalı satırı saynegatif adedi eleeksik saati ayır
Hesaplaciro, şehir, en çok satanbakiye, şüpheli işlemeşik altı ürünlertoplam ve fazla mesai
Sunrapor_metnibanka_raporusipariş listesikişi başı tablo
Kaydettxt dosyasıtarihli txttedarikçiye CSVbordroya CSV

Kural aynı: her adım ayrı fonksiyon; hesap fonksiyonları saf (yazdırmaz, dosyaya dokunmaz, girdiyi değiştirmez), girdi/çıktı yalnızca uçlarda. Böylece "rapor biçimini değiştir" isteği hesaplara, "yeni veri kaynağı" isteği rapora dokunmaz.

Programın girişi: main ve __name__ koruması

Fonksiyonları yazdın; peki program nereden başlar? Python dosyası iki şekilde kullanılabilir: doğrudan çalıştırılır (python rapor.py) ya da başka bir dosya onu import eder (testler gibi). if __name__ == "__main__": bloğu yalnızca birinci durumda çalışır. Ayarlanabilir değerleri dosyanın başına BÜYÜK_HARFLİ sabitler olarak koy; ileride bunları ayar dosyasından ya da komut satırından okuyacaksın.

ESIK_TL = 10_000            # ayarlar en üstte, tek yerde
CIKTI = "gun_sonu.txt"

def oku():
    return [("TR01", "cekme", 1200.5), ("TR02", "havale", 15000.0), ("TR01", "yatirma", 5000.0)]

def supheli(hareketler, esik):
    return [h for h in hareketler if h[1] != "yatirma" and h[2] >= esik]

def rapor(hareketler, liste):
    return f"{len(hareketler)} hareket, {len(liste)} şüpheli"

def main():
    hareketler = oku()
    liste = supheli(hareketler, ESIK_TL)
    metin = rapor(hareketler, liste)
    with open(CIKTI, "w", encoding="utf-8") as f:
        f.write(metin + "\n")
    print(metin)

if __name__ == "__main__":
    main()
Neden main()?Kodu modül seviyesine dağınık yazarsan, test dosyası import rapor dediği anda rapor üretilir ve dosya yazılır. main içine alınca import yalnız fonksiyonları tanımlar; testler onları tek tek çağırır.

Kendi projeni test et: küçük assert seti

Henüz pytest bilmiyorsun ama bilmen gerekmiyor: bilinen küçük girdiler için beklenen çıktıyı assert ile yazan bir testler() fonksiyonu, kodu her değiştirdiğinde "bir şeyi bozdum mu?" sorusunu saniyede cevaplar. En az üç durum test et: normal veri, boş veri, bozuk veri.

def sehre_gore(siparisler):
    sonuc = {}
    for sehir, tutar in siparisler:
        sonuc[sehir] = round(sonuc.get(sehir, 0) + tutar, 2)
    return sonuc

def testler():
    assert sehre_gore([("Ankara", 10.1), ("Ankara", 0.2)]) == {"Ankara": 10.3}, "kuruş yuvarlama"
    assert sehre_gore([]) == {}, "boş gün"
    assert sehre_gore([("İzmir", 5)]) == {"İzmir": 5}, "tek kayıt"
    print("Tüm testler geçti")

testler()

Bilgisayarında proje düzeni

Tarayıcıda tek hücrede çalıştın; gerçek projede birkaç dosya olur. Basit ve yeterli düzen:

gun_sonu_raporu/
├── rapor.py          # fonksiyonlar + main()
├── test_rapor.py     # assert'li testler (sonra pytest)
├── veri/
│   └── hareketler.csv
├── cikti/            # üretilen raporlar (git'e eklenmez)
└── README.md         # ne yapar, nasıl çalışır
cd gun_sonu_raporu
python rapor.py              # Windows
python3 rapor.py             # macOS / Linux
python test_rapor.py         # testler
BelirtiNedenÇözüm
can't open file 'rapor.py'Terminal başka klasördeÖnce cd ile proje klasörüne geç
FileNotFoundError: veri/hareketler.csvGöreli yol çalışma klasörüne görePath(__file__).parent / "veri" / ...
python komutu Microsoft Store'u açıyorWindows takma adıpy rapor.py ya da kurulumu PATH'e ekle
Türkçe harfler bozukencoding verilmemişHer open'a encoding="utf-8"

Dört varyasyon: kendine meydan oku

  1. Ilgaz Bank gün sonu (alıştırmalarda rehberli): hareket CSV'sini oku, hesap bakiyelerini güncelle, eşiği aşan çekme/havaleleri işaretle, raporu yaz.
  2. Stok uyarı raporu: ürün başına farklı kritik eşik, eşiği olmayana varsayılan eşik; kritik ürünleri adede göre sırala, tedarikçiye gönderilecek listeyi üret.
  3. İK mesai özeti: çalışan, gün ve saat kayıtlarından kişi başı toplam saat ve günlük 9 saati aşan fazla mesai.
  4. Dönem karşılaştırma: dünkü ve bugünkü şehir cirolarını karşılaştır, yüzde değişimi hesapla; dün satış olmayan şehirde yüzde yerine "yeni" yaz.
Girdiyi değiştirmeTemizleme fonksiyonunda kayit = s yazıp kayit["adet"] = int(...) dersen, çağıranın orijinal listesini de değiştirirsin; aynı veriyle ikinci rapor farklı çıkar. Önce kopyala: kayit = dict(s). Bakiye sözlüğü için de aynısı: bakiye = dict(acilis).

Bitirmeden önce: projeyi "teslim edilebilir" yapmak

  • README'de üç şey: ne yapar, nasıl çalıştırılır (komut), girdi dosyasının biçimi.
  • Rapor dosyasının adında tarih: gun_sonu_2026-10-27.txt.
  • Hatalı satır sayısı raporda görünür; sıfır değilse ayrıca hatalı satırları bir dosyaya yaz.
  • Sihirli sayılar (10000, 9 saat) sabit olarak en üstte.
from datetime import date

RAPOR_TARIHI = date(2026, 10, 27)          # gerçek betikte date.today()
ham = ["1,TR01,5000", "2,TR01,abc", "3,TR02,300", "4,TR02"]

gecerli, hatalilar = [], []
for no, satir in enumerate(ham, start=1):
    parca = satir.split(",")
    if len(parca) == 3 and parca[2].isdigit():
        gecerli.append(parca)
    else:
        hatalilar.append(f"{no}: {satir}")

ad = f"gun_sonu_{RAPOR_TARIHI.isoformat()}.txt"
with open(ad, "w", encoding="utf-8") as f:
    f.write(f"Geçerli hareket: {len(gecerli)}\n")
if hatalilar:
    with open(ad.replace(".txt", "_hatalar.txt"), "w", encoding="utf-8") as f:
        f.write("\n".join(hatalilar) + "\n")
print("Rapor:", ad)
print("Hatalı satırlar:", hatalilar)
Gerçek işteBir analist ya da yeni başlayan geliştiricinin ilk aylarda yazdığı işlerin çoğu tam olarak bu ölçektedir: bir klasörden dosya okuyan, birkaç hesap yapan, e-postaya yapıştırılacak ya da paylaşılan klasöre bırakılacak bir rapor üreten 100–200 satırlık betik. Bu betik bir kez yazılıp unutulmaz; her ay biri "şu sütunu da ekler misin?" der. İskeleti temiz kurduysan değişiklik on dakika sürer, kurmadıysan yeniden yazarsın. Portfolyon için de bu varyasyonlardan ikisini GitHub'a README'li biçimde koymak, iş görüşmesinde "Python biliyorum" demekten çok daha ikna edicidir.
Uzmanlaş

Bu program gerçekte nasıl çalışır?

Orta seviyede aynı projeyi CSV modülüyle dosyadan okuyacak, dataclass ile siparişi tip güvenli bir nesneye çevirecek, collections.Counter ve defaultdict ile gruplama kodunu yarıya indirecek, unittest ile her fonksiyona test yazacaksın. İleri seviyede ise pandas ile groupby("sehir")["tutar"].sum() tek satır olacak. Temel bu: parçala, saf tut, biçimi ayır.

Zamanlama

"Her sabah" isteği kodun içine girmez; işletim sisteminin zamanlayıcısı (cron, Görev Zamanlayıcı) betiği 07:00'de çalıştırır. Betik if __name__ == "__main__": bloğunda veriyi okur, raporu üretir, dosyaya yazar ve çıkar. Betiğin kendisi "ne zaman" sorusunu bilmez; bu, sorumlulukları ayırmanın bir başka örneğidir.

Alıştırmalara geç (22)