# iklim.co Python sandbox istemcileri Bu klasör HMAC imzalı istek, otomatik login/refresh ve `.env` token saklama özelliklerini ortak bir istemci katmanında toplar. Geo alarm registration istemcisi bu ortak yapıyı kullanarak noktalı virgüllü CSV satırlarını sırayla işler. ## Dosyalar - `common/`: tekrar kullanılabilir API, HMAC, retry ve authentication katmanı - `login.py`: manuel login komutu - `nowcast-geo-register.py`: CSV geo alarm registration istemcisi - `nowcast-point-page.py`, `nowcast-point-get.py`, `nowcast-point-register.py`, `nowcast-point-update.py`, `nowcast-point-unregister.py`: nokta alarm (`/v1/alarms/points`) istemcileri - `nowcast-point-alarm-filter.example.json`, `nowcast-point-registrations.example.csv`, `nowcast-point-update.example.csv`: nokta alarm şablonları - `.env.example`: güvenli ortam değişkeni şablonu - `webhook.example.json`: dört webhook authentication seçeneğini içeren şablon - `nowcast-geo-alarm-filter.example.json`: ortak nowcast geo alarm filtreleri şablonu - `nowcast-geo-alarm-registrations.example.csv`: CSV şablonu Gerçek değerler için `.env`, `webhook.json` ve `nowcast-geo-alarm-filter.json` kullanılır. `.env` ile `webhook.json` kaynak kod deposuna eklenmemelidir. ## Kurulum ```bash cd iklim-sandbox python3 -m venv .venv source .venv/bin/activate python -m pip install -r requirements.txt ``` Yeni kurulumda şablonları kopyalayın ve gerçek değerlerle düzenleyin: ```bash cp .env.example .env cp webhook.example.json webhook.json cp nowcast-geo-alarm-filter.example.json nowcast-geo-alarm-filter.json cp nowcast-geo-alarm-registrations.example.csv nowcast-geo-alarm-registrations.csv ``` ## CSV biçimi Girdi UTF-8 ve noktalı virgül ayracına sahip olmalıdır: ```csv city;district;recipientId Ankara;Çankaya;recipient-001 İstanbul;Kadıköy;recipient-002 ``` Script aynı dosyaya şu üç sonuç sütununu ekler veya mevcut değerlerini günceller: ```csv city;district;recipientId;status;httpStatus;registrationId Ankara;Çankaya;recipient-001;SUCCESS;HTTP_200;f7587d9e-2481-4b4c-818d-c8d1946851b7 ``` - İl ve ilçe adları trim edilip Türkçe büyük/küçük harf kurallarıyla kesin eşleştirilir. - Bulunamayan konumlar `FAILED;NOT_SENT;` olarak kaydedilir. - Daha önce `SUCCESS` olmuş satırlar konsola bilgi yazılarak atlanır. - Her satırdan sonra CSV aynı dizinde geçici dosyayla atomik olarak güncellenir. - `recipientId` boş olamaz ve US-ASCII karakterlerinden oluşmalıdır. ## İl ve ilçe sorgu cache'i Şehir listesi her script çalıştırmasında yalnızca bir kez API'den alınır ve isim-ID eşleştirmesi bellekte tutulur. İlçe listesi de her benzersiz şehir için yalnızca ilk karşılaşmada alınır; aynı şehirdeki sonraki CSV satırlarında bellekteki sonuç kullanılır. Cache diske yazılmaz ve script kapandığında temizlenir. ## Webhook yapılandırması `webhook.json` içindeki `authentication.selected` şu değerlerden biri olmalıdır: - `BASIC` - `JWT_TOKEN` - `API_KEY` - `HMAC_SIGNATURE` Yalnızca seçilen seçeneğin `options` altındaki alanları API'ye gönderilir. İlk initialization isteğinde tam webhook config gönderilir ve `accountId` kesinlikle gönderilmez. İlk başarılı kayıttan sonra `.env` içindeki `IKLIM_WEBHOOK_INITIALIZED=true` yapılır. Sonraki kayıtlar webhook altında yalnızca API'nin `/v1/users/me` yanıtındaki `account.id` değerini gönderir. Webhook'u yeniden tanımlamak için bu değeri elle `false` yapın. Test/prod ortamını veya kullanıcı hesabını değiştirirken de yeni ortamın webhook'u ilk kayıtla tanımlayabilmesi için `IKLIM_WEBHOOK_INITIALIZED=false` yapın. ## Nowcast geo alarm filter yapılandırması Tüm CSV satırlarına uygulanacak ortak nowcast filtreleri `nowcast-geo-alarm-filter.json` içinde tutulur. Yıldırım, thunderstorm ve yağış filtrelerinden ihtiyaç duyulmayan nesneler dosyadan çıkarılabilir. API isteğindeki alan adı doküman gereği yine `filter` olarak gönderilir. ## Otomatik authentication Ortak istemci her yetkili istekten önce access token'ı kontrol eder: 1. Access token yoksa `.env` kullanıcı bilgileriyle login olur. 2. Access token'ın bitmesine 60 saniyeden az kaldıysa geçerli refresh token ile yeniler. 3. Refresh token yoksa/geçersizse yeniden login olur. 4. Sunucu `401` döndürürse refresh veya login yapıp isteği bir kez daha gönderir. 5. Yeni tokenları ve JWT `exp` değerlerini `.env` dosyasına kaydeder. Yetkili isteklerde `Authorization: Bearer ` ile HMAC başlıkları birlikte gönderilir. Tokenlar ve parolalar konsola yazılmaz. ## Retry davranışı Ağ hataları ile `429`, `500`, `502`, `503` ve `504` yanıtları ilk istekten sonra en fazla üç kez, varsayılan olarak 2, 4 ve 8 saniye beklenerek tekrar edilir. `429` yanıtındaki `Retry-After` varsa ona uyulur. Bir operasyonun retry isteklerinde aynı idempotency key; yeni timestamp, nonce ve HMAC imzası kullanılır. Belirsiz sonuç veya `409` sonrasında kayıt `recipientId` ile sorgulanır. Kayıt bulunursa `registrationId` kurtarılır ve satır `SUCCESS` yapılır. ## Debug HTTP logları Her çalıştırmada maskelenmiş HTTP istek ve yanıtları aşağıdaki dosyaya yazılır: ```text logs/iklim-api-debug.log ``` Log; istek metodu, URL, deneme numarası, başlıklar, istek gövdesi, HTTP durum kodu, yanıt başlıkları ve yanıt gövdesini içerir. Parola, kullanıcı adı, JWT, refresh token, API key, webhook secret, `Authorization`, cookie ve `X-Signature` değerleri maskelenir. Dosya 5 MB'a ulaştığında döndürülür ve en fazla üç eski dosya tutulur. Başarılı API yanıtındaki `warning`/`warnings` alanları ayrıca konsola yazılır. ## Çalıştırma Manuel login: ```bash python login.py ``` CSV registration: ```bash python nowcast-geo-register.py nowcast-geo-alarm-registrations.csv ``` Farklı config dosyaları vermek için: ```bash python nowcast-geo-register.py nowcast-geo-alarm-registrations.csv \ --webhook-config another-webhook.json \ --nowcast-filter-config another-nowcast-geo-alarm-filter.json \ --debug-log logs/custom-debug.log ``` ## Nokta alarm (nowcast point) istemcileri Nokta alarm istemcileri `BE-NowcastPointAlarm` servisinin `/v1/alarms/points` endpoint'lerini çağırır. Tüm komutlarda `--help` seçeneklerin tam dökümünü verir. Gerçek filtre değerleri için `nowcast-point-alarm-filter.example.json` dosyasını `nowcast-point-alarm-filter.json` olarak kopyalayın (git'e eklenmez). Webhook için mevcut `webhook.json` kullanılır, ancak geo alarmdan farklı olarak her register isteğinde `webhook.json` içindeki tam webhook (`deliveryPolicy` dahil) gönderilir; `IKLIM_WEBHOOK_INITIALIZED` ve `/v1/users/me` kullanılmaz. Backend nokta alarm kaydında webhook ve `deliveryPolicy` zorunlu tutar, hesabı kullanıcıdan kendisi bulur ve hesapta kayıtlı webhook varsa onu yeniden kullanır. | Komut | Ne yapar | | --- | --- | | `nowcast-point-page.py` | Kayıtları sayfa sayfa listeler; SQLite'a (`iklim-sandbox.db`, `registrationId` anahtarlı) yazar, ekrana basar veya `--output` ile CSV/TXT üretir | | `nowcast-point-get.py` | CSV/TXT içindeki `registrationId` (`--by id`) veya `recipientId` (`--by recipient`) değerlerini sorgular | | `nowcast-point-register.py` | CSV satırlarından kayıt oluşturur | | `nowcast-point-update.py` | CSV satırlarıyla kayıtları kısmen günceller (`--dry-run` destekler) | | `nowcast-point-unregister.py` | Listedeki kayıtları siler (onay ve yedekle) | ### Önemli uyarılar - **Page endpoint'inde sıralama (ORDER BY) yoktur.** Tüm sayfalar gezilirken kayıtların sırası garanti değildir; bu yüzden sayfa kayması yaşanabilir (bir kayıt iki kez gelebilir, bir kayıt hiç gelmeyebilir). - **Tüm kayıtlar çekilirken aynı anda register, update veya unregister yapmayın.** Veri değişirse sayfalar kayar. `nowcast-point-page.py` sonunda sunucunun bildirdiği `totalRecords` ile alınan benzersiz kayıt sayısını karşılaştırır ve fark varsa uyarı verir; bu durumda komutu yeniden çalıştırın. SQLite `registrationId` ile upsert yaptığı için tekrar çalıştırmak kayıt çoğaltmaz. - `nowcast-point-page.py` varsayılan olarak `accountId` göndermez; sunucu listeyi çağıranın kendi hesabına daraltır ve normal kullanıcılar için doğru kullanım budur. `--account-id` yalnızca en az POWER_USER yetkisiyle verilebilir; aksi halde HTTP 403 döner ve betik `--account-id` vermemenizi söyleyen bir hata yazar. ADMIN kullanıcı `accountId` vermezse tüm hesapları görür. - Backend her `RuntimeException`'ı HTTP 400 yapar; kayıt bulunamaması da 400 döner. Yalnızca gerçek 404 `NOT_FOUND` olarak işaretlenir. ### page ```bash python3 nowcast-point-page.py # tüm sayfalar, ekrana python3 nowcast-point-page.py --output point-alarm-registrations.csv # point-alarm-registrations-YYYYmmdd-HHMMSS.csv python3 nowcast-point-page.py --page-number 2 --page-size 50 # yalnızca tek sayfa python3 nowcast-point-page.py --account-id 2f04f1b5-1c0a-4c4e-b0a7-0ba6e9b7f2e1 # yalnızca POWER_USER ve üstü python3 nowcast-point-page.py --recipient-id musteri-1 --recipient-id musteri-2 python3 nowcast-point-page.py --polygon "32.8,39.9;32.9,39.9;32.9,40.0;32.8,39.9" ``` - Poligon `lng,lat` çiftlerinden oluşan **düz halkadır**, en az 4 nokta ve kapalı olmalıdır. Filtre, kayıt noktasının poligonla kesişimine bakar; yarıçap yok sayılır. - Çıktıda webhook bilgisi dosya başında bir kez yer alır (CSV'de `#webhook` satırı); `password`, `jwtToken`, `apiKey` ve `secret` alanları `***` ile maskelenir. Her filtre ayrı bir sütundur. API yanıtında `createdAt`/`updatedAt` bulunmadığı için çıktıda da yoktur. - Etkileşimli terminalde ve `--output` verilmediğinde sonunda "SQLite verisi CSV/TXT dosyasına aktarılsın mı?" sorulur. ### get ```bash python3 nowcast-point-get.py idler.txt --output sonuc.csv # registrationId listesi (TXT), CSV çıktı python3 nowcast-point-get.py idler.csv --output sonuc.txt # registrationId sütunlu CSV, JSON Lines çıktı python3 nowcast-point-get.py alicilar.txt --by recipient --output sonuc.csv # recipientId ile sorgu ``` TXT girdi örneği (`idler.txt`): ```text # bu satır atlanır f7587d9e-2481-4b4c-818d-c8d1946851b7 57d1e2ba-bf2d-4ef3-b078-523060b2000c ``` Girdi CSV ise başlıkta `registrationId` (veya `recipientId`) sütunu aranır; TXT ise satır başına bir ID yazılır, boş satırlar ve `#` ile başlayanlar atlanır. `--output` zorunludur; uzantı `.csv` ise noktalı virgüllü CSV, `.txt` ise JSON Lines yazılır. Satır durumları `SUCCESS`, `NOT_FOUND` (HTTP 404) ve `FAILED`'dır (`httpStatus` ve `errorBody` dolar). Bir `recipientId` birden çok kayıt dönerse her kayıt ayrı satırdır. ### register ```bash cp nowcast-point-alarm-filter.example.json nowcast-point-alarm-filter.json # filtreleri düzenleyin python3 nowcast-point-register.py nowcast-point-registrations.example.csv python3 nowcast-point-register.py kayitlar.csv --filter-config baska-filtre.json --webhook-config baska-webhook.json ``` Girdi CSV örneği (sonuç sütunları işlem sonunda aynı dosyaya eklenir): ```csv recipientId;lat;lng;radius musteri-001;39.9208;32.8541;1000 musteri-002;41.0082;28.9784;5000 ``` CSV sütunları yalnızca `recipientId;lat;lng;radius` değerleridir (`radius` metre, 0-50000). Filtre CSV'de tekrarlanmaz; ortak `nowcast-point-alarm-filter.json` dosyasından okunur ve her kayda uygulanır (`--filter-config` ile değiştirilir). Sonuç sütunları aynı dosyaya yazılır: `status;httpStatus;registrationId;errorBody`. HTTP 200 dışındaki yanıtlarda cevap gövdesi `errorBody` içine yazılır. `SUCCESS` satırlar yeniden çalıştırmada atlanır. Sonucu belirsiz kalan istek yalnızca `recipientId`, nokta ve yarıçap birebir eşleşirse mevcut kayıtla kurtarılır. Aynı hesap, `recipientId`, nokta ve yarıçap ikinci kez kaydedilemez (400). ### update ```bash python3 nowcast-point-update.py guncelle.csv --dry-run # gövdeleri gösterir, istek göndermez python3 nowcast-point-update.py guncelle.csv # gerçekten günceller ``` Girdi CSV örneği (boş hücre gönderilmez): ```csv registrationId;recipientId;lat;lng;radius;lightningType;peakCurrent;inCloudHeight;intersectsAffectedPolygon;intersectsCellPolygon;severityThreshold;speedThreshold;precipitationIntensities f7587d9e-2481-4b4c-818d-c8d1946851b7;;;;2500;;;;;;HIGH;;LIGHT|MODERATE 57d1e2ba-bf2d-4ef3-b078-523060b2000c;;40.0;33.0;;PULSE_IN_CLOUD;;;;true;;; ``` CSV'de `registrationId` zorunludur; `recipientId;lat;lng;radius` ve filtre sütunları (`lightningType;peakCurrent;inCloudHeight;intersectsAffectedPolygon;intersectsCellPolygon;severityThreshold;speedThreshold;precipitationIntensities`, liste `|` ile ayrılır) isteğe bağlıdır. **Boş hücre gönderilmez**; `lat` ve `lng` birlikte verilmelidir. Önce `--dry-run` ile gövdeleri kontrol edin. - Backend PATCH'i bir alanı **temizleyemez** (null = dokunma). - `thunderstorm.speedThreshold` backend PATCH'inde işlenmiyor, değer gönderilse de güncellenmeyebilir (GAIA-476). - Backend sınır güncellemesinde doğrulama yapmaz; yarıçap ve koordinat aralıkları istemcide yerelde doğrulanır. - Webhook güncellemesi bu sürümde desteklenmez. ### unregister ```bash python3 nowcast-point-unregister.py silinecekler.txt # yedek alır, SİL onayı ister python3 nowcast-point-unregister.py silinecekler.csv --output sonuc.csv # sonuçları dosyaya da yazar python3 nowcast-point-unregister.py silinecekler.txt --backup yedek/point-yedek.csv # yedek yolu python3 nowcast-point-unregister.py silinecekler.txt --no-backup # yedeksiz (önerilmez) ``` Girdi `registrationId` içeren bir CSV veya satır başına bir ID içeren TXT olabilir. Başlamadan önce ortam adı ve URL, silinecek kayıt sayısı ve "geri alınamaz" uyarısı gösterilir; bir hesabın **son kaydı** silinirse webhook tanımı da silinir. Onay için `SİL` yazılmalıdır (`--yes` yoktur). `prod` ortamında ek olarak ortam adı (`prod`) yazdırılır. Silmeden önce her kayıt `get-by-id` ile yedeklenir (`--backup`, kapatmak için `--no-backup`); yedek yazılamazsa hiçbir kayıt silinmez.