POT-GPD StudioPeaks-Over-Threshold Flood Frequency Analysis
POT-GPD Studio
Günlük veya saatlik akım serilerinizden bağımsız taşkın olaylarını çıkarın, eşik seçimini inceleyin, GPD modellerini karşılaştırın ve tekerrür debilerini belirsizlikleriyle birlikte hesaplayın.
Turuncu eşik çizgisini sürükleyin — eşiği aşan bağımsız olaylar anında yeniden sayılır.
Platformun zinciri
Amaç yalnızca bir Q100 sayısı üretmek değil; bu sayının eşik seçimine, olay ayırmaya ve örnekleme belirsizliğine ne kadar duyarlı olduğunu göstermektir.
Threshold × declustering matrisi
Q100'ün eşik seçiminden mi yoksa olay ayırma aralığından mı daha çok etkilendiğini tek bakışta gösterir.
Dört POT-GPD yaklaşımı
Uzman seçimi, diagnostik stabilite, uyum iyiliği destekli seçim ve eşik topluluğu aynı veri üzerinde karşılaştırılır.
İki belirsizlik kaynağı birlikte
Bootstrap örnekleme belirsizliği ile eşik kaynaklı yayılım ayrı ayrı raporlanır.
Data Studio
Excel/CSV yükleyin, manuel girin veya sentetik demo istasyonlarla başlayın.
Dosya yükle
⇪
Dosyayı buraya bırakın veya seçmek için tıklayın
.xlsx · .xls · .csv · .txt
Kolon eşleştirme
Zorunlu alanlar: station_id, datetime, discharge. Diğer kolonlar opsiyoneldir.
Manuel giriş
Her satıra bir gözlem: tarih ve debi. Ayırıcı olarak virgül, noktalı virgül, sekme veya boşluk kullanabilirsiniz.
SENTETİK GÖSTERİM VERİSİ — GÖZLEM DEĞİLDİR.Demo istasyonlar yalnızca arayüzü öğretmek için üretilir ve gerçek ölçüm olarak kullanılamaz.
Yüklenen istasyonlar
Veri kalite kontrolü
POT analizinden önce kayıt uzunluğu, boşluklar ve şüpheli değerler incelenir.
POT olay gezgini
Eşik aşımları ile bağımsızlaştırılmış taşkın olayları aynı eksende gösterilir.
Gri: tüm seri · turuncu: eşik · soluk kırmızı: ham aşımlar · dolu kırmızı: bağımsız POT olayları (küme maksimumları).
Küme ayrıntısı
Taşkın mevsimselliği
Olayların ay dağılımı rejim hakkında ipucu verir; sistem bunu otomatik olarak "kar taşkını" gibi etiketlemez.
Threshold Lab
Ortalama artık ömür, parametre stabilitesi ve eşik duyarlılığı birlikte incelenir.
Ortalama aşım (mean residual life)
GPD varsayımı geçerliyse yeterince yüksek eşiklerde eğri yaklaşık doğrusallaşır. En doğrusal nokta tek başına "doğru eşik" değildir; bant %95 güven aralığıdır.
Shape parametresi ξ(u)
Düzeltilmiş ölçek σ* = σ − ξu
Eşik duyarlılığı: QT(u)
Sitenin temel sorusu: eşik değişirse tekerrür debisi ne kadar değişiyor?
Eşik tarama tablosu
GPD Model Lab
Genelleştirilmiş Pareto dağılımının parametreleri, uyum iyiliği ve tanı grafikleri.
Q-Q
P-P
Aşım histogramı ve uyumlanan yoğunluk
Ampirik ve modellenen CDF
Uyum iyiliği
Anderson-Darling ve Cramér-von Mises istatistikleri parametreler tahmin edildiği için parametrik bootstrap ile değerlendirilir.
Dört POT-GPD yaklaşımı
Aynı seri üzerinde dört farklı eşik belirleme stratejisi.
Tekerrür debileri ve risk
Bootstrap güven aralıkları, tekerrür seviyesi grafiği ve tasarım ömrü riski.
Tekerrür debisi tablosu
Tekerrür seviyesi grafiği
Bootstrap dağılımı
Tasarım ömrü riski
Q100 gerçekte ne anlatır?
Threshold × declustering duyarlılık matrisi
Çoklu istasyon çalışması
Tüm istasyonlar aynı kurallarla işlenir ve karşılaştırılır.
İstasyon konumları
Koordinat bilgisi olan istasyonlar gösterilir; nokta büyüklüğü tekerrür debisiyle orantılıdır.
İstasyonlar arası karşılaştırma
Trend ve durağansızlık
Aşım oranı ve GPD ölçek parametresi zamanla değişiyor mu? Değişiyorsa tekerrür süresinin anlamı ne olur?
Durağanlık, buraya kadar olan tüm modüllerin örtük varsayımıdır. Bu modül o varsayımı sınar. Trend bulunursa tek bir "100 yıllık debi" artık kaydın tamamını tanımlamaz; her yıl için ayrı bir etkin değer söz konusudur.
Analiz, Threshold Lab'de seçili eşik ve ayrıştırma aralığıyla elde edilen bağımsız olaylar üzerinde yapılır. Eşiği değiştirirseniz sonuç da değişir.
Yıllık olay sayısı ve oran modeli
Noktalar gözlenen yıllık bağımsız olay sayısıdır; eğri λ(t) = exp(a + b·t) Poisson uyumudur. Kısmi veri içeren yıllar kapsama oranıyla ölçeklenir.
Olay büyüklükleri ve ölçek modeli
Eğriler σ(t) = exp(β₀ + β₁·t) uyumundan gelen %50 ve %90 model kantilleridir; ξ sabit tutulur.
Etkin tekerrür debisi
Her yıl için, o yıl içinde aşılma olasılığı 1/T olan debi. Kesikli çizgi durağan modelin tek değeridir.
Tasarım ömrü boyunca kümülatif risk
Seçilen pencerede bir debinin en az bir kez aşılma olasılığı; durağan ve durağan olmayan model karşılaştırılır.
İstatistiksel testler
Mann-Kendall ve olabilirlik oranı testleri bağımsız olay varsayımına dayanır; ayrıştırma aralığı yetersizse anlamlılık olduğundan büyük görünür. Anlamlı bir p değeri trendin nedenini açıklamaz — arazi kullanımı değişimi, ölçüm yöntemi değişikliği ve iklim sinyali aynı istatistiği üretebilir.
İkiye bölme kontrolü
Rapor ve dışa aktarım
Tekrarlanabilir ayar dosyası, veri çıktıları ve bilimsel rapor.
İndirilebilir dosyalar
İndirme isteğinde tarayıcı onay ister; dosya yalnızca siz kabul ettiğinizde kaydedilir.
Ayarlar (tekrarlanabilirlik)
Bilimsel rapor
Yöntem
"use strict";
/* ============================================================
POT-GPD Studio — bilingual dictionary
============================================================ */
const I18N = {
tr:{
"brand.sub":"Peaks-Over-Threshold taşkın frekans analizi",
"nav.method":"Yöntem","nav.template":"Excel şablonu","nav.new":"Yeni analiz",
"rail.pipeline":"Analiz zinciri","rail.settings":"Ayarlar",
"mod.data":"Data Studio","mod.qc":"Veri kalitesi","mod.pot":"POT olay gezgini","mod.thr":"Threshold Lab",
"mod.gpd":"GPD Model Lab","mod.cmp":"Dört yöntem","mod.risk":"Tekerrür ve risk","mod.multi":"Çoklu istasyon","mod.report":"Rapor ve dışa aktarım",
"mod.trend":"Trend ve durağansızlık",
"ns.sub":"Aşım oranı ve GPD ölçek parametresi zamanla değişiyor mu? Değişiyorsa tekerrür süresinin anlamı ne olur?",
"ns.warnTop":"Durağanlık, buraya kadar olan tüm modüllerin örtük varsayımıdır. Bu modül o varsayımı sınar. Trend bulunursa tek bir “100 yıllık debi” artık kaydın tamamını tanımlamaz; her yıl için ayrı bir etkin değer söz konusudur.",
"ns.T":"Tekerrür süresi T (yıl)","ns.y0":"Tasarım penceresi başlangıcı","ns.N":"Tasarım ömrü N (yıl)","ns.risk":"Hedef kümülatif risk (%)",
"ns.run":"Trend analizini çalıştır",
"ns.ctrlNote":"Analiz, seçili eşik ve ayrıştırma aralığıyla elde edilen bağımsız olaylar üzerinde yapılır. Eşiği değiştirirseniz sonuç da değişir.",
"ns.plotRate":"Yıllık olay sayısı ve oran modeli",
"ns.plotRateNote":"Noktalar gözlenen yıllık bağımsız olay sayısıdır; eğri λ(t) = exp(a + b·t) Poisson uyumudur. Kısmi veri içeren yıllar kapsama oranıyla ölçeklenir.",
"ns.plotMag":"Olay büyüklükleri ve ölçek modeli",
"ns.plotMagNote":"Eğriler σ(t) = exp(β₀ + β₁·t) uyumundan gelen %50 ve %90 model kantilleridir; ξ sabit tutulur.",
"ns.plotEff":"Etkin tekerrür debisi",
"ns.plotEffNote":"Her yıl için, o yıl içinde aşılma olasılığı 1/T olan debi. Kesikli çizgi durağan modelin tek değeridir.",
"ns.plotRisk":"Tasarım ömrü boyunca kümülatif risk",
"ns.plotRiskNote":"Seçilen pencerede bir debinin en az bir kez aşılma olasılığı; durağan ve durağan olmayan model karşılaştırılır.",
"ns.tests":"İstatistiksel testler",
"ns.testNote":"Mann-Kendall ve olabilirlik oranı testleri bağımsız olay varsayımına dayanır; ayrıştırma aralığı yetersizse anlamlılık olduğundan büyük görünür. Anlamlı bir p değeri trendin nedenini açıklamaz — arazi kullanımı değişimi, ölçüm yöntemi değişikliği ve iklim sinyali aynı istatistiği üretebilir.",
"ns.split":"İkiye bölme kontrolü",
"ns.splitNote":"Kayıt iki eşit parçaya bölünüp her parçaya ayrı GPD uyumlanır. Bu, parametrik trend modelinden bağımsız, model varsayımı içermeyen bir kontroldür. İki yarının güven aralıkları örtüşüyorsa parametrik trendi temkinli yorumlayın.",
"ns.need":"Trend analizi için en az 20 bağımsız olay ve 8 yıllık kayıt gerekir. Eşiği düşürmeyi veya daha uzun bir seri yüklemeyi deneyin.",
"ns.statRateB":"Oran eğimi b (1/yıl)","ns.statRateCh":"Oran değişimi","ns.statScaleB":"Ölçek eğimi β₁ (1/yıl)","ns.statScaleCh":"Ölçek değişimi",
"ns.statLRT":"LRT p (ölçek)","ns.statRateP":"LRT p (oran)","ns.statMK":"Mann-Kendall p (tepe)","ns.statEvents":"Bağımsız olay",
"ns.verdictNone":"Bu eşikte ne aşım oranında ne de GPD ölçeğinde istatistiksel olarak anlamlı bir trend bulunamadı (oran p {pr}, ölçek p {ps}). Durağan model bu kayıt için savunulabilir. Bu, trendin yok olduğunu değil, mevcut kayıt uzunluğuyla saptanamadığını gösterir.",
"ns.verdictRate":"Aşım oranında anlamlı bir trend var (p {pr}): eşik aşımları yılda yaklaşık %{chr} oranında değişiyor. GPD ölçeğinde anlamlı değişim yok (p {ps}). Olaylar sıklaşıyor ancak tek bir olayın büyüklük dağılımı benzer kalıyor.",
"ns.verdictScale":"GPD ölçeğinde anlamlı bir trend var (p {ps}): aşım büyüklükleri yılda yaklaşık %{chs} oranında değişiyor. Aşım oranında anlamlı değişim yok (p {pr}). Olay sayısı benzer kalırken olaylar büyüyor.",
"ns.verdictBoth":"Hem aşım oranı (p {pr}, yılda ≈%{chr}) hem de GPD ölçeği (p {ps}, yılda ≈%{chs}) anlamlı biçimde değişiyor. Durağan model bu kayıt için uygun değildir; aşağıdaki etkin tekerrür debisi eğrisi ve kümülatif risk hesabı kullanılmalıdır.",
"ns.rpWarn":"Trend varlığında “T yıllık debi” tek bir sayı değildir. Aşağıdaki etkin değer yalnızca ilgili yıl için geçerlidir; projeksiyon, uydurulan eğilimin gelecekte de süreceği varsayımına dayanır ve bu varsayım istatistikle değil, havzanın fiziğiyle gerekçelendirilmelidir. Kayıt dışına uzatılan yıllar için belirsizlik hızla büyür.",
"ns.dlLevel":"Tasarım ömrü debisi","ns.dlNote":"{y0}–{y1} penceresinde en az bir kez aşılma olasılığı %{r} olan debi: durağan modelde {zs} m³/s, trendli modelde {zn} m³/s.",
"ns.dlCapped":"Hedef risk, model aralığında erişilebilen hiçbir debiye karşılık gelmiyor; hedefi büyütün veya tasarım penceresini kısaltın.",
"ns.colTest":"Test","ns.colStat":"İstatistik","ns.colP":"p değeri","ns.colNote":"Yorum",
"ns.mkCount":"Mann-Kendall — yıllık olay sayısı","ns.mkExcess":"Mann-Kendall — aşım büyüklükleri","ns.mkPeak":"Mann-Kendall — olay tepe debileri",
"ns.lrRate":"Olabilirlik oranı — oran modeli λ(t)","ns.lrScale":"Olabilirlik oranı — ölçek modeli σ(t)","ns.senPeak":"Sen eğimi — tepe debileri",
"ns.sig":"Anlamlı (p < 0.05)","ns.nsig":"Anlamsız (p ≥ 0.05)",
"ns.firstHalf":"İlk yarı","ns.secondHalf":"İkinci yarı","ns.obs":"olay",
"ns.effYear":"Yıl","ns.effQ":"Etkin Q{T} (m³/s)","ns.cnt":"Olay sayısı","ns.riskY":"Kümülatif risk",
"ns.stationary":"Durağan model","ns.nonstationary":"Trendli model","ns.obsCounts":"Gözlenen","ns.q50":"Model medyanı","ns.q90":"Model %90 kantili","ns.peaks":"Olay tepeleri",
"ns.extrapWarn":"Tasarım penceresi kaydın sonundan {k} yıl ötesine uzanıyor. Bu bir tahmin değil, uydurulan eğilimin uzatılmasıdır; sonucu senaryo olarak okuyun.",
"set.station":"İstasyon","set.thr":"Threshold","set.sep":"Ayırma aralığı","set.run":"Yeniden hesapla",
"home.lede":"Günlük veya saatlik akım serilerinizden bağımsız taşkın olaylarını çıkarın, eşik seçimini inceleyin, GPD modellerini karşılaştırın ve tekerrür debilerini belirsizlikleriyle birlikte hesaplayın.",
"home.dragHint":"Turuncu eşik çizgisini sürükleyin — eşiği aşan bağımsız olaylar anında yeniden sayılır.",
"home.upload":"Veri yükle","home.demo":"Demo veriyi dene","home.manual":"Manuel veri girişi","home.multi":"Çoklu istasyon",
"home.chainTitle":"Platformun zinciri",
"home.chainNote":"Amaç yalnızca bir Q100 sayısı üretmek değil; bu sayının eşik seçimine, olay ayırmaya ve örnekleme belirsizliğine ne kadar duyarlı olduğunu göstermektir.",
"home.f1t":"Threshold × declustering matrisi","home.f1":"Q100'ün eşik seçiminden mi yoksa olay ayırma aralığından mı daha çok etkilendiğini tek bakışta gösterir.",
"home.f2t":"Dört POT-GPD yaklaşımı","home.f2":"Uzman seçimi, diagnostik stabilite, uyum iyiliği destekli seçim ve eşik topluluğu aynı veri üzerinde karşılaştırılır.",
"home.f3t":"İki belirsizlik kaynağı birlikte","home.f3":"Bootstrap örnekleme belirsizliği ile eşik kaynaklı yayılım ayrı ayrı raporlanır.",
"home.heroEvent":"Bağımsız taşkın olayı","home.heroThr":"POT eşiği u","home.heroCount":"olay",
"data.sub":"Excel/CSV yükleyin, manuel girin veya sentetik demo istasyonlarla başlayın.",
"data.fileTitle":"Dosya yükle","data.drop":"Dosyayı buraya bırakın veya seçmek için tıklayın","data.sheet":"Sayfa",
"data.mapTitle":"Kolon eşleştirme","data.import":"Veriyi içe aktar",
"data.req":"Zorunlu alanlar: station_id, datetime, discharge. Diğer kolonlar opsiyoneldir.",
"data.manualTitle":"Manuel giriş","data.manualHint":"Her satıra bir gözlem: tarih ve debi. Ayırıcı olarak virgül, noktalı virgül, sekme veya boşluk kullanabilirsiniz.",
"data.manMode":"Giriş biçimi","data.manDT":"tarih , debi","data.manQ":"yalnızca debi (sıralı)","data.manStart":"Başlangıç tarihi","data.manStep":"Adım",
"data.manAdd":"Seriyi ekle","data.demoAdd":"Sentetik demo istasyonları yükle",
"data.synthT":"SENTETİK GÖSTERİM VERİSİ — GÖZLEM DEĞİLDİR.","data.synth":"Demo istasyonlar yalnızca arayüzü öğretmek için üretilir ve gerçek ölçüm olarak kullanılamaz.",
"data.loaded":"Yüklenen istasyonlar","data.wide":"Geniş (wide) format algılandı","data.wideQ":"POT-GPD formatına dönüştürülsün mü?",
"data.wideYes":"Evet, dönüştür","data.long":"Uzun (long) format algılandı",
"data.imported":"{n} gözlem, {s} istasyon içe aktarıldı.","data.needCols":"Lütfen datetime ve discharge kolonlarını seçin.",
"data.none":"(yok)","data.badRows":"{n} satır geçersiz tarih veya debi nedeniyle atlandı.",
"qc.sub":"POT analizinden önce kayıt uzunluğu, boşluklar ve şüpheli değerler incelenir.",
"qc.period":"Kayıt dönemi","qc.len":"Kayıt uzunluğu","qc.res":"Zaman çözünürlüğü","qc.obs":"Gözlem sayısı",
"qc.missing":"Eksik oran","qc.dupl":"Yinelenen zaman damgası","qc.neg":"Negatif debi","qc.zero":"Sıfır debi",
"qc.max":"En büyük debi","qc.med":"Medyan debi","qc.mean":"Ortalama debi","qc.gapmax":"En uzun boşluk","qc.cover":"Kapsama",
"qc.gapTitle":"Veri boşlukları","qc.noGap":"Belirgin bir veri boşluğu bulunmadı.",
"qc.nofill":"{n} veri boşluğu tespit edildi (en uzunu {d}). Bu boşluklar otomatik doldurulmayacaktır: taşkın analizinde ekstrem değerlerin interpolasyonu yanıltıcı sonuç üretir.",
"qc.negWarn":"{n} negatif debi değeri bulundu. Bu değerler analiz dışı bırakılır; kaynağını kontrol edin.",
"qc.dupWarn":"{n} yinelenen zaman damgası bulundu; ilk kayıt korunur.",
"qc.shortWarn":"Kayıt uzunluğu {y} yıl. Uzun tekerrür sürelerinde ekstrapolasyon belirsizliği yüksek olacaktır.",
"qc.ok":"Veri temel kontrollerden geçti.",
"qc.effective":"Etkin kayıt uzunluğu","qc.effNote":"Etkin kayıt uzunluğu = toplam süre × kapsama. Olay hızı λ bu değerle hesaplanır.",
"qc.res.hourly":"saatlik","qc.res.daily":"günlük","qc.res.irregular":"düzensiz","qc.res.hx":"{h} saatlik",
"pot.sub":"Eşik aşımları ile bağımsızlaştırılmış taşkın olayları aynı eksende gösterilir.",
"pot.thrType":"Eşik tipi","pot.pct":"Yüzdelik","pot.abs":"Mutlak değer","pot.thrAbs":"u değeri",
"pot.legend":"Gri: tüm seri · turuncu: eşik · soluk kırmızı: ham aşımlar · dolu kırmızı: bağımsız POT olayları (küme maksimumları).",
"pot.clusters":"Küme ayrıntısı","pot.season":"Taşkın mevsimselliği",
"pot.seasonNote":"Olayların ay dağılımı rejim hakkında ipucu verir; sistem bunu otomatik olarak \"kar taşkını\" gibi etiketlemez.",
"pot.u":"Eşik u","pot.raw":"Ham aşım","pot.events":"Bağımsız olay","pot.lambda":"Olay hızı","pot.perYear":"olay/yıl",
"pot.maxEx":"En büyük aşım","pot.meanEx":"Ortalama aşım","pot.dur":"Ortalama küme süresi",
"pot.cl":"Küme","pot.start":"Başlangıç","pot.peak":"Tepe zamanı","pot.qmax":"Q_max","pot.exc":"Aşım","pot.n":"Gözlem",
"pot.few":"Yalnızca {n} bağımsız olay var. GPD tahmini kararsız olabilir; eşiği düşürmeyi değerlendirin.",
"thr.sub":"Ortalama artık ömür, parametre stabilitesi ve eşik duyarlılığı birlikte incelenir.",
"thr.gridFrom":"Tarama başlangıcı (%)","thr.gridTo":"Tarama sonu (%)","thr.gridStep":"Adım (%)","thr.minEv":"Minimum olay sayısı",
"thr.scan":"Eşik taramasını çalıştır","thr.mrl":"Ortalama aşım (mean residual life)",
"thr.mrlNote":"GPD varsayımı geçerliyse yeterince yüksek eşiklerde eğri yaklaşık doğrusallaşır. En doğrusal nokta tek başına \"doğru eşik\" değildir; bant %95 güven aralığıdır.",
"thr.shape":"Shape parametresi ξ(u)","thr.mscale":"Düzeltilmiş ölçek σ* = σ − ξu","thr.sens":"Eşik duyarlılığı",
"thr.sensNote":"Sitenin temel sorusu: eşik değişirse tekerrür debisi ne kadar değişiyor?","thr.table":"Eşik tarama tablosu",
"thr.band":"Önerilen eşik bandı","thr.bandNone":"Ölçütleri sağlayan kararlı bir bant bulunamadı. Tarama aralığını genişletin veya minimum olay sayısını düşürün.",
"thr.bandMsg":"{a}–{b} yüzdelik aralığında ξ görece kararlı görünmektedir ve olay sayısı yeterlidir. Bu bir öneridir, kesin bir doğru eşik değildir.",
"thr.tsi":"Eşik duyarlılık indeksi (TSI)",
"thr.tsiNote":"TSI, önerilen bant içindeki Q{T} değişiminin medyana oranıdır. Bu, platforma özgü bir tanı ölçütüdür; evrensel bir hidrolojik indeks olarak sunulmamalıdır.",
"thr.needScan":"Önce eşik taramasını çalıştırın.",
"thr.colU":"u","thr.colPct":"Yüzdelik","thr.colN":"Olay","thr.colLam":"λ (1/yıl)","thr.colXi":"ξ","thr.colSe":"SE(ξ)","thr.colSig":"σ","thr.colMs":"σ*","thr.colAD":"AD","thr.colQ":"Q{T}",
"gpd.sub":"Genelleştirilmiş Pareto dağılımının parametreleri, uyum iyiliği ve tanı grafikleri.",
"gpd.est":"Tahmin edici","gpd.gofB":"GOF bootstrap tekrarı","gpd.runGof":"Uyum iyiliği testini çalıştır",
"gpd.dens":"Aşım histogramı ve uyumlanan yoğunluk","gpd.cdf":"Ampirik ve modellenen CDF","gpd.gof":"Uyum iyiliği",
"gpd.gofHint":"Anderson-Darling ve Cramér-von Mises istatistikleri parametreler tahmin edildiği için parametrik bootstrap ile değerlendirilir.",
"gpd.sigma":"Ölçek σ","gpd.xi":"Shape ξ","gpd.nll":"−log L","gpd.nev":"Olay sayısı",
"gpd.heavy":"ξ = {x} pozitiftir: üst kuyruk ağırdır. Uzun tekerrür sürelerinde tahminler hızla büyür ve mutlaka güven aralıklarıyla birlikte yorumlanmalıdır.",
"gpd.light":"ξ = {x} negatiftir: model sonlu bir üst sınır ima eder (yaklaşık {b} m³/s). Bu sınır fiziksel bir üst limit kanıtı değildir, yalnızca uyumlanan modelin özelliğidir.",
"gpd.expo":"ξ = {x} sıfıra yakındır: kuyruk yaklaşık üstel davranır ve tekerrür seviyeleri log(T) ile doğrusal artar.",
"gpd.seWarn":"SE(ξ) = {s}, ξ tahmininin belirsizliği yüksektir. Kuyruk davranışı hakkında güçlü yorum yapmaktan kaçının.",
"gpd.xiHigh":"ξ = {x} oldukça yüksektir. Uzun tekerrür aralıklarında tahminler hızla büyür; sonuçları mutlaka güven aralıklarıyla birlikte okuyun ve eşiğin çok düşük seçilmiş olabileceğini kontrol edin.",
"gpd.xiExtreme":"ξ = {x} > 0.5. Bu değerde GPD varyansı sonsuzdur (ξ ≥ 0.5) ve ξ ≥ 1 ise ortalama da tanımsızdır. Bu genellikle eşiğin çok düşük seçildiğini, karışık bir popülasyonun modellendiğini veya olay sayısının yetersiz olduğunu gösterir. Q100 gibi ekstrapolasyonlar bu durumda güvenilmezdir; eşiği yükseltip parametre stabilitesini yeniden inceleyin.",
"gpd.adRes":"AD = {a} (p ≈ {p}) · CvM = {c} (p ≈ {q}) · KS = {k}",
"gpd.adGood":"Uyum iyiliği testleri mevcut eşikte GPD varsayımını reddetmiyor.",
"gpd.adBad":"Uyum iyiliği testleri mevcut eşikte GPD varsayımıyla uyumsuzluğa işaret ediyor. Daha yüksek bir eşik deneyin.",
"gpd.running":"Parametrik bootstrap çalışıyor…",
"cmp.sub":"Aynı seri üzerinde dört farklı eşik belirleme stratejisi.",
"cmp.T":"Karşılaştırma tekerrür süresi (yıl)","cmp.B":"Bootstrap tekrarı","cmp.run":"Dört yöntemi çalıştır",
"cmp.m1":"Yöntem 1 · Uzman POT-GPD","cmp.m1d":"Eşik, ayırma aralığı ve tahmin edici tamamen kullanıcı tarafından seçilir.",
"cmp.m2":"Yöntem 2 · Diagnostik stabilite","cmp.m2d":"MRL, ξ ve σ* stabilitesi, olay sayısı ve eşik duyarlılığı birlikte değerlendirilip bir bant önerilir.",
"cmp.m3":"Yöntem 3 · Uyum iyiliği destekli","cmp.m3d":"GPD uyumunu kabul edilebilir düzeyde sağlayan en düşük kararlı eşik seçilir; en yüksek p değeri otomatik olarak seçilmez.",
"cmp.m4":"Yöntem 4 · Eşik topluluğu","cmp.m4d":"Diagnostik bant içindeki tüm eşiklerin tekerrür debisi medyanı ve yayılımı raporlanır.",
"cmp.m4warn":"Yöntem 4 bir eşik duyarlılık topluluğudur; literatürde yerleşik bağımsız bir tahmin edici olarak yorumlanmamalıdır.",
"cmp.method":"Yöntem","cmp.thr":"Eşik","cmp.spread":"Eşik kaynaklı yayılım","cmp.ci":"%95 bootstrap GA",
"cmp.agree":"Dört yöntemin Q{T} tahminleri {p}% aralığında değişiyor.",
"cmp.agreeLow":"Yöntemler birbirine yakın sonuç veriyor; eşik seçimi bu seri için belirleyici değil.",
"cmp.agreeHigh":"Yöntemler belirgin biçimde farklılaşıyor; eşik seçimi bu seri için sonucu güçlü biçimde etkiliyor. Raporda tek bir sayı yerine aralık verilmesi daha dürüst olur.",
"cmp.needData":"Önce bir istasyon seçin ve eşik taramasını çalıştırın.",
"risk.sub":"Bootstrap güven aralıkları, tekerrür seviyesi grafiği ve tasarım ömrü riski.",
"risk.form":"Tekerrür formülasyonu","risk.poisson":"Poisson-GPD (yıllık aşılma olasılığı = 1/T)","risk.classic":"Klasik POT (λT aşım ölçeği)",
"risk.B":"Bootstrap tekrarı","risk.seed":"Rastgelelik tohumu","risk.rate":"Olay hızı belirsizliği","risk.run":"Bootstrap çalıştır",
"risk.Ts":"Tekerrür süreleri","risk.table":"Tekerrür debisi tablosu","risk.plot":"Tekerrür seviyesi grafiği",
"risk.boot":"Bootstrap dağılımı","risk.life":"Tasarım ömrü riski","risk.life2":"Proje ömrü","risk.q100":"Q100 gerçekte ne anlatır?",
"risk.heat":"Threshold × declustering duyarlılık matrisi","risk.heatT":"Tekerrür süresi","risk.heatRun":"Matrisi hesapla",
"risk.colT":"T (yıl)","risk.colQ":"Q(T)","risk.colLo":"Alt %2.5","risk.colHi":"Üst %97.5","risk.colW":"GA genişliği",
"risk.extrap":"İstenen tekerrür süresi ({t} yıl) kayıt uzunluğunun ({y} yıl) {r} katıdır. Ekstrapolasyon belirsizliği yüksektir; bu tahmini temkinli yorumlayın.",
"risk.lifeOut":"T = {t} yıl için yıllık aşılma olasılığı yaklaşık %{p}. {n} yıllık bir proje ömründe en az bir kez aşılma olasılığı ≈ %{r}.",
"risk.lifeNote":"Hesap, yıllar arası bağımsızlık ve durağanlık varsayar: R = 1 − (1 − 1/T)^N.",
"risk.q100a":"Q100, belirli varsayımlar altında herhangi bir yılda yaklaşık %1 yıllık aşılma olasılığıyla ilişkilendirilen taşkın büyüklüğüdür.",
"risk.q100b":"Tekerrür süresi bir geri sayım değildir.",
"risk.q100c":"Bu yıl Q100 aşılırsa, gelecek yıl yeniden aşılma olasılığı yine yaklaşık %1'dir. Ayrıca bu olasılık, serinin durağan olduğu ve modelin doğru olduğu varsayımına dayanır.",
"risk.needBoot":"Güven aralıkları için bootstrap çalıştırın.",
"risk.bootDone":"{b} tekrar tamamlandı.","risk.heatNote":"Satırlar ayırma aralığını, sütunlar eşik yüzdeliğini gösterir. Renk Q{T} değeridir.",
"risk.heatRead":"Bu seride Q{T}, eşik değişiminden %{a}, ayırma aralığı değişiminden %{b} oranında etkilenmektedir.",
"multi.sub":"Tüm istasyonlar aynı kurallarla işlenir ve karşılaştırılır.",
"multi.mode":"Eşik stratejisi","multi.fixed":"Ortak yüzdelik (mevcut ayar)","multi.auto":"İstasyon başına diagnostik seçim (Yöntem 2)",
"multi.T":"Tekerrür süresi","multi.B":"Bootstrap tekrarı","multi.run":"Tüm istasyonları çalıştır",
"multi.map":"İstasyon konumları","multi.mapNote":"Koordinat bilgisi olan istasyonlar gösterilir; nokta büyüklüğü tekerrür debisiyle orantılıdır.",
"multi.cmp":"İstasyonlar arası karşılaştırma","multi.spec":"Özgül debi q = Q/A",
"multi.colSt":"İstasyon","multi.colY":"Yıl","multi.colN":"Olay","multi.colThr":"Eşik","multi.colXi":"ξ","multi.colQ":"Q{T}","multi.colCI":"%95 GA","multi.colSpec":"q (m³/s/km²)",
"multi.noArea":"Havza alanı girilmediği için özgül debi hesaplanmadı.",
"rep.sub":"Tekrarlanabilir ayar dosyası, veri çıktıları ve bilimsel rapor.",
"rep.files":"İndirilebilir dosyalar","rep.dlNote":"İndirme isteğinde tarayıcı onay ister; dosya yalnızca siz kabul ettiğinizde kaydedilir.",
"rep.settings":"Ayarlar (tekrarlanabilirlik)","rep.expSet":"Ayarları indir","rep.impSet":"Metindeki ayarları uygula",
"rep.gen":"Bilimsel rapor","rep.build":"Raporu oluştur","rep.dl":"HTML olarak indir",
"rep.secs":["İstasyon bilgisi","Veri dönemi","Veri kalitesi","POT yöntemi","Eşik seçimi","Olay ayırma","GPD parametreleri","Model tanıları","Tekerrür debileri","Bootstrap belirsizliği","Eşik duyarlılığı","Tasarım ömrü riski","Durağanlık kontrolü","Sınırlamalar","Yöntem notu"],
"rep.limits":["Tekerrür hesapları serinin durağan olduğu varsayımına dayanır. Bu varsayım 9. modülde sınanır; rapordaki durağanlık bölümü sonucu özetler.","Tekerrür debileri kayıt uzunluğunun ötesine ekstrapolasyon içerir ve bu bölgede belirsizlik hızla büyür.","Eşik ve ayırma aralığı seçimi sonucu etkiler; bu etki duyarlılık analizinde raporlanmıştır.","Ölçüm hatası, anahtar eğrisi belirsizliği ve eksik veri etkileri bu hesaba dahil değildir.","Bootstrap güven aralıkları yalnızca örnekleme belirsizliğini yansıtır, model belirsizliğini değil."],
"ui.cancel":"İptal","ui.yes":"Evet","ui.no":"Hayır","ui.year":"yıl","ui.years":"yıl","ui.day":"gün","ui.hour":"saat",
"ui.none":"Veri yok. Önce Data Studio'dan bir seri yükleyin.","ui.working":"Hesaplanıyor…","ui.done":"Tamamlandı.",
"ui.autoThr":"Başlangıç eşiği önerildi: P{p} (stabil bant P{a}–P{b}). Bu yalnızca bir başlangıç noktasıdır; kaydırıcıdan serbestçe değiştirebilirsiniz.",
"ui.cancelled":"İptal edildi.","ui.copy":"Kopyalandı.","ui.dlok":"Dosya kaydedildi.","ui.dlno":"İndirme bu görünümde kullanılamıyor.",
"ui.dldecl":"İndirme iptal edildi.","ui.selectSt":"İstasyon seçin",
"m.title":"Yöntem notu"
},
en:{
"brand.sub":"Peaks-Over-Threshold flood frequency analysis",
"nav.method":"Methodology","nav.template":"Excel template","nav.new":"New analysis",
"rail.pipeline":"Analysis chain","rail.settings":"Settings",
"mod.data":"Data Studio","mod.qc":"Data quality","mod.pot":"POT event explorer","mod.thr":"Threshold Lab",
"mod.gpd":"GPD Model Lab","mod.cmp":"Four methods","mod.risk":"Return levels & risk","mod.multi":"Multi-station","mod.report":"Report & export",
"mod.trend":"Trend & nonstationarity",
"ns.sub":"Are the exceedance rate and the GPD scale changing with time? And if they are, what happens to the meaning of a return period?",
"ns.warnTop":"Stationarity is the implicit assumption behind every module up to this point. This one tests it. If a trend is present, a single “100-year discharge” no longer describes the whole record — each year has its own effective value.",
"ns.T":"Return period T (years)","ns.y0":"Design window starts","ns.N":"Design life N (years)","ns.risk":"Target cumulative risk (%)",
"ns.run":"Run trend analysis",
"ns.ctrlNote":"The analysis uses the independent events produced by the current threshold and separation interval. Change the threshold and the result changes with it.",
"ns.plotRate":"Annual event counts and the rate model",
"ns.plotRateNote":"Points are the observed independent events per year; the curve is the Poisson fit λ(t) = exp(a + b·t). Years with partial data are scaled by their coverage.",
"ns.plotMag":"Event magnitudes and the scale model",
"ns.plotMagNote":"Curves are the 50% and 90% model quantiles from the fit σ(t) = exp(β₀ + β₁·t), with ξ held constant.",
"ns.plotEff":"Effective return level",
"ns.plotEffNote":"For each year, the discharge with exceedance probability 1/T within that year. The dashed line is the single stationary value.",
"ns.plotRisk":"Cumulative risk over the design life",
"ns.plotRiskNote":"Probability that a discharge is exceeded at least once in the chosen window, under the stationary and the trended model.",
"ns.tests":"Statistical tests",
"ns.testNote":"Mann-Kendall and likelihood-ratio tests assume independent events; if the separation interval is too short, significance looks stronger than it is. A significant p value does not explain the cause — land-use change, a change in gauging practice and a climate signal all produce the same statistic.",
"ns.split":"Split-half check",
"ns.splitNote":"The record is cut into two equal parts and a separate GPD is fitted to each. This is a model-free cross-check on the parametric trend. If the two confidence intervals overlap, read the parametric trend cautiously.",
"ns.need":"Trend analysis needs at least 20 independent events and 8 years of record. Try a lower threshold or a longer series.",
"ns.statRateB":"Rate slope b (per year)","ns.statRateCh":"Rate change","ns.statScaleB":"Scale slope β₁ (per year)","ns.statScaleCh":"Scale change",
"ns.statLRT":"LRT p (scale)","ns.statRateP":"LRT p (rate)","ns.statMK":"Mann-Kendall p (peaks)","ns.statEvents":"Independent events",
"ns.verdictNone":"At this threshold no statistically significant trend was found, either in the exceedance rate or in the GPD scale (rate p {pr}, scale p {ps}). A stationary model is defensible for this record. That means a trend was not detectable at this record length, not that none exists.",
"ns.verdictRate":"The exceedance rate carries a significant trend (p {pr}): threshold exceedances change by roughly {chr}% per year. The GPD scale does not change significantly (p {ps}). Events are becoming more frequent while the size distribution of a single event stays similar.",
"ns.verdictScale":"The GPD scale carries a significant trend (p {ps}): excess magnitudes change by roughly {chs}% per year. The exceedance rate does not change significantly (p {pr}). The number of events stays similar while the events grow.",
"ns.verdictBoth":"Both the exceedance rate (p {pr}, ≈{chr}% per year) and the GPD scale (p {ps}, ≈{chs}% per year) change significantly. A stationary model is not appropriate for this record; use the effective return level curve and the cumulative risk calculation below.",
"ns.rpWarn":"Under a trend, “the T-year discharge” is not one number. The effective value below applies to its own year only, and any projection rests on the assumption that the fitted tendency continues — an assumption to be justified by the physics of the catchment, not by the statistics. Uncertainty grows quickly beyond the observed record.",
"ns.dlLevel":"Design-life level","ns.dlNote":"Discharge with a {r}% chance of being exceeded at least once between {y0} and {y1}: {zs} m³/s under the stationary model, {zn} m³/s under the trended model.",
"ns.dlCapped":"The target risk does not correspond to any discharge within the model range; raise the target or shorten the design window.",
"ns.colTest":"Test","ns.colStat":"Statistic","ns.colP":"p value","ns.colNote":"Reading",
"ns.mkCount":"Mann-Kendall — annual event counts","ns.mkExcess":"Mann-Kendall — excess magnitudes","ns.mkPeak":"Mann-Kendall — event peak discharges",
"ns.lrRate":"Likelihood ratio — rate model λ(t)","ns.lrScale":"Likelihood ratio — scale model σ(t)","ns.senPeak":"Sen slope — peak discharges",
"ns.sig":"Significant (p < 0.05)","ns.nsig":"Not significant (p ≥ 0.05)",
"ns.firstHalf":"First half","ns.secondHalf":"Second half","ns.obs":"events",
"ns.effYear":"Year","ns.effQ":"Effective Q{T} (m³/s)","ns.cnt":"Events per year","ns.riskY":"Cumulative risk",
"ns.stationary":"Stationary model","ns.nonstationary":"Trended model","ns.obsCounts":"Observed","ns.q50":"Model median","ns.q90":"Model 90% quantile","ns.peaks":"Event peaks",
"ns.extrapWarn":"The design window reaches {k} years past the end of the record. That is an extension of the fitted tendency rather than a forecast — read it as a scenario.",
"set.station":"Station","set.thr":"Threshold","set.sep":"Separation interval","set.run":"Recompute",
"home.lede":"Extract independent flood events from daily or hourly discharge series, study threshold choice, compare GPD models and compute return levels together with their uncertainty.",
"home.dragHint":"Drag the amber threshold line — independent exceedance events are recounted instantly.",
"home.upload":"Upload data","home.demo":"Try demo data","home.manual":"Manual entry","home.multi":"Multi-station",
"home.chainTitle":"The analysis chain",
"home.chainNote":"The goal is not one Q100 number, but showing how much that number depends on threshold choice, declustering and sampling uncertainty.",
"home.f1t":"Threshold × declustering matrix","home.f1":"Shows at a glance whether Q100 is driven more by threshold choice or by the declustering interval.",
"home.f2t":"Four POT-GPD approaches","home.f2":"Expert choice, diagnostic stability, goodness-of-fit assisted selection and a threshold ensemble on the same series.",
"home.f3t":"Two uncertainty sources side by side","home.f3":"Bootstrap sampling uncertainty and threshold-induced spread are reported separately.",
"home.heroEvent":"Independent flood event","home.heroThr":"POT threshold u","home.heroCount":"events",
"data.sub":"Upload Excel/CSV, type data manually, or start with synthetic demo stations.",
"data.fileTitle":"Upload a file","data.drop":"Drop a file here, or click to choose","data.sheet":"Sheet",
"data.mapTitle":"Column mapping","data.import":"Import data",
"data.req":"Required fields: station_id, datetime, discharge. Everything else is optional.",
"data.manualTitle":"Manual entry","data.manualHint":"One observation per line: date and discharge. Comma, semicolon, tab or space all work as separators.",
"data.manMode":"Entry format","data.manDT":"date , discharge","data.manQ":"discharge only (ordered)","data.manStart":"Start date","data.manStep":"Step",
"data.manAdd":"Add series","data.demoAdd":"Load synthetic demo stations",
"data.synthT":"SYNTHETIC DEMONSTRATION DATA — NOT OBSERVED.","data.synth":"Demo stations exist to teach the interface and must not be used as real measurements.",
"data.loaded":"Loaded stations","data.wide":"Wide format detected","data.wideQ":"Convert to POT-GPD format?",
"data.wideYes":"Yes, convert","data.long":"Long format detected",
"data.imported":"Imported {n} observations across {s} stations.","data.needCols":"Please choose the datetime and discharge columns.",
"data.none":"(none)","data.badRows":"{n} rows skipped: unreadable date or discharge.",
"qc.sub":"Record length, gaps and suspicious values are checked before any POT analysis.",
"qc.period":"Record period","qc.len":"Record length","qc.res":"Time resolution","qc.obs":"Observations",
"qc.missing":"Missing","qc.dupl":"Duplicate timestamps","qc.neg":"Negative flows","qc.zero":"Zero flows",
"qc.max":"Maximum flow","qc.med":"Median flow","qc.mean":"Mean flow","qc.gapmax":"Longest gap","qc.cover":"Coverage",
"qc.gapTitle":"Data gaps","qc.noGap":"No notable data gap was found.",
"qc.nofill":"{n} data gaps detected (longest {d}). They will not be filled automatically: interpolating extremes distorts flood frequency analysis.",
"qc.negWarn":"{n} negative discharge values found. They are excluded from the analysis; check their source.",
"qc.dupWarn":"{n} duplicate timestamps found; the first record is kept.",
"qc.shortWarn":"Record length is {y} years. Extrapolation uncertainty will be high at long return periods.",
"qc.ok":"The series passed the basic checks.",
"qc.effective":"Effective record length","qc.effNote":"Effective record length = total span × coverage. The event rate λ is computed from this value.",
"qc.res.hourly":"hourly","qc.res.daily":"daily","qc.res.irregular":"irregular","qc.res.hx":"{h}-hourly",
"pot.sub":"Raw exceedances and declustered independent flood events on the same axis.",
"pot.thrType":"Threshold type","pot.pct":"Percentile","pot.abs":"Absolute value","pot.thrAbs":"u value",
"pot.legend":"Grey: full series · amber: threshold · faded red: raw exceedances · solid red: independent POT events (cluster maxima).",
"pot.clusters":"Cluster detail","pot.season":"Flood seasonality",
"pot.seasonNote":"The monthly distribution hints at the regime; the system does not auto-label it as \"snowmelt flood\".",
"pot.u":"Threshold u","pot.raw":"Raw exceedances","pot.events":"Independent events","pot.lambda":"Event rate","pot.perYear":"events/yr",
"pot.maxEx":"Largest excess","pot.meanEx":"Mean excess","pot.dur":"Mean cluster duration",
"pot.cl":"Cluster","pot.start":"Start","pot.peak":"Peak time","pot.qmax":"Q_max","pot.exc":"Excess","pot.n":"Obs.",
"pot.few":"Only {n} independent events. GPD estimation may be unstable; consider a lower threshold.",
"thr.sub":"Mean residual life, parameter stability and threshold sensitivity, together.",
"thr.gridFrom":"Scan from (%)","thr.gridTo":"Scan to (%)","thr.gridStep":"Step (%)","thr.minEv":"Minimum events",
"thr.scan":"Run threshold scan","thr.mrl":"Mean residual life",
"thr.mrlNote":"If the GPD assumption holds, the curve becomes approximately linear above a high enough threshold. The most linear point is not by itself the correct threshold; the band is a 95% interval.",
"thr.shape":"Shape parameter ξ(u)","thr.mscale":"Modified scale σ* = σ − ξu","thr.sens":"Threshold sensitivity",
"thr.sensNote":"The core question of this platform: how much does the return level move when the threshold moves?","thr.table":"Threshold scan table",
"thr.band":"Recommended threshold band","thr.bandNone":"No stable band met the criteria. Widen the scan range or lower the minimum event count.",
"thr.bandMsg":"Between the {a} and {b} percentiles ξ looks relatively stable and the event count stays adequate. This is a suggestion, not a definitive threshold.",
"thr.tsi":"Threshold sensitivity index (TSI)",
"thr.tsiNote":"TSI is the spread of Q{T} across the recommended band relative to its median. It is a diagnostic metric specific to this platform, not a universal hydrological index.",
"thr.needScan":"Run the threshold scan first.",
"thr.colU":"u","thr.colPct":"Percentile","thr.colN":"Events","thr.colLam":"λ (1/yr)","thr.colXi":"ξ","thr.colSe":"SE(ξ)","thr.colSig":"σ","thr.colMs":"σ*","thr.colAD":"AD","thr.colQ":"Q{T}",
"gpd.sub":"Generalized Pareto parameters, goodness of fit and diagnostic plots.",
"gpd.est":"Estimator","gpd.gofB":"GOF bootstrap replicates","gpd.runGof":"Run goodness-of-fit test",
"gpd.dens":"Excess histogram with fitted density","gpd.cdf":"Empirical vs fitted CDF","gpd.gof":"Goodness of fit",
"gpd.gofHint":"Anderson-Darling and Cramér-von Mises statistics are assessed by parametric bootstrap because the parameters are estimated.",
"gpd.sigma":"Scale σ","gpd.xi":"Shape ξ","gpd.nll":"−log L","gpd.nev":"Events",
"gpd.heavy":"ξ = {x} is positive: the upper tail is heavy. Long return period estimates grow quickly and must be read together with their intervals.",
"gpd.light":"ξ = {x} is negative: the model implies a finite upper bound (about {b} m³/s). That bound is a property of the fitted model, not evidence of a physical limit.",
"gpd.expo":"ξ = {x} is close to zero: the tail behaves nearly exponentially and return levels rise linearly with log(T).",
"gpd.seWarn":"SE(ξ) = {s}; tail-shape uncertainty is high. Avoid strong statements about tail behaviour.",
"gpd.xiHigh":"ξ = {x} is high. Estimates grow quickly at long return periods, so read the results together with their confidence intervals and check whether the threshold is set too low.",
"gpd.xiExtreme":"ξ = {x} > 0.5. At this value the GPD has infinite variance (ξ ≥ 0.5), and for ξ ≥ 1 even the mean is undefined. This usually signals a threshold set too low, a mixed population, or too few events. Extrapolations such as Q100 are unreliable here — raise the threshold and re-inspect parameter stability.",
"gpd.adRes":"AD = {a} (p ≈ {p}) · CvM = {c} (p ≈ {q}) · KS = {k}",
"gpd.adGood":"The goodness-of-fit tests do not reject the GPD assumption at this threshold.",
"gpd.adBad":"The goodness-of-fit tests indicate a conflict with the GPD assumption at this threshold. Try a higher threshold.",
"gpd.running":"Parametric bootstrap running…",
"cmp.sub":"Four threshold strategies applied to the same series.",
"cmp.T":"Comparison return period (years)","cmp.B":"Bootstrap replicates","cmp.run":"Run the four methods",
"cmp.m1":"Method 1 · Expert POT-GPD","cmp.m1d":"Threshold, separation interval and estimator are chosen entirely by the user.",
"cmp.m2":"Method 2 · Diagnostic stability","cmp.m2d":"MRL, ξ and σ* stability, event count and threshold sensitivity are weighed together to suggest a band.",
"cmp.m3":"Method 3 · GOF-assisted","cmp.m3d":"The lowest stable threshold that still fits the GPD acceptably is selected; the highest p-value is never auto-selected.",
"cmp.m4":"Method 4 · Threshold ensemble","cmp.m4d":"Median and spread of return levels across every threshold in the diagnostic band.",
"cmp.m4warn":"Method 4 is a threshold-sensitivity ensemble and should not be interpreted as a universally established standalone estimator.",
"cmp.method":"Method","cmp.thr":"Threshold","cmp.spread":"Threshold-induced spread","cmp.ci":"95% bootstrap CI",
"cmp.agree":"The four methods place Q{T} within a {p}% range.",
"cmp.agreeLow":"The methods agree closely; threshold choice is not decisive for this series.",
"cmp.agreeHigh":"The methods differ markedly; threshold choice strongly drives the answer here. Reporting a range rather than a single number is the more honest option.",
"cmp.needData":"Select a station and run the threshold scan first.",
"risk.sub":"Bootstrap intervals, the return level plot and design-life risk.",
"risk.form":"Return level formulation","risk.poisson":"Poisson-GPD (annual exceedance probability = 1/T)","risk.classic":"Classic POT (λT exceedance scale)",
"risk.B":"Bootstrap replicates","risk.seed":"Random seed","risk.rate":"Event rate uncertainty","risk.run":"Run bootstrap",
"risk.Ts":"Return periods","risk.table":"Return level table","risk.plot":"Return level plot",
"risk.boot":"Bootstrap distribution","risk.life":"Design-life risk","risk.life2":"Project life","risk.q100":"What does Q100 actually mean?",
"risk.heat":"Threshold × declustering sensitivity matrix","risk.heatT":"Return period","risk.heatRun":"Compute matrix",
"risk.colT":"T (yr)","risk.colQ":"Q(T)","risk.colLo":"Lower 2.5%","risk.colHi":"Upper 97.5%","risk.colW":"CI width",
"risk.extrap":"The requested return period ({t} yr) is {r}× the record length ({y} yr). Extrapolation uncertainty is high; read this estimate cautiously.",
"risk.lifeOut":"At T = {t} years the annual exceedance probability is about {p}%. Over a project life of {n} years the probability of at least one exceedance is ≈ {r}%.",
"risk.lifeNote":"The calculation assumes independence between years and stationarity: R = 1 − (1 − 1/T)^N.",
"risk.q100a":"Q100 is the flood magnitude associated, under stated assumptions, with roughly a 1% chance of being exceeded in any given year.",
"risk.q100b":"A return period is not a countdown.",
"risk.q100c":"If Q100 is exceeded this year, next year's probability is still about 1%. That probability also rests on the series being stationary and the model being right.",
"risk.needBoot":"Run the bootstrap to obtain confidence intervals.",
"risk.bootDone":"{b} replicates completed.","risk.heatNote":"Rows are separation intervals, columns are threshold percentiles. Colour is Q{T}.",
"risk.heatRead":"For this series Q{T} moves by {a}% across thresholds and by {b}% across declustering intervals.",
"multi.sub":"Every station is processed under the same rules and compared.",
"multi.mode":"Threshold strategy","multi.fixed":"Common percentile (current setting)","multi.auto":"Per-station diagnostic choice (Method 2)",
"multi.T":"Return period","multi.B":"Bootstrap replicates","multi.run":"Run all stations",
"multi.map":"Station locations","multi.mapNote":"Stations with coordinates are shown; marker size scales with the return level.",
"multi.cmp":"Cross-station comparison","multi.spec":"Specific discharge q = Q/A",
"multi.colSt":"Station","multi.colY":"Years","multi.colN":"Events","multi.colThr":"Threshold","multi.colXi":"ξ","multi.colQ":"Q{T}","multi.colCI":"95% CI","multi.colSpec":"q (m³/s/km²)",
"multi.noArea":"Specific discharge was not computed because catchment area is missing.",
"rep.sub":"A reproducible settings file, data exports and a scientific report.",
"rep.files":"Downloadable files","rep.dlNote":"Each download asks for your confirmation; the file is saved only if you accept.",
"rep.settings":"Settings (reproducibility)","rep.expSet":"Download settings","rep.impSet":"Apply settings from the box",
"rep.gen":"Scientific report","rep.build":"Build the report","rep.dl":"Download as HTML",
"rep.secs":["Station information","Data period","Data quality","POT method","Threshold selection","Declustering","GPD parameters","Model diagnostics","Return levels","Bootstrap uncertainty","Threshold sensitivity","Design-life risk","Stationarity check","Limitations","Methodology note"],
"rep.limits":["Return level calculations assume a stationary series. Module 9 tests that assumption and the stationarity section of this report summarises the outcome.","Return levels extrapolate beyond the record length, where uncertainty grows quickly.","Threshold and separation interval choices affect the result; that effect is reported in the sensitivity analysis.","Measurement error, rating-curve uncertainty and missing data are not propagated here.","Bootstrap intervals reflect sampling uncertainty only, not model uncertainty."],
"ui.cancel":"Cancel","ui.yes":"Yes","ui.no":"No","ui.year":"years","ui.years":"years","ui.day":"days","ui.hour":"hours",
"ui.none":"No data yet. Load a series in Data Studio first.","ui.working":"Computing…","ui.done":"Done.",
"ui.autoThr":"Starting threshold suggested: P{p} (stable band P{a}–P{b}). This is only a starting point — change it freely with the slider.",
"ui.cancelled":"Cancelled.","ui.copy":"Copied.","ui.dlok":"File saved.","ui.dlno":"Downloads are unavailable in this view.",
"ui.dldecl":"Download cancelled.","ui.selectSt":"Choose a station",
"m.title":"Methodology note"
}};
let LANG = "tr";
function t(key, vars){
let s = (I18N[LANG] && I18N[LANG][key]) != null ? I18N[LANG][key] : (I18N.en[key] != null ? I18N.en[key] : key);
if (typeof s === "string" && vars) for (const k in vars) s = s.split("{"+k+"}").join(vars[k]);
return s;
}
function applyI18n(){
document.documentElement.lang = LANG;
document.querySelectorAll("[data-i18n]").forEach(function(el){
const v = t(el.getAttribute("data-i18n"));
if (typeof v === "string") el.innerHTML = v;
});
}
"use strict";
/* ============================================================
Numerical core
============================================================ */
const MS_DAY = 86400000, MS_HOUR = 3600000, YEAR_DAYS = 365.25;
function num(x, d){ if(x==null||!isFinite(x)) return "–";
const dd = (d==null)? (Math.abs(x)>=100?0:Math.abs(x)>=10?1:Math.abs(x)>=1?2:3) : d;
return x.toLocaleString(LANG==="tr"?"tr-TR":"en-US",{minimumFractionDigits:dd,maximumFractionDigits:dd}); }
function pct(x,d){ return num(x,d==null?1:d)+"%"; }
function mean(a){ let s=0; for(let i=0;i>>0; return function(){
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a>>>15), 1 | a);
t = (t + Math.imul(t ^ (t>>>7), 61 | t)) ^ t;
return ((t ^ (t>>>14))>>>0) / 4294967296; }; }
function poissonRand(lam, rnd){
if(lam < 30){ const L = Math.exp(-lam); let k=0,p=1; do{ k++; p*=rnd(); }while(p>L); return k-1; }
// normal approximation for large lambda
let u=0,v=0; do{u=rnd();}while(u<=0); v=rnd();
const z = Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v);
return Math.max(0, Math.round(lam + Math.sqrt(lam)*z));
}
/* ---------- GPD distribution ---------- */
function gpdCDF(y, sigma, xi){
if(y<=0) return 0;
if(Math.abs(xi)<1e-8) return 1-Math.exp(-y/sigma);
const z = 1 + xi*y/sigma; if(z<=0) return xi>0?1:1;
return 1 - Math.pow(z, -1/xi);
}
function gpdPDF(y, sigma, xi){
if(y<0) return 0;
if(Math.abs(xi)<1e-8) return Math.exp(-y/sigma)/sigma;
const z = 1 + xi*y/sigma; if(z<=0) return 0;
return Math.pow(z, -1/xi - 1)/sigma;
}
function gpdQuantile(p, sigma, xi){
if(Math.abs(xi)<1e-8) return -sigma*Math.log(1-p);
return sigma/xi*(Math.pow(1-p, -xi)-1);
}
function gpdRandom(rnd, sigma, xi){ let u=rnd(); if(u<=0)u=1e-12; if(u>=1)u=1-1e-12; return gpdQuantile(u,sigma,xi); }
function gpdNLL(sigma, xi, y){
const n=y.length; if(!(sigma>0)||!isFinite(sigma)) return 1e15;
if(Math.abs(xi)<1e-9){ let s=0; for(let i=0;i0)||!isFinite(sigma)||!isFinite(k)) return null;
return {sigma:sigma, xi:-k, method:"pwm"};
}
/* Nelder-Mead on (log sigma, xi) */
function nelderMead(f, x0, maxIter){
maxIter = maxIter||600;
const n = x0.length, alpha=1, gamma=2, rho=0.5, sig=0.5;
let simplex = [x0.slice()];
for(let i=0;i1e-8? 0.08*Math.abs(p[i]) : 0.08); simplex.push(p); }
let fv = simplex.map(f);
for(let it=0; iti).sort((a,b)=>fv[a]-fv[b]);
simplex = idx.map(i=>simplex[i]); fv = idx.map(i=>fv[i]);
if(Math.abs(fv[n]-fv[0]) < 1e-10*(Math.abs(fv[0])+1e-10)) break;
const cen = new Array(n).fill(0);
for(let i=0;ic + alpha*(c - simplex[n][j])); const fr = f(xr);
if(fr < fv[0]){
const xe = cen.map((c,j)=>c + gamma*(xr[j]-c)); const fe = f(xe);
if(fec + rho*(simplex[n][j]-c)); const fc = f(xc);
if(fc < fv[n]){ simplex[n]=xc; fv[n]=fc; }
else { for(let i=1;i<=n;i++){ simplex[i]=simplex[i].map((v,j)=>simplex[0][j]+sig*(v-simplex[0][j])); fv[i]=f(simplex[i]); } }
}
}
const best = fv.map((v,i)=>i).sort((a,b)=>fv[a]-fv[b])[0];
return {x:simplex[best], f:fv[best]};
}
function gpdFitMLE(y, opts){
opts = opts||{};
const n = y.length; if(n<5) return null;
const pwm = gpdFitPWM(y);
const starts = [];
if(opts.init && isFinite(opts.init[0]) && opts.init[0]>0) starts.push([Math.log(opts.init[0]), opts.init[1]]);
if(pwm) starts.push([Math.log(pwm.sigma), Math.max(-0.45, Math.min(0.9, pwm.xi))]);
const m = mean(y);
if(!opts.fast) starts.push([Math.log(Math.max(m,1e-9)), 0.1], [Math.log(Math.max(m,1e-9)), -0.1], [Math.log(Math.max(m*0.7,1e-9)), 0.3]);
else if(!starts.length) starts.push([Math.log(Math.max(m,1e-9)), 0.1]);
const iters = opts.fast? 220 : 600;
const obj = function(p){ const s = Math.exp(p[0]); let xi = p[1];
if(!isFinite(s)||!isFinite(xi)||Math.abs(xi)>5) return 1e15; return gpdNLL(s, xi, y); };
let best=null;
for(const st of starts){ const r = nelderMead(obj, st, iters); if(!best || r.f < best.f) best = r; }
if(!best || best.f>=1e14) return pwm ? {sigma:pwm.sigma, xi:pwm.xi, nll:gpdNLL(pwm.sigma,pwm.xi,y), se:[NaN,NaN], cov:null, method:"pwm-fallback", n:n} : null;
const sigma = Math.exp(best.x[0]), xi = best.x[1];
let se=[NaN,NaN], cov=null;
if(!(opts && opts.noSE)){
const hs = Math.max(1e-5, 1e-4*sigma), hx = 1e-4;
const f = (a,b)=>gpdNLL(a,b,y);
const f0 = f(sigma,xi);
const fss = (f(sigma+hs,xi) - 2*f0 + f(sigma-hs,xi))/(hs*hs);
const fxx = (f(sigma,xi+hx) - 2*f0 + f(sigma,xi-hx))/(hx*hx);
const fsx = (f(sigma+hs,xi+hx) - f(sigma+hs,xi-hx) - f(sigma-hs,xi+hx) + f(sigma-hs,xi-hx))/(4*hs*hx);
const det = fss*fxx - fsx*fsx;
if(isFinite(det) && Math.abs(det)>1e-14){
const c11 = fxx/det, c22 = fss/det, c12 = -fsx/det;
cov = [[c11,c12],[c12,c22]];
se = [c11>0?Math.sqrt(c11):NaN, c22>0?Math.sqrt(c22):NaN];
}
}
return {sigma:sigma, xi:xi, nll:best.f, se:se, cov:cov, method:"mle", n:n};
}
function fitGPD(y, estimator, opts){
if(estimator==="pwm"){ const p = gpdFitPWM(y); if(!p) return null;
return {sigma:p.sigma, xi:p.xi, nll:gpdNLL(p.sigma,p.xi,y), se:[NaN,NaN], cov:null, method:"pwm", n:y.length}; }
return gpdFitMLE(y, opts);
}
/* ---------- Declustering (runs, time based) ---------- */
function decluster(series, u, sepHours){
const sepMs = sepHours*MS_HOUR, clusters = [];
let cur = null;
for(let i=0;iu)) continue;
if(cur && (d.t - cur.lastT) <= sepMs){
cur.lastT = d.t; cur.n++;
if(d.q > cur.qmax){ cur.qmax = d.q; cur.tpeak = d.t; }
} else {
if(cur) clusters.push(cur);
cur = {start:d.t, lastT:d.t, tpeak:d.t, qmax:d.q, n:1};
}
}
if(cur) clusters.push(cur);
for(const c of clusters){ c.excess = c.qmax - u; c.durH = (c.lastT - c.start)/MS_HOUR; }
return clusters;
}
function rawExceedances(series, u){ const o=[]; for(const d of series) if(d.q>u) o.push(d); return o; }
/* ---------- Return levels ---------- */
function returnLevel(u, sigma, xi, lambda, T, mode){
if(!(lambda>0)||!(T>1)) return NaN;
const m = (mode==="classic") ? lambda*T : lambda/(-Math.log(1-1/T));
if(m<=0) return NaN;
if(Math.abs(xi)<1e-8) return u + sigma*Math.log(m);
return u + sigma/xi*(Math.pow(m, xi)-1);
}
function annualExceedProb(z, u, sigma, xi, lambda){
if(z<=u) return 1-Math.exp(-lambda);
const y=z-u; const s = (Math.abs(xi)<1e-8) ? Math.exp(-y/sigma) : Math.pow(Math.max(1e-300,1+xi*y/sigma), -1/xi);
return 1-Math.exp(-lambda*s);
}
function designLifeRisk(T, N){ const p=1/T; return 1-Math.pow(1-p, N); }
/* ============================================================
Nonstationarity: trend in the exceedance RATE and in the GPD SCALE
------------------------------------------------------------
Stationary POT-GPD assumes the event rate and the excess distribution
do not change with time. When they do, a single return period stops
describing the whole record. The tools below quantify the change; the
panel reports them as evidence, never as a licence to extrapolate.
============================================================ */
function normCDF(z){
// Abramowitz & Stegun 7.1.26 on erf
const t = 1/(1+0.3275911*Math.abs(z)/Math.SQRT2);
const y = 1 - (((((1.061405429*t - 1.453152027)*t) + 1.421413741)*t - 0.284496736)*t + 0.254829592)*t*Math.exp(-z*z/2);
return z>=0 ? 0.5*(1+y) : 0.5*(1-y);
}
function chi2p1(D){ if(!(D>0)) return 1; return 2*(1-normCDF(Math.sqrt(D))); }
function mannKendall(y){
const n = y.length; if(n<8) return null;
let S=0;
for(let i=0;i0?1:(d<0?-1:0); }
// tie correction
const counts = {}; y.forEach(v=>{ counts[v]=(counts[v]||0)+1; });
let tie=0; for(const k in counts){ const c=counts[k]; if(c>1) tie += c*(c-1)*(2*c+5); }
const varS = (n*(n-1)*(2*n+5) - tie)/18;
const z = S>0 ? (S-1)/Math.sqrt(varS) : (S<0 ? (S+1)/Math.sqrt(varS) : 0);
const tau = 2*S/(n*(n-1));
return {S:S, varS:varS, z:z, p:2*(1-normCDF(Math.abs(z))), tau:tau, n:n};
}
function senSlope(x, y){
const sl=[];
for(let i=0;i0) ? expo[i] : 1;
const mu = e*Math.exp(a + b*ts[i]);
const r = counts[i] - mu;
g0 += r; g1 += r*ts[i];
h00 += mu; h01 += mu*ts[i]; h11 += mu*ts[i]*ts[i];
}
const det = h00*h11 - h01*h01; if(!(Math.abs(det)>1e-12)) break;
const da = ( h11*g0 - h01*g1)/det, db = (-h01*g0 + h00*g1)/det;
a += da; b += db;
if(Math.abs(da)<1e-10 && Math.abs(db)<1e-10) break;
}
// observed information -> standard errors
let h00=0,h01=0,h11=0;
for(let i=0;i0) ? expo[i] : 1;
const mu = e*Math.exp(a + b*ts[i]);
h00 += mu; h01 += mu*ts[i]; h11 += mu*ts[i]*ts[i];
}
const det = h00*h11 - h01*h01;
const seA = det>0 ? Math.sqrt(h11/det) : NaN, seB = det>0 ? Math.sqrt(h00/det) : NaN;
let ll=0, ll0=0;
const m0 = mean(counts.map((c,i)=>c/((expo&&expo[i]>0)?expo[i]:1)));
for(let i=0;i0) ? expo[i] : 1;
const mu = e*Math.exp(a + b*ts[i]); ll += counts[i]*Math.log(Math.max(1e-300,mu)) - mu;
const mu0 = e*Math.max(1e-9,m0); ll0 += counts[i]*Math.log(mu0) - mu0;
}
const D = 2*(ll-ll0);
return {a:a, b:b, seA:seA, seB:seB, ll:ll, ll0:ll0, D:D, p:chi2p1(D),
lambdaAt:function(t){ return Math.exp(a + b*t); }};
}
/* Nonstationary GPD: sigma(t) = exp(b0 + b1*t), xi constant */
function nsGpdNLL(b0, b1, xi, ts, ys){
let nll = 0;
for(let i=0;i0) || !isFinite(s)) return 1e12;
const z = ys[i]/s;
if(Math.abs(xi)<1e-8){ nll += Math.log(s) + z; }
else {
const w = 1 + xi*z; if(w<=1e-12) return 1e12;
nll += Math.log(s) + (1+1/xi)*Math.log(w);
}
}
return nll;
}
function nsGpdFit(ts, ys, stat){
const b0 = Math.log(Math.max(1e-6, stat && stat.sigma>0 ? stat.sigma : mean(ys)));
const xi0 = stat && isFinite(stat.xi) ? stat.xi : 0.1;
const f = function(v){ return nsGpdNLL(v[0], v[1], v[2], ts, ys); };
let best = null;
const starts = [[b0,0,xi0],[b0,0.01,xi0],[b0,-0.01,xi0],[b0,0,0.05],[b0,0.02,-0.05]];
for(const st0 of starts){
const r = nelderMead(f, st0, 900);
if(r && isFinite(r.f) && (!best || r.f SE for the trend coefficient
const h=[1e-4, 1e-4, 1e-4], p=[b0h,b1h,xih];
const H=[[0,0,0],[0,0,0],[0,0,0]];
for(let i=0;i<3;i++) for(let j=0;j<3;j++){
const pp=p.slice(), pm=p.slice(), mp=p.slice(), mm=p.slice();
pp[i]+=h[i]; pp[j]+=h[j]; pm[i]+=h[i]; pm[j]-=h[j];
mp[i]-=h[i]; mp[j]+=h[j]; mm[i]-=h[i]; mm[j]-=h[j];
H[i][j] = (f(pp)-f(pm)-f(mp)+f(mm))/(4*h[i]*h[j]);
}
let seB1 = NaN;
try{
const inv = invert3(H);
if(inv && inv[1][1]>0) seB1 = Math.sqrt(inv[1][1]);
}catch(e){}
const nll0 = stat && isFinite(stat.nll) ? stat.nll : nsGpdNLL(b0h, 0, xih, ts, ys);
const D = 2*(nll0 - best.f);
return {b0:b0h, b1:b1h, xi:xih, nll:best.f, nll0:nll0, D:D, p:chi2p1(Math.max(0,D)),
seB1:seB1, n:ys.length, sigmaAt:function(t){ return Math.exp(b0h + b1h*t); }};
}
function invert3(m){
const a=m[0][0],b=m[0][1],c=m[0][2],d=m[1][0],e=m[1][1],f=m[1][2],g=m[2][0],h=m[2][1],i=m[2][2];
const A=e*i-f*h, B=-(d*i-f*g), C=d*h-e*g;
const det=a*A+b*B+c*C; if(!(Math.abs(det)>1e-14)) return null;
return [[A/det, -(b*i-c*h)/det, (b*f-c*e)/det],
[B/det, (a*i-c*g)/det, -(a*f-c*d)/det],
[C/det, -(a*h-b*g)/det, (a*e-b*d)/det]];
}
/* Effective return level of year t: the level whose annual exceedance
probability equals 1/T IN THAT SINGLE YEAR. It is a snapshot, not a
frequency over a long record. */
function effectiveRL(u, sigmaT, xi, lambdaT, T){
return returnLevel(u, sigmaT, xi, lambdaT, T, "poisson");
}
/* Cumulative risk of exceeding level z at least once between years y0..y0+N-1
under a changing rate/scale: 1 - exp(-sum_t Lambda_t(z)) */
function nsCumulativeRisk(z, u, xi, years, sigmas, lambdas){
let sum = 0;
for(let i=0;i0) || !(lam>0)) continue;
const y = z-u; if(y<=0){ sum += lam; continue; }
const q = (Math.abs(xi)<1e-8) ? Math.exp(-y/s) : Math.pow(Math.max(1e-300, 1+xi*y/s), -1/xi);
sum += lam*q;
}
return {risk: 1-Math.exp(-sum), expected: sum};
}
/* Design-life level: the level whose cumulative risk over the design window
equals the target (e.g. 0.10). Solved by bisection. */
function nsDesignLevel(target, u, xi, years, sigmas, lambdas){
let lo = u, hi = u + 50*Math.max.apply(null, sigmas.concat([1]));
if(nsCumulativeRisk(hi,u,xi,years,sigmas,lambdas).risk > target) return {z:NaN, capped:true};
for(let it=0; it<200; it++){
const mid = 0.5*(lo+hi);
if(nsCumulativeRisk(mid,u,xi,years,sigmas,lambdas).risk > target) lo = mid; else hi = mid;
}
return {z:0.5*(lo+hi), capped:false};
}
/* Split-sample check: fit the two halves of the record separately.
A model-free cross-check on whichever trend the parametric fits report. */
function splitHalves(ts, ys, estimator){
if(ys.length<30) return null;
const idx = ts.map((t,i)=>i).sort((a,b)=>ts[a]-ts[b]);
const half = Math.floor(idx.length/2);
const y1 = idx.slice(0,half).map(i=>ys[i]), y2 = idx.slice(half).map(i=>ys[i]);
const f1 = fitGPD(y1, estimator||"mle"), f2 = fitGPD(y2, estimator||"mle");
if(!f1||!f2) return null;
return {first:f1, second:f2, n1:y1.length, n2:y2.length,
tMid: ts[idx[half]], mean1: mean(y1), mean2: mean(y2)};
}
/* Full nonstationarity assessment for one POT result. */
function assessTrend(res, st, cfg){
if(!res || !res.clusters || res.clusters.length<20) return null;
const MSY = 365.2425*MS_DAY;
const tYear = function(ms){ return new Date(ms).getUTCFullYear() + (ms - Date.UTC(new Date(ms).getUTCFullYear(),0,1))/MSY; };
const tsRaw = res.clusters.map(c=>tYear(c.tpeak));
const t0 = mean(tsRaw);
const ts = tsRaw.map(t=>t-t0);
const ys = res.clusters.map(c=>c.excess);
const peaks = res.clusters.map(c=>c.qmax);
// annual event counts, with partial-year exposure from the data span
const y1 = Math.floor(Math.min.apply(null,tsRaw)), y2 = Math.floor(Math.max.apply(null,tsRaw));
const years=[], counts=[], expo=[];
const covered = coverageByYear(st, y1, y2);
for(let y=y1; y<=y2; y++){
const e = covered[y]!=null ? covered[y] : 1;
if(e < 0.25) continue; // a year with almost no data says nothing
years.push(y); expo.push(e);
counts.push(tsRaw.filter(t=>Math.floor(t)===y).length);
}
if(years.length<8) return null;
const tsY = years.map(y=>y+0.5-t0);
const rate = poissonTrendFit(tsY, counts, expo);
const statFit = res.fit;
const ns = nsGpdFit(ts, ys, statFit);
const mkCount = mannKendall(counts.map((c,i)=>c/expo[i]));
const mkExcess = mannKendall(ys);
const mkPeak = mannKendall(peaks);
const sen = senSlope(tsRaw, peaks);
const split = splitHalves(ts, ys, cfg.estimator);
return {t0:t0, ts:ts, tsRaw:tsRaw, ys:ys, peaks:peaks, years:years, counts:counts, expo:expo,
rate:rate, ns:ns, stat:statFit, u:res.u, lambda:res.lambda,
mkCount:mkCount, mkExcess:mkExcess, mkPeak:mkPeak, senPeak:sen, split:split,
yearFrom:years[0], yearTo:years[years.length-1]};
}
/* Fraction of each calendar year actually covered by observations. */
function coverageByYear(st, y1, y2){
const out = {};
const step = (st.qc && st.qc.stepH>0) ? st.qc.stepH*MS_HOUR : MS_DAY;
for(const d of st.series){
const y = new Date(d.t).getUTCFullYear();
out[y] = (out[y]||0) + step;
}
for(let y=y1;y<=y2;y++){
const full = Date.UTC(y+1,0,1)-Date.UTC(y,0,1);
out[y] = out[y]!=null ? Math.min(1, out[y]/full) : 0;
}
return out;
}
/* ---------- Goodness of fit ---------- */
function gofStats(y, sigma, xi){
const s = sortedCopy(y), n = s.length;
const u = s.map(v=>Math.min(1-1e-12, Math.max(1e-12, gpdCDF(v, sigma, xi))));
let A=0, W=0, Dp=0, Dm=0;
for(let i=1;i<=n;i++){
A += (2*i-1)*(Math.log(u[i-1]) + Math.log(1-u[n-i]));
const d = u[i-1] - (2*i-1)/(2*n); W += d*d;
Dp = Math.max(Dp, i/n - u[i-1]); Dm = Math.max(Dm, u[i-1] - (i-1)/n);
}
return {AD: -n - A/n, CvM: W + 1/(12*n), KS: Math.max(Dp,Dm), n:n};
}
function gofParametricBootstrap(y, estimator, B, rnd){
const fit = fitGPD(y, estimator, {noSE:true}); if(!fit) return null;
const obs = gofStats(y, fit.sigma, fit.xi);
const n = y.length; let geAD=0, geCvM=0, ok=0;
for(let b=0;b= obs.AD) geAD++; if(g2.CvM >= obs.CvM) geCvM++;
}
if(!ok) return {obs:obs, pAD:NaN, pCvM:NaN, fit:fit};
return {obs:obs, pAD:(geAD+1)/(ok+1), pCvM:(geCvM+1)/(ok+1), fit:fit, B:ok};
}
/* ---------- Mean residual life ---------- */
function mrlCurve(values, us){
const out=[];
for(const u of us){
const ex=[]; for(const v of values) if(v>u) ex.push(v-u);
if(ex.length<5){ out.push({u:u, e:NaN, lo:NaN, hi:NaN, n:ex.length}); continue; }
const m = mean(ex), s = sd(ex), se = s/Math.sqrt(ex.length);
out.push({u:u, e:m, lo:m-1.96*se, hi:m+1.96*se, n:ex.length});
}
return out;
}
/* ---------- Full POT pipeline for one configuration ---------- */
function runPOT(st, cfg){
// cfg: {thrType, thrPct, thrAbs, sepHours, estimator, rlMode, Ts}
const series = st.series;
const sortedQ = st.sortedQ;
const u = (cfg.thrType==="abs" && isFinite(cfg.thrAbs)) ? cfg.thrAbs : quantile(sortedQ, cfg.thrPct/100);
const clusters = decluster(series, u, cfg.sepHours);
const excess = clusters.map(c=>c.excess).filter(v=>v>0);
const years = st.qc.effYears;
const lambda = years>0 ? clusters.length/years : NaN;
const res = {u:u, thrPct:cfg.thrPct, clusters:clusters, excess:excess, n:clusters.length, lambda:lambda, years:years,
raw:rawExceedances(series,u).length, fit:null, rl:{}, mode:cfg.rlMode||"poisson"};
if(excess.length>=5){
const fit = fitGPD(excess, cfg.estimator||"mle");
res.fit = fit;
if(fit){
const Ts = cfg.Ts || [2,5,10,20,25,50,100,200,500];
for(const T of Ts) res.rl[T] = returnLevel(u, fit.sigma, fit.xi, lambda, T, res.mode);
res.gof = gofStats(excess, fit.sigma, fit.xi);
res.upperBound = fit.xi<0 ? u - fit.sigma/fit.xi : Infinity;
}
}
return res;
}
/* ---------- Threshold scan ---------- */
function thresholdScan(st, cfg, from, to, step){
const rows=[], sortedQ = st.sortedQ;
for(let p=from; p<=to+1e-9; p+=step){
const c = Object.assign({}, cfg, {thrType:"pct", thrPct:p});
const r = runPOT(st, c);
if(!r.fit) { rows.push({p:p, u:r.u, n:r.n, lambda:r.lambda, xi:NaN, sigma:NaN, mscale:NaN, seXi:NaN, seMs:NaN, AD:NaN, rl:{}, ok:false}); continue; }
const xi=r.fit.xi, sg=r.fit.sigma, ms = sg - xi*r.u;
let seMs = NaN;
if(r.fit.cov){ const v = r.fit.cov[0][0] + r.u*r.u*r.fit.cov[1][1] - 2*r.u*r.fit.cov[0][1]; seMs = v>0?Math.sqrt(v):NaN; }
rows.push({p:p, u:r.u, n:r.n, lambda:r.lambda, xi:xi, sigma:sg, mscale:ms, seXi:r.fit.se[1], seMs:seMs,
AD:r.gof?r.gof.AD:NaN, CvM:r.gof?r.gof.CvM:NaN, rl:r.rl, ok:true});
}
return rows;
}
/* Stable-band search.
Read the parameter-stability plots the way the literature does: start at the highest
threshold that still has enough events and walk DOWN while each new point stays
statistically consistent with the running weighted mean of xi (and with sigma*).
The drifting low-threshold region is excluded because its small standard errors make
the drift detectable. */
function chi2Crit(k, z){
if(k<=0) return 0;
const a = 2/(9*k);
return k*Math.pow(1 - a + z*Math.sqrt(a), 3);
}
/* Homogeneity (Cochran-Q style) walk from the highest threshold downwards.
A band is kept only while the shape estimates inside it stay statistically
consistent with one common value; adding a discrepant low threshold inflates
Q and stops the walk, which prevents the band from drifting down to the
lowest grid point. Reported as a SUGGESTION, never as a proven optimum. */
function bandQ(rows, key, seKey){
let sw=0, swx=0;
for(const r of rows){ const se=r[seKey]; if(!(isFinite(se)&&se>0)) return null; const w=1/(se*se); sw+=w; swx+=r[key]*w; }
const mu = swx/sw; let Q=0;
for(const r of rows){ const se=r[seKey]; Q += (r[key]-mu)*(r[key]-mu)/(se*se); }
return {mu:mu, Q:Q, se:Math.sqrt(1/sw)};
}
function findStableBand(rows, minEvents, minLen){
minLen = minLen||4;
const ok = rows.filter(r=>r.ok && r.n>=minEvents && isFinite(r.xi) && isFinite(r.seXi) && r.seXi>0);
if(ok.length < minLen) return null;
const desc = ok.slice().sort((a,b)=>b.p-a.p);
// Anchor: the highest thresholds are the least biased but the noisiest, so the
// reference shape is their inverse-variance weighted mean. It is FROZEN, which
// stops the band from drifting down to the lowest grid point.
const nRef = Math.max(3, Math.min(desc.length, Math.ceil(desc.length/3)));
const ref = bandQ(desc.slice(0,nRef), "xi", "seXi");
if(!ref) return null;
const refM = bandQ(desc.slice(0,nRef).filter(r=>isFinite(r.mscale)&&isFinite(r.seMs)&&r.seMs>0), "mscale", "seMs");
let acc = null, usedZ = NaN;
for(const z of [1.645, 2.33, 3.09]){
const a = desc.slice(0, nRef);
for(let i=nRef;i tolX) break;
if(refM && isFinite(r.mscale) && isFinite(r.seMs) && r.seMs>0){
const tolM = z*Math.max(r.seMs, 0.35*refM.se);
if(Math.abs(r.mscale - refM.mu) > tolM) break;
}
a.push(r);
}
if(a.length >= minLen){ acc = a; usedZ = z; break; }
if(!acc) acc = a;
}
if(!acc || acc.length<2) return null;
const band = acc.slice().sort((a,b)=>a.p-b.p);
const qx = bandQ(band, "xi", "seXi");
const mu = qx ? qx.mu : mean(band.map(r=>r.xi));
// Bias-variance: inside the stable band take the LOWEST threshold that is still
// close to the band's weighted shape, so the fit keeps as many events as possible.
let sel = band[band.length-1];
for(const r of band){ if(Math.abs(r.xi-mu) <= 1.5*r.seXi){ sel = r; break; } }
return {from:band[0], to:band[band.length-1], rows:band, selected:sel, xiWeighted:mu,
xiSe: qx?qx.se:NaN, Q: qx?qx.Q:NaN, z:usedZ, score:Math.abs(mu), zX:NaN, zM:NaN};
}
function tsi(values){
const v = values.filter(isFinite); if(v.length<2) return NaN;
const med = median(v);
return med!==0 ? (Math.max.apply(null,v)-Math.min.apply(null,v))/Math.abs(med)*100 : NaN;
}
/* ---------- Bootstrap (chunked, cancellable) ---------- */
async function bootstrapPOT(res, cfg, opts){
const B = opts.B||1000, rnd = mulberry32(opts.seed||42), Ts = opts.Ts, rateUnc = !!opts.rateUnc;
const ex = res.excess, n = ex.length, years = res.years, mode = res.mode;
const store = {}; Ts.forEach(T=>store[T]=[]);
const init = res.fit ? [res.fit.sigma, res.fit.xi] : null;
const xis=[], sgs=[], lams=[];
let done=0, used=0;
for(let b=0;bsetTimeout(r,0));
}
}
if(opts.onProgress) opts.onProgress(B,B);
const ci = {};
for(const T of Ts){ const s = sortedCopy(store[T]);
ci[T] = {lo:quantile(s,0.025), med:quantile(s,0.5), hi:quantile(s,0.975), n:s.length, draws:store[T]}; }
return {ci:ci, xis:xis, sigmas:sgs, lambdas:lams, B:used, cancelled:false};
}
"use strict";
/* ============================================================
State, ingestion, QC
============================================================ */
const S = {
stations: {}, // id -> station
order: [],
active: null,
cfg: { thrType:"pct", thrPct:95, thrAbs:NaN, sepHours:72, estimator:"mle", rlMode:"poisson",
Ts:[2,5,10,20,25,50,100,200,500], B:1000, seed:42, rateUnc:true,
grid:{from:90,to:99.3,step:0.3,minEvents:25} },
res: null, scan:null, band:null, boot:null, methods:null, heat:null, multi:null, gof:null, trend:null, trendOut:null,
raw:null, report:null
};
let DL = null; // downloads capability
(async function(){ try{ if(window.claude && claude.use) DL = await claude.use("downloads"); }catch(e){} })();
function toast(msg, ms){
const el = document.getElementById("toast");
el.textContent = msg; el.classList.remove("hidden");
clearTimeout(el._t); el._t = setTimeout(()=>el.classList.add("hidden"), ms||3200);
}
async function saveFile(filename, data){
if(!DL){ toast(t("ui.dlno")); return false; }
try{ await DL.save({filename:filename, data:data}); toast(t("ui.dlok")); return true; }
catch(err){ toast(err && err.code==="declined" ? t("ui.dldecl") : (err && err.message) || t("ui.dlno")); return false; }
}
function csvEscape(v){ if(v==null) return ""; const s=String(v); return /[",;\n]/.test(s)? '"'+s.replace(/"/g,'""')+'"' : s; }
function toCSV(rows){ return rows.map(r=>r.map(csvEscape).join(",")).join("\n"); }
function isoDate(ms){ const d=new Date(ms); return d.toISOString().slice(0,19).replace("T"," "); }
function isoDay(ms){ return new Date(ms).toISOString().slice(0,10); }
/* ---------- date parsing ---------- */
function parseDate(v){
if(v==null||v==="") return NaN;
if(v instanceof Date) return v.getTime();
if(typeof v === "number"){
if(v>20000000) return v; // already ms
if(v>1000 && v<80000){ // Excel serial
return Math.round((v-25569)*MS_DAY);
}
return NaN;
}
const s = String(v).trim();
let m = s.match(/^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})([ T](\d{1,2}):(\d{2})(:(\d{2}))?)?/);
if(m) return Date.UTC(+m[1], +m[2]-1, +m[3], +(m[5]||0), +(m[6]||0), +(m[8]||0));
m = s.match(/^(\d{1,2})[-/.](\d{1,2})[-/.](\d{4})([ T](\d{1,2}):(\d{2})(:(\d{2}))?)?/);
if(m){ let d=+m[1], mo=+m[2]; if(mo>12){ const k=d; d=mo; mo=k; }
return Date.UTC(+m[3], mo-1, d, +(m[5]||0), +(m[6]||0), +(m[8]||0)); }
const p = Date.parse(s); return isNaN(p)? NaN : p;
}
function parseNum(v){
if(v==null||v==="") return NaN;
if(typeof v==="number") return v;
let s = String(v).trim().replace(/\s/g,"");
if(/,\d{1,3}$/.test(s) && !/\./.test(s)) s = s.replace(",", ".");
else s = s.replace(/,/g,"");
const x = parseFloat(s); return isFinite(x)? x : NaN;
}
/* ---------- QC ---------- */
function buildQC(series){
const n = series.length;
const q = series.map(d=>d.q);
const sq = sortedCopy(q);
const diffs=[]; for(let i=1;i0.5 && stepH<170) res="hx";
const span = n>1 ? series[n-1].t - series[0].t : 0;
const expected = step>0 ? Math.round(span/step)+1 : n;
const coverage = expected>0 ? Math.min(1, n/expected) : 1;
const gaps=[]; for(let i=1;i step*2.5) gaps.push({from:series[i-1].t, to:series[i].t, days:d/MS_DAY}); }
gaps.sort((a,b)=>b.days-a.days);
const spanYears = span/(MS_DAY*YEAR_DAYS);
return {
n:n, start:series[0].t, end:series[n-1].t, stepH:stepH, res:res,
spanYears:spanYears, effYears: Math.max(0.5, spanYears*coverage), coverage:coverage,
expected:expected, missingPct:(1-coverage)*100,
gaps:gaps, gapCount:gaps.length, maxGapDays: gaps.length? gaps[0].days : 0,
max:sq[n-1], min:sq[0], med:quantile(sq,0.5), mean:mean(q),
neg:q.filter(v=>v<0).length, zero:q.filter(v=>v===0).length
};
}
function addStation(id, name, rows, meta){
// rows: [{t,q}]
const seen = new Set(); let dup=0, neg=0;
const clean=[];
rows.sort((a,b)=>a.t-b.t);
for(const r of rows){
if(!isFinite(r.t)||!isFinite(r.q)) continue;
if(seen.has(r.t)){ dup++; continue; }
if(r.q<0){ neg++; continue; }
seen.add(r.t); clean.push({t:r.t,q:r.q});
}
if(clean.length<20) return null;
const st = {id:id, name:name||id, series:clean, sortedQ:sortedCopy(clean.map(d=>d.q)),
meta:meta||{}, dup:dup, negDropped:neg};
st.qc = buildQC(clean);
if(!S.stations[id]) S.order.push(id);
S.stations[id] = st;
return st;
}
function activeStation(){ return S.active ? S.stations[S.active] : null; }
/* ---------- file reading ---------- */
function readTable(file, cb){
const name = file.name.toLowerCase();
const rd = new FileReader();
if(name.endsWith(".csv")||name.endsWith(".txt")){
rd.onload = function(){
const out = Papa.parse(rd.result, {skipEmptyLines:true, dynamicTyping:false});
cb({sheets:{"CSV":out.data}, names:["CSV"]});
};
rd.readAsText(file);
} else {
rd.onload = function(){
const wb = XLSX.read(new Uint8Array(rd.result), {type:"array", cellDates:true});
const sheets={}, names=[];
wb.SheetNames.forEach(function(nm){
const arr = XLSX.utils.sheet_to_json(wb.Sheets[nm], {header:1, raw:true, defval:null});
if(arr && arr.length>1){ sheets[nm]=arr; names.push(nm); }
});
cb({sheets:sheets, names:names});
};
rd.readAsArrayBuffer(file);
}
}
function detectFormat(arr){
const head = (arr[0]||[]).map(h=>String(h==null?"":h).trim().toLowerCase());
const body = arr.slice(1, Math.min(arr.length, 60));
const dateIdx = head.findIndex(h=>/date|time|tarih|zaman|datetime|day/.test(h));
const hasQ = head.some(h=>/discharge|flow|debi|akim|akım|q_?m3|value|streamflow/.test(h));
const hasStation = head.some(h=>/station|istasyon|gauge|site|id/.test(h));
let numericCols=0;
for(let c=0;c3 && ok/tot>0.85) numericCols++;
}
const wide = (dateIdx>=0 && !hasStation && numericCols>=2) || (dateIdx>=0 && !hasQ && numericCols>=2);
return {head:head, dateIdx:dateIdx, wide:wide, numericCols:numericCols};
}
function guessCol(head, patterns){
for(const p of patterns){ const i = head.findIndex(h=>p.test(h)); if(i>=0) return i; }
return -1;
}
/* ---------- synthetic demo ----------
Baseflow: seasonal AR(1) in log space.
Floods: seasonal Poisson arrivals, peak magnitude = uTrue + GPD(sigmaTrue, xiTrue),
shaped into a rising limb and an exponential recession.
The generator therefore has a KNOWN tail, which is what makes it useful for teaching.
------------------------------------- */
function synthStation(id, name, o){
const rnd = mulberry32(o.seed);
const norm = function(){ let u=0,v=0; do{u=rnd();}while(u<=0); v=rnd();
return Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v); };
const nDays = Math.round(o.years*YEAR_DAYS), start = Date.UTC(o.startYear,0,1);
const q = new Array(nDays);
let x = 0;
for(let i=0;i=nDays) continue;
const shape = k<0 ? Math.exp(-2*Math.pow(k/o.rise,2)) : Math.exp(-k/o.rec);
if(peak>q[j]) q[j] = q[j] + (peak-q[j])*shape;
}
i += Math.max(1, Math.round(o.rec));
}
}
const rows = [];
for(let i=0;i redraw fn
function plot(id, traces, layout, cfg){
const el = document.getElementById(id); if(!el) return;
const base = {
paper_bgcolor:"rgba(0,0,0,0)", plot_bgcolor:"rgba(0,0,0,0)",
font:{family:'"IBM Plex Sans", system-ui, sans-serif', size:11.5, color:cssv("--ink-2")},
margin:{l:58,r:16,t:16,b:44}, hovermode:"closest",
xaxis:{gridcolor:cssv("--line-soft"), zerolinecolor:cssv("--line"), linecolor:cssv("--line")},
yaxis:{gridcolor:cssv("--line-soft"), zerolinecolor:cssv("--line"), linecolor:cssv("--line")},
legend:{orientation:"h", y:1.12, x:0, font:{size:10.5}},
showlegend:false
};
const lay = Object.assign({}, base, layout||{});
lay.xaxis = Object.assign({}, base.xaxis, (layout&&layout.xaxis)||{});
lay.yaxis = Object.assign({}, base.yaxis, (layout&&layout.yaxis)||{});
Plotly.react(el, traces, lay, Object.assign({displayModeBar:false, responsive:true}, cfg||{}));
}
function C(){ return {water:cssv("--water"), thr:cssv("--thr"), event:cssv("--event"), ink:cssv("--ink"),
muted:cssv("--muted"), line:cssv("--line"), soft:cssv("--water-soft"), ok:cssv("--ok")}; }
/* ============================================================
Navigation
============================================================ */
const PANELS = ["home","data","qc","pot","thr","gpd","cmp","risk","multi","trend","report","method"];
function go(p){
PANELS.forEach(function(k){ const el=document.getElementById("p-"+k); if(el) el.classList.toggle("on", k===p); });
document.querySelectorAll(".navitem").forEach(n=>n.classList.toggle("on", n.dataset.go===p));
window.scrollTo({top:0, behavior:"instant"});
S.panel = p;
renderPanel(p);
}
function renderPanel(p){
switch(p){
case "home": renderHero(); break;
case "data": renderDataPanel(); break;
case "qc": renderQC(); break;
case "pot": renderPOTPanel(); break;
case "thr": renderThrPanel(); break;
case "gpd": renderGPDPanel(); break;
case "cmp": renderCmpPanel(); break;
case "risk": renderRiskPanel(); break;
case "multi": renderMultiPanel(); break;
case "trend": renderTrendPanel(); break;
case "report": renderReportPanel(); break;
case "method": renderMethod(); break;
}
}
function needData(container){
const el = document.getElementById(container);
if(el) el.innerHTML = '
'+t("ui.none")+'
';
}
/* ============================================================
Home hero — draggable threshold on a synthetic hydrograph
============================================================ */
const HERO = {series:null, thr:0.62};
function heroSeries(){
if(HERO.series) return HERO.series;
const rnd = mulberry32(7), n=240, a=[]; let b=0.28;
for(let i=0;i=n) break;
a[idx] = Math.max(a[idx], a[idx] + mag*Math.exp(-Math.pow((k-dur*0.4)/(dur*0.5),2))); }
HERO.series = a; return a;
}
function renderHero(){
const svg = document.getElementById("heroSvg"); if(!svg) return;
const W=900, H=330, pad={l:30,r:20,t:110,b:28};
const a = heroSeries(), n=a.length, mx=Math.max.apply(null,a)*1.08;
const X = i => pad.l + i*(W-pad.l-pad.r)/(n-1);
const Y = v => H-pad.b - (v/mx)*(H-pad.t-pad.b);
const thrV = HERO.thr*mx;
let d = "M"+X(0)+","+Y(a[0]);
for(let i=1;ithrV){ if(cur && i-cur.last<=4){ cur.last=i; if(a[i]>cur.max){cur.max=a[i];cur.idx=i;} }
else { if(cur) ev.push(cur); cur={last:i, max:a[i], idx:i}; } } }
if(cur) ev.push(cur);
const dots = ev.map(e=>'').join("");
const area = d + "L"+X(n-1)+","+Y(0)+"L"+X(0)+","+Y(0)+"Z";
svg.innerHTML =
''+
''+
''+
''+
dots+
''+t("home.heroThr")+''+
''+ev.length+' '+t("home.heroCount")+'';
const drag = svg.querySelector(".thrdrag");
function move(clientY){
const r = svg.getBoundingClientRect();
const yy = (clientY - r.top)/r.height*H;
const v = (H-pad.b-yy)/(H-pad.t-pad.b)*mx;
HERO.thr = Math.max(0.15, Math.min(0.98, v/mx));
renderHero();
}
function down(e){ e.preventDefault();
const mv = ev2 => move(ev2.touches? ev2.touches[0].clientY : ev2.clientY);
const up = ()=>{ window.removeEventListener("mousemove",mv); window.removeEventListener("mouseup",up);
window.removeEventListener("touchmove",mv); window.removeEventListener("touchend",up); };
window.addEventListener("mousemove",mv); window.addEventListener("mouseup",up);
window.addEventListener("touchmove",mv,{passive:false}); window.addEventListener("touchend",up);
}
drag.addEventListener("mousedown", down); drag.addEventListener("touchstart", down, {passive:false});
svg.addEventListener("click", function(e){ if(e.target===drag) return; });
const chain = document.getElementById("homeChain");
if(chain) chain.innerHTML = ["Data","QC","POT","Declustering","Threshold diagnostics","4-method","GPD","Bootstrap",
"Threshold sensitivity","Declustering sensitivity","Return levels","Design-life risk","Multi-station","Report"]
.map(s=>""+s+"").join("");
}
/* ============================================================
1 · Data Studio
============================================================ */
let PENDING = null; // parsed workbook awaiting mapping
function renderDataPanel(){
const card = document.getElementById("loadedCard");
const tb = document.getElementById("stationTable");
if(!S.order.length){ card.hidden = true; return; }
card.hidden = false;
let h = "
"+t("multi.colSt")+"
"+t("qc.obs")+"
"+t("qc.period")+
"
"+t("qc.len")+"
"+t("qc.res")+"
"+t("qc.max")+
"
"+t("qc.missing")+"
";
for(const id of S.order){
const st = S.stations[id], q = st.qc;
h += "
'; return; }
drawTrend();
}
function runTrend(){
const st = activeStation(); if(!st){ toast(t("ui.none")); return; }
const r = S.res;
toast(t("ui.working"));
setTimeout(function(){
S.trend = assessTrend(r, st, S.cfg);
// the design window should start just after the record unless the user set it
if(S.trend && !S.nsY0Touched){
const el = document.getElementById("nsY0"); if(el) el.value = String(S.trend.yearTo+1);
}
if(!S.trend){
document.getElementById("nsStats").innerHTML="";
document.getElementById("nsVerdict").innerHTML = '
'+t("ns.need")+'
';
document.getElementById("nsTable").innerHTML=""; document.getElementById("nsSplit").innerHTML="";
["plotNsRate","plotNsMag","plotNsEff","plotNsRisk"].forEach(id=>{ const e=document.getElementById(id); if(e) e.innerHTML=""; });
toast(t("ns.need")); return;
}
drawTrend(); toast(t("ui.done"));
}, 30);
}
function pfmt(p){
if(!isFinite(p)) return "–";
if(p < 0.0001) return LANG==="tr" ? "< 0,0001" : "< 0.0001";
return num(p,4);
}
function pEq(p){
if(!isFinite(p)) return "= –";
if(p < 0.0001) return LANG==="tr" ? "< 0,0001" : "< 0.0001";
return "= "+num(p,4);
}
function nsInputs(){
const g = id => parseFloat((document.getElementById(id)||{}).value);
const T = g("nsT")||100, N = Math.max(1, g("nsN")||50), risk = Math.min(99, Math.max(1, g("nsRisk")||10));
let y0 = g("nsY0"); if(!isFinite(y0)) y0 = new Date().getUTCFullYear();
return {T:T, N:Math.round(N), risk:risk/100, y0:Math.round(y0)};
}
function drawTrend(){
const tr = S.trend, c = C(), r = S.res; if(!tr) return;
const inp = nsInputs(), T = inp.T;
const xi = tr.ns ? tr.ns.xi : (tr.stat? tr.stat.xi : NaN);
const rateCh = (Math.exp(tr.rate.b)-1)*100; // % per year
const scaleCh = tr.ns ? (Math.exp(tr.ns.b1)-1)*100 : NaN;
/* ---- summary tiles ---- */
const stat=(k,v,u)=>'
"+(LANG==="tr"
? "Bu kayıt için trend analizi çalıştırılmadı. 9. modülde çalıştırılırsa sonuçlar burada raporlanır."
: "The trend analysis was not run for this record. Run it in module 9 and the outcome is reported here.")+"
"+(LANG==="tr"
? "Eşik üstü değerler (peaks-over-threshold) yaklaşımı kullanılmış, aşımlara genelleştirilmiş Pareto dağılımı uydurulmuştur."
: "A peaks-over-threshold approach was used and a generalized Pareto distribution was fitted to the excesses.")+"
"+t("risk.bootDone",{b:boot.B})+" "+(LANG==="tr"?"Yeniden örnekleme bağımsız olaylar üzerinden yapılmıştır":"Resampling was performed on the independent events")+
(S.cfg.rateUnc? (LANG==="tr"?" ve olay hızı belirsizliği Poisson varsayımıyla dahil edilmiştir.":" and event-rate uncertainty was included under a Poisson assumption."):".")+"
");
h += S1(12, trendReportSection());
h += S1(13, "
"+t("rep.limits").map(x=>"
"+x+"
").join("")+"
");
h += S1(14, "
"+(LANG==="tr"
? "Eşik aşımları runs yöntemiyle bağımsızlaştırılmış, aşımlar için GPD parametreleri "+S.cfg.estimator.toUpperCase()+" ile tahmin edilmiş, tekerrür debileri "+
(S.cfg.rlMode==="poisson"?"Poisson-GPD":"klasik POT")+" formülasyonuyla hesaplanmıştır. Güven aralıkları "+(boot?boot.B:S.cfg.B)+" tekrarlı bootstrap ile üretilmiştir."
: "Exceedances were declustered with the runs method, GPD parameters were estimated by "+S.cfg.estimator.toUpperCase()+
", and return levels follow the "+(S.cfg.rlMode==="poisson"?"Poisson-GPD":"classic POT")+" formulation. Intervals come from a "+(boot?boot.B:S.cfg.B)+"-replicate bootstrap.")+
"
'+(tr?"1 · Eşik aşımı yaklaşımı":"1 · The peaks-over-threshold approach")+'
'+
'
'+(tr?"Yıllık maksimumlar yerine, belirli bir eşiği aşan tüm gözlemler kullanılır. Bu, veriyi daha verimli kullanır ancak iki ek karar gerektirir: eşik ve olayların bağımsızlaştırılması."
:"Instead of annual maxima, every observation above a threshold is used. This uses the record more efficiently but forces two extra decisions: the threshold and how events are made independent.")+'
'+(tr?"Ardışık aşımlar arasında seçilen ayırma aralığından kısa bir boşluk varsa bunlar aynı taşkın olayına aittir ve yalnızca küme maksimumu tutulur. Ayırma aralığı zaman çözünürlüğünden bağımsız olarak kullanıcı tarafından seçilir; sistem kesin bir kural dayatmaz."
:"Consecutive exceedances separated by less than the chosen interval belong to one flood event, and only the cluster maximum is kept. The interval is chosen by the user rather than fixed by a rule.")+'
'+(tr?"ξ ≈ 0 civarında ayrı bir üstel form kullanılır; aksi halde optimizasyon kararsızlaşır. Başlangıç değerleri olasılık ağırlıklı momentlerden (PWM) alınır. Standart hatalar gözlenen bilgi matrisinden hesaplanır."
:"A separate exponential form is used near ξ ≈ 0, otherwise the optimizer becomes unstable. Starting values come from probability-weighted moments; standard errors from the observed information matrix.")+'
'+(tr?"İkinci form, yıllık maksimumun aşılma olasılığını tam olarak 1/T yapar. Büyük T için −ln(1−1/T) ≈ 1/T olduğundan iki form birbirine yakınsar."
:"The second form makes the annual maximum's exceedance probability exactly 1/T. For large T the two coincide because −ln(1−1/T) ≈ 1/T.")+'
'+
'
'+(tr?"5 · Belirsizlik":"5 · Uncertainty")+'
'+
'
'+(tr?"Bağımsız olaylar yeniden örneklenerek her tekrarda GPD yeniden uydurulur; olay hızı istenirse Poisson dağılımıyla birlikte örneklenir. %2.5 ve %97.5 yüzdelikleri güven aralığını verir. Eşik kaynaklı yayılım ayrı bir belirsizlik kaynağıdır ve bootstrap aralığıyla karıştırılmamalıdır."
:"Independent events are resampled and the GPD refitted for each replicate; the event rate can be resampled from a Poisson distribution. The 2.5% and 97.5% percentiles form the interval. Threshold-induced spread is a separate source and should not be merged with the bootstrap interval.")+'
'+(tr?"TSI ve ilgili ölçütler bu platforma özgü karar destek tanılarıdır; formal istatistiksel testler veya yerleşik hidrolojik indeksler olarak sunulmamalıdır."
:"TSI and related measures are decision-support diagnostics specific to this platform; they are not formal statistical tests or established hydrological indices.")+'