Ana içeriğe geç

Bedelli Limit Hesapları (Ledger)

Bedelli üretim limiti (Md.7), bir tüketim tesisinin ihtiyaç fazlası üretiminin bedelli olarak ödenebilecek üst sınırıdır. Bu limitin üzerinde kalan ihtiyaç fazlası SKB ödemeli (sistem kullanım bedeli) olarak tespit edilir. Zeus, resmî tarafta LÜM'de tutulan bu limiti bir hesap + salt-append hareket defteri (ledger) ile aynalar.


Temel kavramlar

  • Hesap (account) — bir resmî tesisin bir yıl için bedelli limit hesabıdır. Tesis × yıl tekildir (aynı tesis + aynı yıl için tek hesap).
  • Hareket (movement) — hesabın limitindeki her değişim bir hareket satırıdır. Hareketler UPDATE/DELETE edilmez (salt-append); düzeltme yeni bir hareketle yapılır (Md.15 izlenebilirlik).
  • Kalan limit (balance) — mutable bir kolonda tutulmaz; initial_limit_kwh + SUM(hareket.amount_kwh) ile canlı hesaplanır (bedelli_limit_balances görünümü).
Neden mutable "kalan" kolonu yok?

Kalan bedelli limit, gerçek-zamanlı SKB kararının (Md.7(5)) girdisidir. Bayat (stale) bir kalan-limit değeri yanlış karar doğururdu. Bu yüzden kalan limit materyalize edilmez — her okuma anında hareketlerin toplamından hesaplanır. Bu, izlenebilirlik kırmızı çizgisinin bir gereğidir.


Hesap açma

POST /api/mahsuplasma/limits/accounts
{
"facility_id": "...",
"year": 2026,
"initial_limit_kwh": 120000.0,
"current_official_limit_kwh": 120000.0 // gönderilmezse initial kabul edilir
}

Bu çağrı atomiktir: hesap satırı + bir başlangıç hareketi tek transaction'da yazılır. Başarısızlık durumları:

  • Aynı tesis + aynı yıl için ikinci hesap → 409 (tekil hesap).
  • facility_id geçersiz/cross-tenant → 422.

initial hareket neden amount=0?

Hesap açıldığında bir initial hareketi eklenir ama amount_kwh = 0'dır. Bunun nedeni double-count (çift sayım) önleme:

  • Başlangıç limiti zaten hesabın initial_limit_kwh kolonundadır ve balances görünümü kalanı initial_limit_kwh + SUM(amount_kwh) ile hesaplar.
  • Eğer initial hareketin amount'ı > 0 olsaydı, başlangıç limiti iki kez sayılırdı (2× initial).

Yani initial hareket bir audit marker'dır (hesabın ne zaman açıldığının izi); gerçek başlangıç limiti hesap satırındadır. Sonraki tüm hareketler (adjust/transfer/tüketim) bu bazdan işaretli delta olarak eklenir.


Hareket defteri (movements)

GET /api/mahsuplasma/limits/accounts/{account_id}/movements

Hareketleri yeni→eski sıralı listeler (salt-okunur). İşaret kuralı: kullanım NEGATİF, artış POZİTİF.

Hareket tipini KULLANICI SEÇMEZ

Aşağıdaki 9 hareket tipi bir açılır liste / form seçeneği değildir. Zeus, her işlem için doğru tipi kendisi yazar: hesap açınca initial, saatlik hesap motoru mahsuplaşan/ihtiyaç fazlası için mahsuplasan_consumption ve bedelli_ihtiyac_fazlasi, manuel düzeltmede manual_adjustment, transferde group_transfer_out + group_transfer_in, geç-veri düzeltmesinde correction_reversal + correction_reapply. Sen yalnızca manuel düzeltme (işaretli miktar + gerekçe) ve transfer (kaynak/hedef + miktar) girersin; tip alanı diye bir şey doldurmazsın. Bu tablo, hareket defterinde hangi satırın neden oluştuğunu okuyabilmen içindir.

Kod içindeki tam liste (bedelli_limit_movements.movement_type — 9 değerli DB CHECK). Her tip için "kim/ne zaman yazar" ve işaret:

movement_typeTR karşılığıİşaretKim / ne zaman yazar
initialAçılış izi0Hesap açılışında (bkz. Hesap açma). Yalnızca "hesap ne zaman açıldı" audit marker'ı; başlangıç limiti hesap satırındadır (bu yüzden amount=0).
mahsuplasan_consumptionMahsuplaşan tüketim düşümüSaatlik hesap motoru (otomatik). Md.7(3): mahsuplaşmaya konu üretimin, tesisin gruba oranınca limitten düşülen kısmı.
bedelli_ihtiyac_fazlasiBedelli ihtiyaç fazlası düşümüSaatlik hesap motoru (otomatik). Limit içinde kalan, bedelli ödenebilecek ihtiyaç fazlasının limitten düşülen kısmı.
manual_adjustmentManuel düzeltme±SenManuel düzeltme formundan (işaretli miktar + zorunlu gerekçe). LÜM/mutabakat farkını elle işlemek için.
lum_adjustmentLÜM revizyon senkronu±Ayrılmış (henüz otomatik yazılmıyor). İleride LÜM (Lisanssız Üretim Modülü) resmî limit revizyonunu otomatik aynalamak için tanımlı; bugün limit revizyonu manual_adjustment ile elle girilir.
group_transfer_outTransfer — kaynaktan çıkışSenGruplar-arası transfer (kaynak hesaptan çıkış).
group_transfer_inTransfer — hedefe giriş+Sen — transferde hedef hesaba giriş (çift hareketin ikinci ayağı).
correction_reversalDüzeltme — geri alma+ veya −Düzeltme (correction cascade) — otomatik. Geç/düzeltilmiş veri gelince eski hesap koşusunun (run) etkisini nötrler; hesap × eski-koşu başına tek agregat satır (Md.15).
correction_reapplyDüzeltme — yeniden uygulama±Düzeltme (correction cascade) — otomatik. Reversal sonrası yeni (düzeltilmiş) toplamı yeniden yazar.
Kalan limit nasıl hesaplanır?

kalan = initial_limit_kwh + tüm hareketlerin amount_kwh toplamı

initial hareketin amount'ı 0 olduğundan, hareketsiz bir hesabın kalanı başlangıç limitine eşittir. Kullanım hareketleri negatif yazıldığı için her mahsuplaşan tüketim / ihtiyaç fazlası düşümü kalanı azaltır; artış/geri-ekleme (+) kalanı büyütür. Kalan limit bu sürümde ayrı bir GET endpoint'i olarak router'a bağlanmamıştır; frontend/konsol kalanı hesap + hareketlerden türetir. Transfer akışı kalanı doğrudan bedelli_limit_balances görünümünden okur.

Hareketler nereden gelir? (akış)

Kısaca: initial ve düzeltme (correction) satırlarını Zeus otomatik yazar; saatlik hesap motoru mahsuplaşan tüketim + ihtiyaç fazlasını otomatik yazar; yalnız manuel düzeltme ve transfer senin girdiğin işlemlerdir.


Manuel düzeltme (adjust)

Bir hesaba manuel limit düzeltmesi eklemek için:

POST /api/mahsuplasma/limits/accounts/{account_id}/adjust
{
"amount_kwh": -5000.0, // negatif = düşür, pozitif = artır (0 reddedilir)
"reason": "LÜM düzeltmesi — Haziran mutabakatı", // ZORUNLU (denetim izi)
"billing_period": "2026-06-01" // gönderilmezse TR-yerel ay-başı
}

Alanların anlamı ve nereden geleceği:

AlanZorunluNe yazacağım / nereden bulurumBoş / yanlış olursa
amount_kwh (miktar)İşaretli kWh: negatif = limiti düşür, pozitif = limiti artır. Değeri LÜM raporu / mutabakat farkından alırsın (ör. "−5000" = 5000 kWh düşüş).Sıfır reddedilir (422). İşareti ters verirsen limit yanlış yönde değişir — düzeltmek için yeni bir ters hareket girersin (eski satır silinmez).
reason (gerekçe)Serbest metin — niçin düzelttiğinin açıklaması (ör. "LÜM düzeltmesi — Haziran mutabakatı").Boşsa istek reddedilir. Md.15 denetim izi gereği zorunlu.
billing_period (dönem)Düzeltmenin ait olduğu fatura dönemi başı (YYYY-AY-01). Fatura döneminden alınır.Boş bırakırsan TR yerel ay-başı otomatik yazılır.
  • amount_kwh işaretlidir ve sıfır reddedilir.
  • reason zorunludur (Md.15 denetim izi).
  • Mevcut hesap satırı veya önceki hareketler değiştirilmez — yeni bir manual_adjustment hareketi eklenir (salt-append).
LÜM revizyonu bugün elle girilir

Resmî limit LÜM'de değişirse, bu değişimi bugün bir manual_adjustment ile (işaretli fark + gerekçe) işlersin. lum_adjustment tipi ileride bu senkronu otomatikleştirmek için ayrılmıştır; bu sürümde otomatik yazan servis yoktur.


Gruplar-arası transfer (BETA)

Bir tüketim tesisi başka bir gruba geçtiğinde, kalan bedelli limiti yeni gruba transfer edilir (Md.5(4)):

POST /api/mahsuplasma/limits/transfer
{
"source_account_id": "...",
"target_account_id": "...",
"amount_kwh": 10000.0,
"reason": "Grup değişikliği — ...",
"contracted_power_confirmed": false
}

Alanların anlamı ve nereden geleceği:

AlanZorunluNe yazacağım / nereden bulurumBoş / yanlış olursa
source_account_id (kaynak hesap)Limitin çıkacağı (tesis × yıl) hesabının kimliği (id). Bu id'yi hesap listesinden alırsın: GET /api/mahsuplasma/limits/accounts her hesabın id, facility_id ve year alanını döndürür (facility_id/year ile daraltabilirsin).Geçersiz veya başka tenant'ın hesabı → 422.
target_account_id (hedef hesap)Kalan limitin aktarılacağı (yeni gruptaki tesis × yıl) hesabının id'si — aynı GET .../limits/accounts listesinden.Geçersiz/cross-tenant → 422. Kaynak == hedef → 422 (self-transfer).
amount_kwh (miktar)Transfer edilecek kalan limit — pozitif kWh (yön kaynak→hedef sabittir; işaret girmezsin). Değeri LÜM raporu / mutabakat kararından alırsın.Sıfır veya negatif reddedilir (422). Kaynak kalan limit miktardan azsa → 422 (INSUFFICIENT_LIMIT_BALANCE).
reason (gerekçe)Serbest metin — transferin niçin yapıldığı (ör. "Grup değişikliği — ...").Boşsa reddedilir. Md.15 denetim izi gereği zorunlu.
contracted_power_confirmedMd.5(4) sözleşme-gücü ön koşulunun beyanıdır (true/false). Bu fazda doğrulanmaz; false gönderirsen yanıt bir warnings listesi içerir (aşağıdaki uyarıya bakın).Varsayılan false.
billing_period (dönem)Transferin ait olduğu fatura dönemi başı (YYYY-AY-01).Boş bırakırsan TR yerel ay-başı otomatik yazılır.

Transfer, kaynakta group_transfer_out (negatif) + hedefte group_transfer_in (pozitif) hareket çiftini tek transaction'da yazar (yarım transfer yasak).

Eşzamanlılık güvencesi: Kaynak ve hedef hesap satırları SELECT ... FOR UPDATE ile (id-sıralı; deadlock önleme) kilitlenir. Aynı kaynaktan eşzamanlı iki transfer serileşir — ikinci transfer, birincinin düşümünü içeren güncel kalanı görür. Böylece over-transfer/negatif-kalan yarışı kapanır.

Başarısızlık: kaynak kalan limit transfer miktarından azsa → 422 (INSUFFICIENT_LIMIT_BALANCE). Kaynak == hedef → 422.

Transfer BETA — Md.5(4)/28/7 tam kalibrasyon henüz yok

Transfer iskelet/beta'dır. contracted_power_confirmed alanı Md.5(4) sözleşme-gücü ön koşulunun bir beyanıdır ve bu sürümde doğrulanmaz; false gönderilirse yanıt bir warnings listesi içerir. Sözleşme-gücü doğrulaması + Yönetmelik 28/7 oran-bazı tam kalibrasyonu ileri bir sürüme bırakılmıştır (mevcut sürüm yalnız kalan-limit yeterliliğini ve çift hareketin atomikliğini garanti eder). Bu tarihe kadar transferi manuel gözetim altında kullanın.


Md.7(3) — mahsuplaşan tüketim de limitten düşer

Önemli bir mevzuat noktası: mahsuplaşmaya ve satışa konu edilen üretim, her tüketim tesisinin limitinin gruba ait toplam limite oranı gözetilerek limitten düşülür. Yani sadece ihtiyaç fazlası değil, mahsuplaşan tüketim de limitten düşer. Ayrıca Md.7(5) gereği limit bittikten sonra üretim-tüketim mahsuplaşmaya devam eder; ancak ihtiyaç fazlası artık SKB ödemeli olur.

Otomatik limit düşümü CANLI

Saatlik mahsuplaşma sonucu limitten düşüm artık elle değil, otomatik yazılır. Saatlik hesap motoru çalıştığında, Md.7(3) oran-bazlı dağıtımı yapar ve her tüketim/karma tesis hesabına iki tip hareketi fiilen ekler:

  • mahsuplasan_consumption () — mahsuplaşan tüketimin limitten düşen payı,
  • bedelli_ihtiyac_fazlasi () — bedelli ihtiyaç fazlasının limitten düşen payı.

Paylar, grubun oran tabanına göre dağıtılır: varsayılan remaining (hesabın kalan limiti / grubun toplam kalan limiti), alternatif initial (başlangıç limiti oranı — LÜM kalibrasyonu). Yuvarlama tek noktada (kWh, 3 ondalık) yapılır; payların toplamı yuvarlanmış kullanıma birebir eşitlenir, böylece kalan-limit kayması (drift) olmaz.

Bu hareketleri sen tetiklemezsin — arka planda zamanlanmış görevler yürütür:

Görev (Celery beat)Ne zamanNe yapar
mahsuplasma.calc_prev_hour_preliminaryHer saat :15Bir önceki TR saatinin ön (preliminary) hesabı → hareketleri yazar
mahsuplasma.calc_prev_hour_correctedHer saat :45Aynı saatin geç-veri düzeltmesi; girdi değişmediyse NO-OP
mahsuplasma.calc_daily_backfillHer gün 03:10Son 7 günü yeniden hesaplar; değişen dönemler correction zinciriyle yenilenir

Geç/düzeltilmiş veri geldiğinde eski koşunun (run) etkisi correction_reversal ile nötrlenir, yeni toplam correction_reapply ile yeniden yazılır (salt-append; hiçbir eski satır silinmez, Md.15). Bu ledger hareketleri manuel düzeltme ve transfer ile aynı defterde birikir; kalan limit her okuma anında hepsinin toplamından türetilir.


İlgili sayfalar