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_balancesgörünümü).
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_idgeç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_kwhkolonundadır vebalancesgörünümü kalanıinitial_limit_kwh + SUM(amount_kwh)ile hesaplar. - Eğer
initialhareketin 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.
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_type | TR karşılığı | İşaret | Kim / ne zaman yazar |
|---|---|---|---|
initial | Açılış izi | 0 | Hesap 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_consumption | Mahsuplaş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_fazlasi | Bedelli ihtiyaç fazlası düşümü | − | Saatlik hesap motoru (otomatik). Limit içinde kalan, bedelli ödenebilecek ihtiyaç fazlasının limitten düşülen kısmı. |
manual_adjustment | Manuel düzeltme | ± | Sen — Manuel düzeltme formundan (işaretli miktar + zorunlu gerekçe). LÜM/mutabakat farkını elle işlemek için. |
lum_adjustment | LÜ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_out | Transfer — kaynaktan çıkış | − | Sen — Gruplar-arası transfer (kaynak hesaptan çıkış). |
group_transfer_in | Transfer — hedefe giriş | + | Sen — transferde hedef hesaba giriş (çift hareketin ikinci ayağı). |
correction_reversal | Dü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_reapply | Düzeltme — yeniden uygulama | ± | Düzeltme (correction cascade) — otomatik. Reversal sonrası yeni (düzeltilmiş) toplamı yeniden yazar. |
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:
| Alan | Zorunlu | Ne yazacağım / nereden bulurum | Boş / 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_kwhişaretlidir ve sıfır reddedilir.reasonzorunludur (Md.15 denetim izi).- Mevcut hesap satırı veya önceki hareketler değiştirilmez — yeni bir
manual_adjustmenthareketi eklenir (salt-append).
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:
| Alan | Zorunlu | Ne yazacağım / nereden bulurum | Boş / 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_confirmed | ❌ | Md.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 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.
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 zaman | Ne yapar |
|---|---|---|
mahsuplasma.calc_prev_hour_preliminary | Her saat :15 | Bir önceki TR saatinin ön (preliminary) hesabı → hareketleri yazar |
mahsuplasma.calc_prev_hour_corrected | Her saat :45 | Aynı saatin geç-veri düzeltmesi; girdi değişmediyse NO-OP |
mahsuplasma.calc_daily_backfill | Her gün 03:10 | Son 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
- Uygunluk Kuralları — R007 — limit hesabı zorunluluğu.
- Kurulum Akışı — Adım 6 — hesap açma.
- SSS ve Sorun Giderme —
INSUFFICIENT_LIMIT_BALANCEvb. - Teknik: Veri Modeli — bedelli_limit_balances VIEW
- Operasyonel: Migration runbook
0095(ledger tabloları +bedelli_limit_balancesVIEW) /0098(hesap motoru + otomatik hareketler).