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:
- 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.
- 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.