itsrda

Giriş-Çıkış Kayıtlarından Çalışma Süresi Hesaplamak: Eşleştirme Algoritması

Kart okuyucudan gelen ham GİRİŞ/ÇIKIŞ kayıtlarını vardiyalara dönüştürmek; eksik çıkış, gece vardiyası ve çift okuma gibi gerçek hayat sorunlarını Python örnekleriyle ele alıyoruz.

Bir kart okuyucu size yalnızca bir olay listesi verir: "şu kart şu saatte okutuldu". Oysa bir işletmenin ihtiyacı "bu kişi bu ay kaç saat çalıştı, kaç gün geldi, kaç saat fazla mesai yaptı" sorularıdır. Aradaki köprü, ham olayları vardiyalara dönüştüren eşleştirme mantığıdır. Basit görünür, ama gerçek veride her zaman beklenmedik durumlar çıkar. Bu yazı, bu mantığı adım adım ve test edilebilir biçimde kurar.

Veri modeli

Tek tabloluk bir model yeterlidir:

Alan Açıklama
personel_id Kişi
islem GIRIS veya CIKIS
tarih_saat Olayın zamanı

Önemli bir ilke: ham olayları asla değiştirmeyin veya silmeyin. Hesaplama bu olaylardan her seferinde yeniden üretilir; böylece mantığı düzelttiğinizde eski aylar da düzelir. Düzeltme gerekiyorsa, ayrı bir "düzeltme" kaydı ekleyin.

En basit algoritma

Bir kişinin olaylarını zamana göre sıralayın. Bir GİRİŞ gördüğünüzde bir vardiya açın; sıradaki ÇIKIŞ onu kapatır:

from datetime import datetime, timedelta

def vardiyalari_esle(olaylar):
    """olaylar: [(islem, datetime), ...] zamana göre sıralı, tek kişi."""
    vardiyalar, acik = [], None
    for islem, zaman in olaylar:
        if islem == "GIRIS":
            if acik is None:
                acik = zaman
            # acik varken ikinci GIRIS: tekrar okuma, yoksay
        elif islem == "CIKIS":
            if acik is not None:
                vardiyalar.append((acik, zaman))
                acik = None
            # acik yokken CIKIS: giriş kaydı yok, yoksay
    return vardiyalar, acik

Fonksiyon, tamamlanmış vardiyaların listesini ve (varsa) hâlâ açık kalan girişi döndürür. Bu hâliyle gece vardiyasını da doğru işler, çünkü eşleştirme takvim gününe değil, olay sırasına bakar: 22:00 girişi ile ertesi gün 06:00 çıkışı tek bir vardiyadır. Günlere göre gruplayıp eşleştirmeye çalışmak, gece vardiyasını ikiye böler ve yanlış sonuç verir; yaygın bir hatadır.

Gerçek hayat sorun 1: unutulan çıkış

Çalışan çıkışta kartı okutmayı unutursa, açık bir giriş kalır. Sonraki giriş geldiğinde ne yapacaksınız? Seçenekler:

  1. Eski vardiyayı geçersiz sayıp sonrakini yeni vardiya olarak açmak. Bu, uydurma süre eklemez; unutulan gün "eksik" olarak işaretlenir ve yönetici düzeltir.
  2. Eski vardiyayı standart bir süreyle kapatmak. Bu, bir tahmindir ve çalışan aleyhine ya da lehine hata üretebilir.

Pratik ve dürüst yöntem birincisidir, bir üst süre sınırı ile birlikte: bir vardiya, örneğin 18 saati aşıyorsa büyük olasılıkla çıkış unutulmuştur.

MAKS_VARDIYA = timedelta(hours=18)

def vardiyalari_esle(olaylar, simdi=None):
    simdi = simdi or datetime.now()
    vardiyalar, eksik, acik = [], [], None
    for islem, zaman in olaylar:
        if islem == "GIRIS":
            if acik is not None:
                if zaman - acik > MAKS_VARDIYA:
                    eksik.append(acik)      # önceki çıkış unutulmuş
                    acik = zaman
                # aksi halde tekrar okuma, yoksay
            else:
                acik = zaman
        elif islem == "CIKIS" and acik is not None:
            if zaman - acik <= MAKS_VARDIYA:
                vardiyalar.append((acik, zaman))
            else:
                eksik.append(acik)
            acik = None
    if acik is not None and simdi - acik > MAKS_VARDIYA:
        eksik.append(acik)
        acik = None
    return vardiyalar, eksik, acik

Dönen eksik listesi, bir "çıkış unutulanlar" uyarısı için doğrudan kullanılabilir. Bu listeyi yöneticiye göstermek, sorunun ay sonunda değil, ertesi gün fark edilmesini sağlar.

Gerçek hayat sorun 2: çift okuma

Okuyucuya art arda iki kez kart tutulabilir ya da cihaz aynı olayı iki kez göndermiş olabilir. Yukarıdaki mantık, açık vardiya varken gelen ikinci girişi zaten yoksayar. Çıkış için de, açık vardiya yokken gelen çıkış yoksayılır. Ek olarak ardışık okumalar arasına kısa bir eşik koymak (örneğin 30-60 saniye) bazı donanımlarda faydalı olabilir.

Gerçek hayat sorun 3: mola

Çoğu kart sistemi, öğle molasında çıkış-giriş yapıldığında iki ayrı vardiya üretir. Bunun toplam süreye etkisi doğrudur (molada geçen süre sayılmaz), ama "kişi kaç gün geldi" hesabını bozabilir. Bu yüzden gün sayısını vardiya sayısından değil, farklı takvim günlerinden türetin:

def calisilan_gun(vardiyalar):
    return len({giris.date() for giris, cikis in vardiyalar})

Gece vardiyasında giriş tarihi başlangıç günü sayılır; bu bir tercihtir ve kuralı açıkça belgelemeniz gerekir.

Toplam süre ve fazla mesai

def toplam_dakika(vardiyalar):
    return sum(int((c - g).total_seconds() // 60) for g, c in vardiyalar)

Fazla mesai tanımı işyerine ve mevzuata göre değişir. Basit bir yaklaşım, günlük standart süreyi (örneğin 8,5 saat) aşan kısmı hesaplamaktır; ama haftalık sınırlar ve resmî tatil kuralları hukuki bir konudur ve yazılım yalnızca bir ön hesap sağlamalıdır. Bordroya esas bir hesap için mutlaka muhasebe veya hukuk danışmanınızla doğrulayın.

Ay sınırı

Bir vardiya ayın son günü başlayıp ertesi ayın ilk günü bitebilir. Aylık raporda bu vardiyayı hangi aya yazacağınızı baştan belirleyin. En yaygın kural, girişin ait olduğu aydır. Sorguyu yazarken ayın başından bir gün öncesinden başlayıp ayın sonundan bir gün sonrasına kadar olayları çekmek, sınırdaki vardiyaların eşleşmesini sağlar; sonra yalnızca girişi o aya ait olanları sayarsınız.

Test yazmak

Bu tür mantığın doğruluğunu gözle denetlemek zordur. Küçük testler yazın:

def t(s): return datetime.strptime(s, "%Y-%m-%d %H:%M")

def test_gece_vardiyasi():
    v, e, a = vardiyalari_esle([("GIRIS", t("2026-10-01 22:00")), ("CIKIS", t("2026-10-02 06:00"))])
    assert len(v) == 1 and (v[0][1] - v[0][0]).seconds == 8 * 3600

def test_unutulan_cikis():
    v, e, a = vardiyalari_esle([("GIRIS", t("2026-10-01 08:00")), ("GIRIS", t("2026-10-02 08:00")),
                                ("CIKIS", t("2026-10-02 17:00"))])
    assert len(v) == 1 and len(e) == 1

def test_cift_okuma():
    v, e, a = vardiyalari_esle([("GIRIS", t("2026-10-01 08:00")), ("GIRIS", t("2026-10-01 08:00")),
                                ("CIKIS", t("2026-10-01 17:00"))])
    assert len(v) == 1

Gerçek verinizdeki tuhaf bir günü bulduğunuzda, onu bir test olarak ekleyin; mantık zamanla sağlam bir güvenceye dönüşür.

Veriyi korumak

Giriş-çıkış verisi kişisel veridir. Hesaplamayı yapan panelin erişim kontrolü, kayıtların saklama süresi ve yedekleri için KVKK rehberine göz atın. Ham olaylarınızı kaybetmemek için de düzenli veritabanı yedeği şarttır: hesaplama her zaman yeniden yapılabilir, ham olaylar ise yeniden üretilemez.

Not: Komutları üretim ortamında çalıştırmadan önce yedek alın ve önce test ortamında deneyin. Bir adım sizde farklı davranıyorsa bize yazın, rehberi güncelleyelim.