omeryanbas.com

Ömer Yanbaş

Genel Müdür, Ticofab Yazılım

MesajlaşmaVeri

Tek bir Türkçe karakter mesaj maliyetini neden ikiye katlayabilir

Kısa mesaj ya 160 karakterlik 7 bitlik alfabeyle ya da 70 karakterlik 16 bitlik kodlamayla gider. Tek bir harf tüm mesajı ve faturayı değiştirir.

130 karakterlik bir mesaj tek birim olarak gider. Biri metni düzenler, bir kelimeyi değiştirir ve aynı kampanya iki katına çıkar. Başka hiçbir şey değişmemiştir: kitle aynı, uzunluk neredeyse aynı, sağlayıcı aynı. Değişen kelimenin içinde 7 bitlik alfabede bulunmayan bir harf vardır ve o tek harf mesajın tamamını, yalnızca 70 karakterin sığdığı 16 bitlik kodlamaya taşımıştır.

Gerçekte ne oluyor

Kısa mesaj 140 baytlık bir yük olarak taşınır. Bu baytları doldurmanın iki yolu vardır ve aralarındaki fark küçük değildir:

KodlamaTek mesajda karakterBölündüğünde parça başına
7 bitlik GSM alfabesi160153
16 bitlik (UTF-16)7067

7 bitlik alfabe sekiz karakteri yedi bayta paketler, 160 karakterin 140 bayta sığması bundandır. İçinde ASCII, birkaç para birimi simgesi, birkaç Yunan büyük harfi ve belirli bir aksanlı harf kümesi vardır. Geri kalan her şey, birim başına iki bayt tutan ve bu yüzden 70 karakter alan 16 bitlik kodlamaya düşer.

Parayı yakan kural şudur: kodlama mesajın tamamı için seçilir. Karışık gönderim diye bir şey yoktur. Tablonun dışında tek bir karakter varsa metindeki diğer bütün karakterler de yeniden kodlanır.

Türkçe için durum beklenenden keskindir. Temel tabloda Ö, ö, Ü, ü ve büyük Ç vardır. Küçük ç yoktur; ğ, ı, İ ve ş harflerinin hiçbir hâli yoktur. Yani ÇAY 7 bitlik bir kelimedir, çay değildir. Elle yazılmış bir karakter sayacının faturayla anlaşamamasının tipik sebebi bu asimetridir.

İkinci maliyet daha sessizdir. On karakter bir uzantı tablosunda durur ve kaçış karakteri artı karakterin kendisi olarak gönderilir, yani 7 bitlik alfabenin içinde bile ikişer birim yer kaplar:

^  {  }  \  [  ~  ]  |  €  ve form feed

Şablonunda üç çift süslü parantez olan bir mesaj, göründüğünden altı birim uzundur. 155 karakterde bu fark tam olarak parça sınırıdır.

Bu, şablonlu gönderimde özellikle can sıkıcıdır. {ad} gibi bir yer tutucu, gönderim anında değeriyle değiştirilse bile, metni yazan kişinin ekranında iki süslü parantez olarak durur ve sayaç henüz doldurulmamış şablonu sayıyorsa dört birim fazla gösterir. Tersi de olur: yer tutucunun yerine gelecek isim Şeyma ise, önizlemede 7 bitlik görünen mesaj gönderim anında 16 bitliğe döner. Sayacı her zaman doldurulmuş metin üzerinde çalıştırın, şablon üzerinde değil.

Mesaj sığmadığında bölünür ve her parça, kaçıncı parça olduğunu söyleyen altı baytlık bir başlık taşır. Bu başlık yükten düşülür, 153 ile 67 sayıları buradan gelir. 130 karakterlik bir metin 7 bitlik alfabede tek parçadır. Aynı metin 16 bitlikte 67 birimlik parçalara karşı 130 birimdir, yani ikidir. 140 karaktere çıkarın, üç olur. Fatura karakter sayısını değil parça sayısını takip eder.

Nasıl görülür

Sayacı önce fonksiyon olarak yazın ve gerçek bir cümle için ürettiği sayılara bakın. Alfabe düz bir katar olmalı, düzenli ifade değil:

const BASIC = new Set([...
  "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?¡" +
  "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà"]);

const EXT = new Set([..."\f^{}\\[~]|€"]);

export function analyse(text: string) {
  let units = 0;
  for (const ch of text) {
    if (BASIC.has(ch)) units += 1;
    else if (EXT.has(ch)) units += 2;
    else return ucs2(text);
  }
  return { encoding: "GSM7", units, segments: units <= 160 ? 1 : Math.ceil(units / 153) };
}

function ucs2(text: string) {
  const units = text.length;   /* UTF-16 birimi, yani emoji iki eder */
  return { encoding: "UCS2", units, segments: units <= 70 ? 1 : Math.ceil(units / 67) };
}

Sonra aynı cümleyi iki kez geçirin, biri aksanlı biri aksansız:

node -e '
const { analyse } = require("./segments");
const t = "Siparisiniz kargoya verildi, takip numaraniz mesajin devaminda yer aliyor. Iyi gunler dileriz.";
console.log(analyse(t));
console.log(analyse(t.replace("Siparisiniz", "Siparişiniz")));
'
# { encoding: 'GSM7', units: 94, segments: 1 }
# { encoding: 'UCS2', units: 94, segments: 2 }

Tek harf, bir fazla parça ve ekrandaki karakter sayısı hiç kıpırdamadı. Burası aynı zamanda arayüzünüzün insanlara ne söylediğine bakma anıdır: sağlayıcı iki parça faturalarken ekranda 94 / 160 yazan bir sayaç, hiç sayaç olmamasından kötüdür.

Çözüm

Dört değişiklik, ve yalnızca birincisi gerçekten kodla ilgili.

  1. Tek fonksiyon, tek yer. Arayüz, API, arka plandaki worker ve ücret tahmini aynı analyse fonksiyonunu kullansın. Bu kuralın iki ayrı uygulaması bir ay içinde birbirinden ayrılır ve arayüzdeki her zaman iyimser olandır.
  2. Gerçek sayıyı göndermeden önce gösterin. Sayaç kodlamayı, kullanılan birimi, parça sayısını ve çıkan maliyeti yazsın ve yazarken güncellensin. İnsanlar yaklaşan parça sınırını gördüklerinde metni düzenler, faturayı ise kimse düzenlemez.
  3. Harf sadeleştirmeyi önizlemeli bir seçenek olarak sunun. ş yerine s, ğ yerine g, ı yerine i, ç yerine c, ö yerine o, ü yerine u koymak iki parçalık bir mesajı tek parçaya döndürür. Dönüştürülmüş metni orijinalin yanında gösterin ve gönderen onaylasın.
  4. Önemli karakterlerde tahmin yürütmeyi reddedin. İsimler, adresler, hukuki bildirimler ve alıcının geri okuyacağı her tutar harflerini korumalıdır. Kampanya metni sadeleştirilebilir; bir kişinin soyadı sadeleştirilemez, bir harf düşünce anlamı değişen cümle de öyle.

Büyük harfe çevirmek burada başlı başına bir tuzaktır. Metni temel tabloya sığdırmak için büyük harfe katlıyorsanız doğrudan noktalı ve noktasız i sorununun içine girersiniz; locale bilmeyen bir çevirim ı harfini bir yerde I yapar, i harfini başka bir yerde yine I yapar. Bir normalizasyon kütüphanesinin makul davranmasını ummak yerine dönüşüm tablosunu açıkça yazın.

Standartta, 7 bitlik mesajın içinde Türkçe harflere izin veren bir ulusal dil tablosu var. Ben onun üstüne kurmuyorum. Rotalar ve telefonlar arasında desteği tutarsız, soru işareti dizisi olarak ulaşan bir mesaj da doğru ulaşanla aynı parayı yakıyor.

Çalıştığını nasıl doğrularsınız

Kontrol bir birim testi değil, faturayla karşılaştırmadır. Gönderim anında tahmin edilen parça sayısını her satıra yazın ve geri dönen sayıyla kıyaslayın:

SELECT encoding,
       predicted_segments,
       billed_segments,
       count(*) AS rows
FROM messages
WHERE created_at > now() - interval '1 day'
GROUP BY 1, 2, 3
ORDER BY rows DESC
LIMIT 20;

İki sayının ayrıştığı her satır sayaçtaki bir hatadır ve şekli size hangisi olduğunu söyler. Uzun 7 bitlik mesajlarda bir birimlik fark genellikle uzantı karakterlerinin tek birim sayıldığı anlamına gelir. Kodlamanın kendisinde anlaşmazlık varsa sebebi genellikle görünmeyen bir karakterdir: kelime işlemciden yapıştırılmış bir kırık tırnak veya bölünmez boşluk, metnin içinde kimsenin göremediği yerde durur.

İkinci kontrol geriye dönüktür. Elinizde zaten gönderilmiş metinler varsa sayacı hepsinin üzerinde bir kez çalıştırıp dağılıma bakın: kaç mesaj 16 bitliğe düşmüş, bunların kaçı tek bir Türkçe harf yüzünden düşmüş ve bu harfler nerede geçiyor. Bakımını yaptığım bir platformda bu döküm, maliyetin büyük kısmının pazarlama metninden değil, sonuna eklenen kısa bir kalıptan geldiğini gösterdi. Metni ölçmeden önce hangi metnin ölçüleceğini bilmek, ölçümün yarısıdır.

Nelere dikkat etmeli

  • Küçük ç temel tabloda yok ama büyük Ç var. "Türkçe harfleri" listeleyerek yazılmış her sayaç bu maddeyi paraya mal olan yönde yanlış yapar.
  • Parça bölünmesi, kaçış karakteriyle uzantı karakterinin arasına düşmemeli. Parçaları sade bir bölmeyle değil de açgözlü bir döngüyle doldurmazsanız, yanlış konumda } ile biten mesaj o karakteri karşı tarafta kaybeder.
  • 16 bitlik sayım UTF-16 birimleriyle yapılır. Emoji ikidir, bayrak veya ten tonu dizisi daha fazladır. Parçayı asla surrogate çiftinin ortasından kesmeyin.
  • Zengin metin editöründen gelen metne dikkat edin. Kırık tırnaklar, üç nokta karakteri ve bölünmez boşluk ekranda sıradan görünür, üçü de 16 bitlik kodlamayı tetikler.
  • Sondaki satır sonu bir karakterdir. Son kelimeden sonra bırakılan boşluk da öyle. Tam 160 birimde duran bir mesajda bu, koca bir fazladan parça demektir.

Ders tek bir alfabeden geniştir. Gösterilmeyen bir birim üzerinden ücretlendiren her sistem, zararsız bir düzenlemenin açıklanamayan bir maliyete dönüştüğü sistemdir ve düzenlemeyi yapan kişinin ikisini birbirine bağlamasının hiçbir yolu yoktur. Faturalanan birimi düzenlenen şeyin yanına koyun, onu faturalayan kodla aynı kodla hesaplayın; sürpriz sınıfının tamamı ortadan kalkar. Sayılar görünür olduğunda bir sonraki soru genelde maliyet değil hız olur ve orası bambaşka bir sınırlar kümesidir.

Sorular ve cevaplar

Bir mesaja kaç karakter sığar?
Her karakter 7 bitlik GSM alfabesindeyse 160, tek bir karakter bile dışarıdaysa 70. Mesaj bu sınırı aştığında parçalara bölünür ve her parça küçük bir başlık taşır. Bu başlık yüzünden parça başına 7 bitlikte 153, 16 bitlikte 67 karakter kalır; iki parçanın 320 değil 306 karakter tutmasının sebebi budur.
Hangi Türkçe harfler 7 bitlik alfabede var?
Temel tabloda Ö, ö, Ü, ü ve büyük Ç var, Türkçeden başka bir şey yok. Küçük ç ile ğ, ı, İ ve ş harflerinin iki hâli de yok, dolayısıyla bunlardan biri mesajın tamamını 16 bitlik kodlamaya iter. Bu da şaşırtıcı bir sonuç doğurur: büyük harfle yazılmış bir kelime, aynı kelimenin küçük harfli hâlinden ucuz olabilir.
Emoji tek karakter mi sayılır?
Hayır. 16 bitlik kodlama UTF-16 birimlerini sayar ve emojilerin çoğu bir surrogate çiftidir, yani iki birim tutar. Birleştiricilerle kurulan emojiler daha da fazla yer kaplar. Uzunluğu code point olarak sayan bir sayaç eksik sayar, parçalara körlemesine bölen bir kod ise surrogate çiftini ortadan ikiye bölebilir.
Maliyeti düşürmek için Türkçe karakterleri sadeleştirmek güvenli mi?
Sıradan pazarlama metninde genellikle evet, kampanyanın maliyetini yarıya indirebilir. Harflerin kendisi anlam taşıdığında yanlış cevaptır: kişi isimleri, adresler, hukuki bildirimler, alıntılanan tutarlar ve alıcının geri okuyacağı her şey. Bunu herkesin unuttuğu genel bir ayar değil, önizlemeli ve mesaj bazında verilen bir karar yapın.