پرش به محتوا
مستندات
EN
ورود به پنل
شروع
  • نقشهٔ مستندات
  • راه‌اندازی سریع
  • مفهوم‌ها
جمع‌آوری داده
  • تعریف رویداد
  • فرهنگ‌نامهٔ رویدادها
  • گذاشتن رویداد
  • ادغام هویت
  • SDK وب
  • SDK اندروید
  • ثبت دستگاه
  • سرور به سرور
  • کاتالوگ محصولات
  • وب‌هوک
مخاطب و پیام‌رسانی
  • سگمنت
  • سناریو
  • پیام تراکنشی
  • رضایت و سقف
  • پیام درون‌برنامه‌ای
تحلیل و خروجی
  • گزارش و خروجی
مرجع توسعه‌دهنده
  • مرجع API
    • نقاط ورود داده
    • API مدیریتی
  • کدهای خطا
  • سقف‌ها
  • OpenAPI
ابزارهای توسعه
  • سرور MCP
  • کار با عامل
حریم خصوصی و تغییرات
  • داده‌های شخصی
  • نسخه و تغییرها

ساخت سگمنت و معنی دقیق هر شرط

زبان شرط‌ها، همهٔ عملگرها، و تلهٔ اصلی: نام رویدادِ غلط ایراد نمی‌گیرد، فقط هیچ‌کس را برنمی‌گرداند.

یک سگمنت، یک درخت JSON از شرط‌هاست. پنل هیچ‌وقت SQL نمی‌فرستد؛ همین درخت را می‌فرستد و سرور آن را کامپایل می‌کند. برای شما یعنی هر کاری که پنل می‌کند از راه API هم شدنی است، و چند کار هست که فقط از راه API شدنی است: شرط روی ویژگی رویداد، تجمیع، و عضویت در سگمنت دیگر هیچ‌کدام دکمه‌ای در پنل ندارند.

این صفحه زبان شرط‌ها را کامل می‌گوید: شکل دقیق JSON، هر نوع شرط، هر عملگر، پنجره‌های زمانی، و فهرست چیزهایی که وجود ندارند. اگر ایجنت هوش مصنوعی هستید، بخش تله را قبل از نوشتن اولین فیلتر بخوانید.

#شکل کلی یک تعریف

RULE INPUTS
TraitsWho they are
EventsWhat they did
EngagementHow they respond
SEGMENTICSegment engineRules are evaluated against current customer state
READY TO USE
AudienceCurrent members
CampaignOne-time activation
JourneyContinuous automation
تبدیل ویژگی، رویداد و تعامل به مخاطب، کمپین و سناریو

بیرونی‌ترین لایه Definition است و دو فیلد دارد.

JSON
{
  "version": 1,
  "root": { "kind": "group", "op": "and", "children": [] }
}
فیلدنوعلازمتوضیح
versionعدد صحیحنهکامپایلر هیچ‌وقت آن را نمی‌خواند. پنل همیشه 1 می‌نویسد. اگر ننویسید، 0 ذخیره می‌شود و هیچ اتفاقی نمی‌افتد.
rootیک Nodeبلهاگر نباشد، Node خالی با kind تهی می‌ماند و کامپایلر با segment: unknown node kind: "" رد می‌کند.

در همه فراخوانی‌های HTTP این شیء یک لایه عمیق‌تر، داخل فیلدی به نام definition می‌نشیند:

JSON
{"definition": {"version": 1, "root": {"kind": "trait", "trait": "city", "operator": "eq", "value": {"type": "string", "str": "تهران"}}}}

root لازم نیست گروه باشد. یک شرط تنها هم ریشه معتبری است.

هر گره یک ساختار واحد است با فیلد kind که تعیین می‌کند کدام فیلدهای دیگر خوانده می‌شوند. ترتیب فیلدها روی سیم اهمیت دارد، چون اثر انگشت سگمنت (که تأیید کمپین با آن «همان مخاطب» را از «مخاطبی که بعد از تأیید عوض شده» تشخیص می‌دهد) از sha256 بایت‌های JSON ساخته می‌شود.

فیلد JSONنوعکدام kind آن را می‌خواند
kindرشتههمه. یکی از group، trait، event، segment، engagement، churn
opرشتهفقط group
notبولینفقط group، engagement، churn
childrenآرایه Nodeفقط group
traitرشتهفقط trait
compare_traitرشتهفقط trait. ویژگی دوم به‌جای value، در مقایسه دو ویژگی
eventرشتهفقط event
negateبولینفقط event
countشیءفقط event
aggregateشیءفقط event
propertiesآرایه شیءفقط event
windowشیءفقط event
segment_idعدد صحیحفقط segment
in_segmentبولینفقط segment
bandرشتهفقط engagement و churn
metricرشتهفقط engagement
operatorرشتهtrait، engagement، churn
valueشیءtrait، engagement، churn

هر فیلدی که در ستون سوم نیامده باشد، روی آن نوع گره بی‌سروصدا نادیده گرفته می‌شود. این مهم‌ترین منبع سردرگمی است: not: true روی یک گره trait یا event یا segment هیچ کاری نمی‌کند و هیچ خطایی هم نمی‌دهد. برای نفی رویداد negate را بگذارید و برای نفی عضویت in_segment: false.

کل درخت به یک شرط روی جدول پرونده‌ها تبدیل می‌شود:

SQL
SELECT user_id FROM segmentic.profiles FINAL
WHERE tenant_id = {tenant:UInt32} AND (<شرط کامپایل‌شده>)

FINAL عمدی است و خواندن را گران می‌کند: بدون آن پرونده‌ای که دو بار به‌روزرسانی شده دو بار شمرده می‌شود، و اندازه غلط مخاطب اعتماد را همان لحظه از بین می‌برد.

#گروه: and، or و not

JSON
{"kind": "group", "op": "or", "not": false, "children": [ ]}
  • op فقط دو مقدار معنادار دارد: and و or. هر چیزی که دقیق برابر or نباشد، AND معنی می‌دهد. "OR" با حروف بزرگ هم AND است. هیچ اعتبارسنجی روی این فیلد نیست و هیچ خطایی نمی‌گیرید.
  • children نباید خالی باشد. آرایه خالی یعنی خطای segment: group has no children.
  • not: true کل گروه را در NOT (...) می‌پیچد.
  • عمق تودرتویی: ریشه عمق صفر است و سقف عمق ۸ است، پس روی هم ۹ سطح. عمیق‌تر یعنی segment: nesting too deep.
  • اندازه: سقف ۲۰۰ گره. بودجه هر گره را یک واحد و هر عضو آرایه properties آن را هم یک واحد حساب می‌کند، چون شرط روی ویژگی رویداد هم یک شرط است. بیشتر یعنی segment: too many conditions.

سازنده شرط در پنل کل درخت را ویرایش می‌کند: هر شرط عملگر خودش را دارد و با «و» یا «یا» به شرط بالای خودش وصل می‌شود، و انتخاب عملگری غیر از عملگر همان فهرست، آن دو شرط را در یک گروه می‌گذارد. پنل تا سه سطح گروه می‌سازد در حالی که کامپایلر تا نه سطح را می‌پذیرد؛ این سقف خوانایی است نه ایمنی، و تعریفی که از راه API عمیق‌تر ساخته شده باشد در پنل درست باز و درست ویرایش می‌شود، فقط عمیق‌تر نمی‌شود. گروهی که not دارد و شرط عضویت در سگمنت دیگر در پنل کنترلی ندارند: نشان داده می‌شوند و دست‌نخورده می‌مانند.

#شرط روی ویژگی پرونده

JSON
{"kind": "trait", "trait": "city", "operator": "eq", "value": {"type": "string", "str": "تهران"}}

نام ویژگی trim می‌شود؛ تهی یا بلندتر از ۱۲۸ بایت یعنی segment: invalid identifier.

نام به همین ترتیب در چهار دسته گشته می‌شود و اولین جایی که پیدا شد برنده است: شش پرچم دسترس‌پذیری، هفت ویژگی عددی محاسبه‌شده، شانزده ستون رشته‌ای، و بعد birthday. نامی که در هیچ‌کدام نباشد ویژگی سفارشی است.

#ویژگی‌هایی که ستون واقعی‌اند

شش پرچم دسترس‌پذیری. has_push، has_email، has_phone، push_opt_in، email_opt_in، sms_opt_in.

اینها به col = 1 یا col = 0 تبدیل می‌شوند. منطق ساده و غافلگیرکننده است: مقدار پیش‌فرض «بله» است، اگر value.bool بفرستید همان می‌شود، و اگر عملگر neq باشد وارونه می‌شود. هر عملگر دیگری، از gt تا contains تا is_set، دقیق مثل تساوی رفتار می‌کند. پس has_push با عملگر contains هم قابل کامپایل است و همان کاری را می‌کند که eq می‌کرد.

هفت ویژگی عددی محاسبه‌شده.

نام ویژگیچه چیزی را می‌شمارد
total_eventsتعداد کل رویدادهای این پرونده
total_revenueمجموع مبلغ خریدها
order_countتعداد سفارش
days_since_last_seendateDiff('day', last_seen, now())
days_since_last_orderdateDiff('day', last_order_at, now())
days_until_birthdayروز تا تولد بعدی: امروز صفر است، سه روز دیگر سه
days_until_signup_anniversaryهمان حساب روی first_seen

days_until_birthday یک سالگرد است، نه یک تاریخ. تاریخ تولد ذخیره‌شده یک تاریخ در گذشته است و مقایسه آن با یک پنجره زمانی بعد از سال اول هیچ‌کس را برنمی‌گرداند. ۲۹ فوریه در سال غیرکبیسه روی ۱ مارس می‌افتد. برای کسی که تاریخ تولد ندارد مقدار NULL است، پس هر مقایسه‌ای روی او نادرست می‌شود و ساده از مخاطب بیرون می‌ماند.

days_until_signup_anniversary قبل از مقایسه ماه و روز، first_seen را که یک DateTime است به روز کوتاه می‌کند. بدون این کار کسی که ساعت ۲۳:۳۰ ثبت‌نام کرده با کسی که فردا ۰۰:۳۰ ثبت‌نام کرده یک روز اختلاف پیدا می‌کرد.

هر هفت‌تا عددی‌اند، پس روی آن‌ها is_set یعنی expr != 0 و is_not_set یعنی expr = 0. یک ویژگی عددی که برابر صفر است، «ثبت نشده» خوانده می‌شود.

شانزده ستون رشته‌ای. user_id، email، phone، first_name، last_name، gender، city، region، country، language، timezone، device_type، os_name، app_version، push_provider، national_id.

national_id تا مدتی در این فهرست نبود در حالی که تمام مدت ذخیره می‌شد، پس هر سگمنتی روی آن، برای هر مشتری، هیچ‌کس را برنمی‌گرداند و هیچ خطایی هم هیچ‌جا نبود. همین حادثه دلیل وجود این فهرست است.

#ویژگی‌های خودتان

هر نامی که در آن چهار دسته نباشد، یک ویژگی سفارشی است و در نگاشت‌های ClickHouse دنبالش می‌گردیم. نام ناشناس خطا نیست، چون مشتری‌ها مدام ویژگی خودشان را تعریف می‌کنند و رد کردن نام‌های ناشناس این قابلیت را بی‌مصرف می‌کرد. شاخه‌ای که انتخاب می‌شود به شکل مقایسه بستگی دارد.

شکل مقایسهSQL
is_set یا is_not_sethas(mapKeys(traits), {p0:String}) و نفی آن
مقدار بولین با eq یا neqlower(traits[{p0:String}]) = {p1:String} که {p1} رشته true یا false است
عملگر عددی، یا هر عملگری با مقدار عددی بجز in و not_in(mapContains(traits_num, {p0:String}) AND traits_num[{p0:String}] < {p1:Float64})
هر چیز دیگرtraits[{p0:String}] با قواعد رشته‌ای بخش عملگرها

سه نکته که هر کدام یک باگ واقعی روی داده مشتری بودند.

حضور با کلید سنجیده می‌شود، نه با مقدار. ویژگی ثبت‌نشده و ویژگی ثبت‌شده با مقدار تهی دو چیز متفاوت‌اند، و is_set روی ویژگی سفارشی تنها جایی است که این دو از هم جدا می‌شوند.

بولین با متن مقایسه می‌شود. ویژگی‌ای که به شکل JSON true فرستاده شده در traits رشته "true" و در traits_num عدد 1 ذخیره می‌شود. نگاشت متنی انتخاب شده چون نیمه‌ای است که همه پرونده‌ها دارند. جهت نفی هم عمدی است: neq true یعنی «می‌دانیم false است»، نه «نمی‌دانیم true است»، پس کسی که این ویژگی را هرگز نفرستاده در هیچ‌کدام از دو مخاطب نیست.

عدد با mapContains محافظت می‌شود. نگاشت ClickHouse برای کلید موجودنبود صفر برمی‌گرداند، پس بدون این محافظ «موجودی کمتر از ۱۰» هر پرونده‌ای را که هیچ موجودی ندارد برمی‌گرداند: این باگ اول بار با ۱۱۴۹۴۳ کاربر روی حسابی با حدود ۱۱۵۰۰۰ کاربر گزارش شد، عددی که مثل یک جواب واقعی به نظر می‌رسد. جهت شکست عمدی انتخاب شده است: شرط عددی روی ویژگی‌ای که هیچ‌کس ندارد حالا هیچ‌کس را انتخاب می‌کند نه همه را. مخاطب بی‌سروصدا خالی یعنی کمپینی که نمی‌رود و کسی متوجه می‌شود؛ مخاطب بی‌سروصدا همه یعنی کمپینی که رفته و برنمی‌گردد.

#birthday فقط به دو سؤال جواب می‌دهد

birthday یک ستون Nullable(Date) است و تنها is_set و is_not_set را می‌پذیرد:

JSON
{"kind": "trait", "trait": "birthday", "operator": "is_set"}

هر عملگر دیگری در زمان کامپایل رد می‌شود با این متن:

segment: unsupported operator: birthday only answers is_set and is_not_set; for an anniversary use days_until_birthday

دلیلش این است که مقایسه غیرعددی is_set ستون را با رشته تهی می‌سنجد و ClickHouse این را روی یک Date با Code: 38. Cannot parse date رد می‌کند. برای سؤال سالگرد از days_until_birthday استفاده کنید که یک عدد است.

#مقایسه دو ویژگی با هم

compare_trait به‌جای مقدار ثابت، ویژگی دومی را می‌گذارد، تا یک شرط بتواند دو عددی را بپرسد که خود پرونده هر دو را دارد:

JSON
{"kind": "trait", "trait": "gc_referrals_total", "operator": "gt", "compare_trait": "gc_referrals_active"}

این یعنی «کسی را دعوت کرده که هنوز فعال نشده»، و هیچ عدد ثابتی این را نمی‌گوید: خط برای کسی که دو نفر را دعوت کرده روی دو است و برای کسی که چهل نفر را دعوت کرده روی چهل. روی همان حسابی که این نیاز از آن آمد، این مخاطب ۸۸۸ نفر است. نزدیک‌ترین چیزی که با یک آستانه ثابت به دست می‌آید ۴۰ نفر است.

وقتی compare_trait ست شده باشد، value خوانده نمی‌شود.

شش عملگر. eq، neq، gt، gte، lt، lte. هر چیز دیگری در زمان کامپایل رد می‌شود، چون between دو کران می‌خواهد و in یک فهرست، و یک ویژگی دوم هیچ‌کدام نیست، و is_set فقط درباره یک طرف می‌پرسد:

segment: unsupported operator: comparing two traits takes eq, neq, gt, gte, lt or lte, got "between"

هر دو طرف باید عدد باشند. پرچم‌های دسترسی UInt8 هستند و عدد حساب می‌شوند. ستون متنی در هر طرف با نام خودش رد می‌شود، و birthday هم همان رد قبلی خودش را نگه می‌دارد:

segment: unsupported operator: city holds text, and comparing two traits compares numbers

دلیلش این است که وقتی هیچ طرفی مقدار ثابت نیست، چیزی نمانده که شکل مقایسه از آن خوانده شود. مسیر تک‌ویژگی بین traits_num و traits را از روی عملگر و مقداری که گرفته انتخاب می‌کند، و اینجا مقداری در کار نیست. عدد بودن مبهم نمی‌ماند، چون یک ویژگی عددی موقع ورود در هر دو نقشه نوشته می‌شود، پس mapContains(traits_num, key) جواب مطمئن «این ویژگی عدد است» را می‌دهد. برای جهت دیگر چنین آزمونی وجود ندارد، پس یک جفت متنی مجبور بود نقشه را حدس بزند، و حدسی که نقشه خالی را بخواند هیچ‌کس را انتخاب می‌کند و در عین حال شبیه جواب به نظر می‌رسد.

پرونده‌ای که یکی از دو طرف را ندارد در مخاطب نیست. هر ویژگی سفارشی نگهبان حضور خودش را همراه دارد، و days_until_birthday و شکل days_since_ وقتی چیزی برای شمردن نباشد از قبل NULL می‌دهند. این همان جهت شکستی است که مسیر عددی ویژگی‌ها انتخاب کرده بود: مخاطبی که بی‌صدا خالی است کمپینی است که نمی‌رود و دیده می‌شود، و مخاطبی که بی‌صدا همه است کمپینی است که رفته.

دو طرفSQL
دو ویژگی سفارشی(mapContains(traits_num, {p0:String}) AND mapContains(traits_num, {p1:String}) AND traits_num[{p0:String}] > traits_num[{p1:String}])
دو ستون واقعی(order_count > total_events)
یکی از هر کدام(mapContains(traits_num, {p0:String}) AND order_count < traits_num[{p0:String}])

پنل این یکی را نمی‌سازد. سطر شرط یک کنترل مقدار دارد و این شرط به انتخابگر ویژگی دوم احتیاج دارد، پس سازنده چنین شرطی را فقط‌خواندنی نشان می‌دهد و جعبه مقداری که کامپایلر هیچ‌وقت نمی‌خواندش را نمی‌کشد.

#شرط روی رویداد

JSON
{
  "kind": "event",
  "event": "order_completed",
  "negate": false,
  "window": {"kind": "last", "amount": 30, "unit": "day"},
  "properties": [],
  "count": {"operator": "gte", "value": 3}
}

نام رویداد trim می‌شود؛ تهی یا بلندتر از ۱۲۸ بایت یعنی segment: invalid identifier.

هر شرط رویداد به یک زیرکوئری روی segmentic.events تبدیل می‌شود که همیشه این سه شرط پایه را دارد:

SQL
tenant_id = {tenant:UInt32}
AND name = {p0:String}
AND is_bot = 0

فیلتر ربات همیشگی است و هیچ راهی برای خاموش کردنش نیست. کسی نمی‌خواهد به یک خزنده پوش بفرستد.

بعد پنجره زمانی، بعد شرط‌های properties، و در آخر یک HAVING که یا از aggregate می‌آید یا از count. GROUP BY user_id فقط وقتی اضافه می‌شود که HAVING وجود داشته باشد.

بیرونی‌ترین لایه:

  • negate غایب یا false: user_id IN (زیرکوئری)
  • negate: true: user_id NOT IN (زیرکوئری)

NOT IN عمدی است: «انجام نداده» باید کسانی را که هیچ رویدادی ندارند هم شامل شود، و چون کوئری بیرونی از جدول پرونده‌ها می‌آید، NOT IN این را رایگان می‌دهد.

ترکیب negate: true با count را با دقت بخوانید. هر دو اعمال می‌شوند، پس نتیجه user_id NOT IN (... HAVING count() >= 3) است، یعنی «حداقل سه بار انجام نداده»، که کسی را که دو بار انجام داده در مخاطب نگه می‌دارد. کامپایلر هشدار نمی‌دهد. جمله توصیفی هم کمکی نمی‌کند: برای رویداد نفی‌شده، شمارش در جمله نوشته نمی‌شود و فقط «انجام نداده‌اند» را می‌خوانید.

#شرط روی ویژگی رویداد

JSON
{"property": "category", "operator": "eq", "value": {"type": "string", "str": "موبایل"}}

سه فیلد و بس: property، operator، value. نام ویژگی لازم است و سقف ۱۲۸ بایت دارد.

ترتیب تصمیم‌گیری:

  1. revenue مستقیم روی ستون ارتقایافته revenue می‌نشیند و عددی حساب می‌شود.
  2. عملگر عددی، یا هر عملگری با مقدار عددی بجز in و not_in، به props_num[{key:String}] می‌رود.
  3. مقدار بولین با eq یا neq روی props_str[key] با رشته true یا false مقایسه می‌شود.
  4. is_set و is_not_set به has(mapKeys(props_str), {key:String}) تبدیل می‌شوند.
  5. باقی همه روی props_str[{key:String}] با قواعد رشته‌ای.

دو چیز که باید بدانید. اول اینکه اعضای properties همیشه با AND به هم وصل می‌شوند؛ هیچ راهی برای OR بین دو شرط ویژگی یک رویداد وجود ندارد. دوم اینکه برخلاف مسیر ویژگی پرونده، اینجا محافظ mapContains نیست. props_num برای کلیدی که رویداد نداشته صفر برمی‌گرداند، پس شرط "revenue_share" lt 10 رویدادهایی را هم می‌گیرد که این ویژگی را هرگز نداشته‌اند.

#تعداد دفعات

JSON
{"count": {"operator": "gte", "value": 3}}
فیلدنوعتوضیح
operatorرشتهیکی از eq، neq، gt، gte، lt، lte، between
valueعددحد یا کران پایین
value2عددفقط برای between، کران بالا

به HAVING count() <op> {value:Float64} تبدیل می‌شود، و برای between به HAVING count() BETWEEN {a:Float64} AND {b:Float64}. مقدار به شکل Float64 بایند می‌شود، پس تعداد اعشاری بدون هیچ اعتراضی پذیرفته می‌شود و کاری که انتظار دارید نمی‌کند.

count() سطرهای رویداد را می‌شمارد. شمارش مقدارهای یکتا وجود ندارد.

پنل فقط gte، gt، lte، lt و eq را نشان می‌دهد؛ between فقط از API قابل نوشتن است.

#تجمیع روی یک ویژگی عددی

JSON
{"aggregate": {"function": "sum", "property": "revenue", "operator": "gte", "value": 2000000}}
فیلدنوعتوضیح
functionرشتهفقط sum، avg، min، max. بزرگ و کوچک حروف مهم نیست.
propertyرشتهrevenue یا هر کلید عددی رویداد
operatorرشتههمان شش عملگر عددی، به‌علاوه between
valueعددحد
value2عددفقط برای between

نامی خارج از آن چهار تابع یعنی segment: invalid identifier. تابع count در اینجا وجود ندارد؛ برای شمردن از count بخش قبل استفاده کنید. first و last هم وجود ندارند، پس هیچ راهی برای شرط گذاشتن روی مقدار ویژگی در آخرین رخداد یک رویداد نیست.

اینجا هم مثل properties محافظ mapContains نیست، پس sum روی ویژگی‌ای که بیشتر رویدادها ندارند بی‌سروصدا صفر جمع می‌زند.

aggregate بی‌سروصدا بر count مقدم است. اگر هر دو را بفرستید، count نادیده گرفته می‌شود.

پنجره‌ای که تجمیع روی آن اجرا می‌شود، همان window خود گره رویداد است. فیلد جداگانه‌ای برای دوره تجمیع وجود ندارد.

#عضویت در سگمنت دیگر

JSON
{"kind": "segment", "segment_id": 1234, "in_segment": true}

به این تبدیل می‌شود:

SQL
user_id IN (SELECT user_id FROM segmentic.segment_members
            WHERE tenant_id = {tenant:UInt32} AND segment_id = {p0:UInt64})

segment_id باید باشد و غیرصفر باشد، وگرنه segment: invalid identifier: segment_id must be set.

دو تله اینجاست و هر دو بی‌سروصدا هستند.

in_segment پیش‌فرض false است و false یعنی NOT IN. ننوشتن این فیلد یعنی «عضو نیست»، نه «عضو هست».

این شرط فقط جدول فهرست‌های ثابت را می‌خواند. segment_members جایی است که عضویت سگمنت‌های static نوشته می‌شود. یک سگمنت dynamic هیچ سطری آنجا ندارد، پس اشاره به آن یک مجموعه تهی می‌دهد. این خطا نیست، فقط صفر است.

#تعامل

امتیازهای تعامل هر شب روی کل تاریخچه پیام‌ها محاسبه می‌شوند و در جدول خودشان زندگی می‌کنند، چون این یک اسکن است که هیچ نوشتن روی پرونده نمی‌تواند آن را حمل کند. به همین دلیل یک kind جداگانه است و نه یک ویژگی عددی.

دو شکل دارد که به همین ترتیب بررسی می‌شوند.

گروه. band را بگذارید. مقدارهای مجاز: engaged، passive، dormant، lost، new.

JSON
{"kind": "engagement", "band": "dormant"}

سنجه. metric را بگذارید. مقدارهای مجاز: score، ignored_streak، open_rate، click_rate، days_since_engaged.

JSON
{"kind": "engagement", "metric": "ignored_streak", "operator": "gte", "value": {"type": "number", "num": 10}}

عملگر باید عددی باشد: فقط gt، gte، lt، lte، between. توجه کنید که eq و neq در این تعریف عددی نیستند و رد می‌شوند، با متن segment: unsupported operator: engagement needs a numeric operator, got "eq". یک نرخ یا یک زنجیره بی‌پاسخ، «شامل» معناداری ندارد.

اگر نه band بدهید و نه metric: segment: invalid identifier: engagement needs a band or a metric.

نام گروه و نام سنجه هر دو اعتبارسنجی می‌شوند، برخلاف نام رویداد. دلیلش صریح در کد نوشته شده: غلط املایی بی‌سروصدا هیچ‌کس را برنمی‌گرداند، و سگمنتی که هیچ‌کس را برنمی‌گرداند دقیق شبیه سگمنتی است که مخاطبش ساکت شده، و همین دومی چیزی است که این قابلیت برای تشخیصش ساخته شده.

not: true روی این گره کار می‌کند و به NOT IN تبدیل می‌شود، که درست هم هست: کسی که کار شبانه هرگز امتیازش نداده، به‌طور قطع «فعال اثبات‌شده» نیست.

زیرکوئری با FINAL اجرا می‌شود، چون جدول یک ReplacingMergeTree است که کار شبانه بازنویسی‌اش می‌کند و بدون FINAL کسی که دو شب پشت‌سرهم امتیاز گرفته روی سطر قدیمی هم می‌افتد.

#ریسک ریزش

JSON
{"kind": "churn", "band": "high"}

گروه. band یکی از high، medium، low، unknown.

آستانه. operator را بگذارید و عددی باشد. مقایسه روی ستون probability انجام می‌شود که درصد صحیح است، نه کسر. یعنی «ریسک ریزش بیشتر از ۷۰» را با 70 می‌نویسید نه با 0.7.

JSON
{"kind": "churn", "operator": "gt", "value": {"type": "number", "num": 70}}

اگر هیچ‌کدام: segment: invalid identifier: churn needs a band or a threshold. اگر عملگر عددی نباشد: segment: unsupported operator: churn risk needs a numeric operator, got "eq".

دقت کنید که تقارن با تعامل برقرار نیست: churn شکل آستانه را وقتی انتخاب می‌کند که operator تهی نباشد، در حالی که engagement شکل سنجه را وقتی انتخاب می‌کند که metric تهی نباشد. گره churn که هم band و هم operator دارد، band را برمی‌دارد.

کسی که پیش‌بینی ندارد به عمد نه در هیچ گروهی است و نه بالای هیچ آستانه‌ای، پس از هر دو شکل بیرون می‌افتد. کسی که مدل هرگز ندیده «کم‌ریسک» نیست؛ نامشخص است، و کمپین بازگردانی‌ای که این دو را یکی بگیرد بودجه‌اش را خرج کسانی می‌کند که هیچ‌کس نگاهشان نکرده.

not: true کار می‌کند و NOT IN می‌دهد. زیرکوئری هم FINAL دارد.

#عملگرها

این پانزده رشته همه عملگرهای موجودند. رشته را دقیق همین‌طور بنویسید.

عملگرنوع مقدارمعنیSQL تولیدشده
eqرشته، عدد، بولینبرابر استlower(expr) = {p:String} یا expr = {p:Float64}
neqرشته، عدد، بولینبرابر نیستlower(expr) != {p:String} یا expr != {p:Float64}
containsرشتهزیررشته، بدون حساسیت به بزرگی حروفpositionCaseInsensitiveUTF8(expr, {p:String}) > 0
not_containsرشتهزیررشته نیستpositionCaseInsensitiveUTF8(expr, {p:String}) = 0
starts_withرشتهبا این شروع می‌شودstartsWith(lower(expr), {p:String})
ends_withرشتهبه این ختم می‌شودendsWith(lower(expr), {p:String})
gtعددبیشتر ازexpr > {p:Float64}
gteعددحداقلexpr >= {p:Float64}
ltعددکمتر ازexpr < {p:Float64}
lteعددحداکثرexpr <= {p:Float64}
betweenعدد، هم num و هم num2بازه بسته از دو طرفexpr BETWEEN {a:Float64} AND {b:Float64}
inفهرست رشتهیکی از این‌هاhas({p:Array(String)}, lower(expr))
not_inفهرست رشتههیچ‌کدام از این‌هاNOT has({p:Array(String)}, lower(expr))
is_setبدون مقدارثبت شده استبسته به نوع ستون، پایین را ببینید
is_not_setبدون مقدارثبت نشده استبسته به نوع ستون، پایین را ببینید

هر عملگری بجز is_set و is_not_set به value نیاز دارد. نبودش یعنی segment: operator requires a value.

is_set سه معنی متفاوت دارد و این تفاوت واقعی است، نه ظرافت:

کجاis_setis_not_set
ستون عددیexpr != 0expr = 0
ستون رشته‌ایexpr != ''expr = ''
ویژگی سفارشیhas(mapKeys(traits), key)نفی همان
birthdaybirthday IS NOT NULLbirthday IS NULL

هر مقایسه رشته‌ای، هر دو طرف را تا می‌کند تا فیلتری که با «ی» فارسی نوشته شده، پرونده‌ای را که با یای عربی (U+064A) ذخیره شده هم بگیرد. این شایع‌ترین دلیلی است که مخاطب دست‌ساز کوتاه برمی‌گردد. تهراني و تهرانی هر دو به یک مقدار بایند می‌شوند. همین تا کردن با کامپایلر تحلیل‌ها مشترک است، چون داشبوردی که روی «تهران» فیلتر شده باید دقیق همان آدم‌هایی را بشمارد که این سگمنت می‌شمارد.

کل فهرست in به شکل یک پارامتر Array(String) می‌رود، پس فهرست هزار شهری هم یک جای‌نگهدار است.

#مقدارها

JSON
{"type": "number", "num": 1000, "num2": 5000}
typeکدام فیلد بار را می‌برد
stringstr
numbernum و برای کران بالای between هم num2
boolbool
listlist، آرایه‌ای از رشته
datedate و date2

type هیچ‌وقت با عملگر تطبیق داده نمی‌شود. مقدار {"type": "string", "str": "۵"} با عملگر gt باعث می‌شود کامپایلر num را بخواند که صفر است، و شرط expr > 0 می‌شود. خطایی نمی‌گیرید.

type: "date" روی سیم پذیرفته می‌شود و کامپایلر هرگز آن را نمی‌خواند. تابع مقایسه فقط num، num2، str و list را می‌خواند. یک مقدار تاریخ با عملگر رشته‌ای در عمل با رشته تهی مقایسه می‌شود. مقایسه تاریخ روی ویژگی پرونده پیاده‌سازی نشده است. برای سؤال سالگرد از days_until_birthday و days_until_signup_anniversary استفاده کنید.

فهرست حداکثر ۱۰۰۰ عضو دارد؛ بیشتر یعنی segment: list has too many values. فهرست تهی با in یا not_in یعنی segment: operator requires a value.

#پنجره زمانی

JSON
{"kind": "last", "amount": 30, "unit": "day"}

window فقط روی گره event خوانده می‌شود. روی trait، segment، engagement و churn بی‌سروصدا نادیده گرفته می‌شود.

kindفیلدهای لازمشرط تولیدشده
all_time یا رشته تهیهیچهیچ شرطی روی event_time گذاشته نمی‌شود
lastamount، unitevent_time >= now() - INTERVAL {p:UInt32} <UNIT>
betweenfrom، toevent_time BETWEEN {p:DateTime64(3)} AND {p:DateTime64(3)}
afterfromevent_time >= {p:DateTime64(3)}
beforetoevent_time < {p:DateTime64(3)}

نبودن کل شیء window همان all_time است. هر kind دیگری یعنی segment: invalid time window: kind "...".

پنجره نسبی. unit یکی از minute، hour، day، week، month است و بزرگی حروف مهم نیست. واحد تنها بخشی از کوئری است که نمی‌تواند پارامتر بایندشده باشد، و فهرست مجاز دقیق به همین دلیل وجود دارد. amount باید بین ۱ و ۱۰۰۰۰ باشد.

INTERVAL n MONTH در ClickHouse یک ماه تقویمی است. ولی تابعی که زمین‌بازی زمانی یک تعریف را برای زمان‌بند حساب می‌کند، ماه را ۳۰ روز می‌گیرد. یعنی برای پنجره‌های ماهانه، پیش‌بررسی زمان‌بند و کوئری واقعی کمی با هم اختلاف دارند.

پنجره مطلق. from و to مهرزمان RFC 3339 هستند.

JSON
{"kind": "between", "from": "2026-03-21T00:00:00Z", "to": "2026-06-21T00:00:00Z"}

between هر دو کران را می‌خواهد و to نباید قبل از from باشد، وگرنه segment: invalid time window: between needs from and to یا segment: invalid time window: to is before from. after فقط from می‌خواهد و before فقط to. دقت کنید که after شامل خود لحظه است (>=) و before نیست (<).

همه‌چیز UTC است. فیلد منطقه زمانی روی پنجره وجود ندارد، منطقه زمانی مشتری روی پنجره اعمال نمی‌شود، و تاریخ جلالی روی سیم فرستاده نمی‌شود. این تصمیم صریح است: پنل تاریخ را جلالی نشان می‌دهد و همیشه لحظه UTC می‌فرستد.

جلالی فقط در جمله توصیفی ظاهر می‌شود. پنجره {"kind": "after", "from": "2026-03-21T00:00:00Z"} در فارسی این‌طور خوانده می‌شود:

پس از ۱ فروردین ۱۴۰۵

و همان لحظه در انگلیسی 21 March 2026 است.

پنل between را می‌نویسد، before و after را نه. کنترل پنجره همان فهرست ۱، ۷، ۱۴، ۳۰، ۹۰، ۱۸۰ و ۳۶۵ روز به‌علاوه «همه زمان‌ها» را دارد، و کنار آن‌ها گزینه «بین دو تاریخ» که تقویم جلالی را باز می‌کند و from و to را می‌نویسد. کوهورت، یعنی «کسانی که اولین بار X را بین این دو روز انجام دادند»، تنها چیزی است که با پنجره نسبی گفته نمی‌شود و به همین دلیل این گزینه وجود دارد.

تقویم در این حالت میان‌بر نسبی نشان نمی‌دهد. میان‌برها بازه‌ای می‌دهند که مدام جابه‌جا می‌شود و این کنترل باید دو تاریخ ثابت بدهد، پس گزینه‌ای که خروجی‌اش باید فورا منجمد شود اصلا پیشنهاد نمی‌شود.

before و after هنوز فقط از راه API نوشته می‌شوند.

#تله: نام رویداد غلط، کامپایل تمیز و مخاطب صفر

نام رویداد در برابر هیچ فهرستی بررسی نمی‌شود. کامپایلر فقط چک می‌کند که تهی نباشد و از ۱۲۸ بایت بلندتر نباشد، بعد آن را به‌عنوان پارامتر بایند می‌کند. order_completd یک SQL کامل و معتبر تولید می‌کند که صفر سطر برمی‌گرداند، و این از یک مخاطب واقعی صفر قابل تشخیص نیست.

همین تله برای نام ویژگی پرونده و نام ویژگی رویداد هم برقرار است، در properties و در aggregate.

چرا این‌طور طراحی شده: مشتری‌ها مدام ویژگی و رویداد خودشان را تعریف می‌کنند و رد کردن نام‌های ناشناس این قابلیت را بی‌مصرف می‌کرد. سابقه‌اش هم هست: ویژگی national_id تمام مدت ذخیره می‌شد ولی از فهرست ستون‌ها جا افتاده بود، پس هر سگمنتی روی آن، برای هر مشتری، هیچ‌کس را برنمی‌گرداند و هیچ خطایی هیچ‌جا نبود.

راه چاره این است که نام‌ها را نپرسید، بخوانید.

Shell
curl https://api.segmentic.net/v1/schema/events \
  -H "Authorization: Bearer sk_seg_..."
JSON
{
  "events": [
    {"name": "order_completed", "volume": 812443, "prop_keys": ["revenue", "category", "coupon"], "last_seen": "2026-08-06"},
    {"name": "product_viewed", "volume": 4192010, "prop_keys": ["sku", "category"], "last_seen": "2026-08-07"}
  ]
}

فهرست به ترتیب حجم مرتب است. last_seen مفیدترین ستون این پاسخ است: رویدادی با حجم بزرگ و last_seen سه هفته پیش یعنی یکپارچه‌سازی‌ای که خراب شده، و هیچ عدد دیگری این را نمی‌گوید. حجم به‌تنهایی تا یک ماه بعد هم سالم به نظر می‌رسد، چون پنجره‌اش ۹۰ روز است.

برای ویژگی‌های پرونده:

Shell
curl https://api.segmentic.net/v1/schema/traits \
  -H "Authorization: Bearer sk_seg_..."
JSON
{
  "traits": ["city", "loyalty_tier", "gc_key_balance"],
  "schema": [
    {"name": "city", "kind": "string", "users": 114233},
    {"name": "loyalty_tier", "kind": "string", "users": 40112},
    {"name": "gc_key_balance", "kind": "number", "users": 98004}
  ]
}

kind می‌گوید ویژگی در کدام نگاشت زندگی می‌کند، و همان چیزی است که تعیین می‌کند «بیشتر از ۵۰۰۰۰۰۰» و «برابر ۵۰۰۰۰۰۰» روی چه ستونی کامپایل می‌شوند. users تعداد پرونده‌هایی است که این ویژگی را دارند؛ ویژگی‌ای که سه نفر دارند به احتمال زیاد آن چیزی نیست که فکر می‌کنید.

هر دو مسیر مجوز event.read می‌خواهند.

بعد از ساختن فیلتر، آن را با POST /v1/audiences/validate بخوانید و جمله فارسی برگشتی را با آنچه در سرتان بود مقایسه کنید. کسی که به‌جای مشهد «تهران» می‌خواند، باگش را قبل از خرج کردن یک کوئری پیدا کرده است.

#هشت مثال کامل

هر مثال، JSON کامل است به‌علاوه جمله‌ای که سرور برمی‌گرداند. جمله فارسی چیزی است که POST /v1/audiences/validate در فیلد description_fa می‌دهد. جمله انگلیسی چیزی است که پنل در حالت انگلیسی نشان می‌دهد؛ API عمومی همیشه فارسی جواب می‌دهد، چون میان‌افزار زبان روی آن سوار نیست و پیش‌فرض فارسی است.

مقدارها ترجمه نمی‌شوند. هر رشته‌ای که در فیلتر بنویسید، همان‌طور که هست در جمله برمی‌گردد.

#سبد رها شده

JSON
{
  "definition": {
    "version": 1,
    "root": {
      "kind": "group",
      "op": "and",
      "children": [
        {
          "kind": "event",
          "event": "product_added_to_cart",
          "count": {"operator": "gte", "value": 1},
          "window": {"kind": "last", "amount": 7, "unit": "day"}
        },
        {
          "kind": "event",
          "event": "order_completed",
          "negate": true,
          "count": {"operator": "gte", "value": 1},
          "window": {"kind": "last", "amount": 7, "unit": "day"}
        }
      ]
    }
  }
}
JSON
{"valid": true, "description_fa": "کاربرانی که در ۷ روز گذشته «افزودن به سبد» را حداقل یک بار انجام داده‌اند و در ۷ روز گذشته «خرید» انجام نداده‌اند"}

انگلیسی، از پنل:

Users who in the last 7 days did “Added to cart” at least once and in the last 7 days did not do “Purchase”

#خریدار تهرانی که اپ را باز نکرده

JSON
{
  "definition": {
    "version": 1,
    "root": {
      "kind": "group",
      "op": "and",
      "children": [
        {
          "kind": "event",
          "event": "order_completed",
          "count": {"operator": "gte", "value": 3},
          "window": {"kind": "last", "amount": 30, "unit": "day"}
        },
        {"kind": "trait", "trait": "city", "operator": "eq", "value": {"type": "string", "str": "تهران"}},
        {"kind": "event", "event": "app_opened", "negate": true}
      ]
    }
  }
}
JSON
{"valid": true, "description_fa": "کاربرانی که در ۳۰ روز گذشته «خرید» را حداقل ۳ بار انجام داده‌اند و شهر آن‌ها «تهران» است و «باز کردن اپ» انجام نداده‌اند"}

رویداد سوم پنجره ندارد، پس «هیچ‌وقت اپ را باز نکرده‌اند» معنی می‌دهد، نه «در سی روز گذشته باز نکرده‌اند».

#مجموع خرید در نود روز

JSON
{
  "definition": {
    "version": 1,
    "root": {
      "kind": "event",
      "event": "order_completed",
      "window": {"kind": "last", "amount": 90, "unit": "day"},
      "aggregate": {"function": "sum", "property": "revenue", "operator": "gte", "value": 2000000}
    }
  }
}
JSON
{"valid": true, "description_fa": "کاربرانی که در ۹۰ روز گذشته مجموع مبلغ «خرید» آن‌ها حداقل ۲٬۰۰۰٬۰۰۰ است"}
Users who in the last 90 days have a total amount for “Purchase” that is at least 2,000,000

اینجا ریشه یک گره رویداد است، نه گروه. این معتبر است.

عددها در جمله فارسی با ارقام فارسی و جداکننده هزارگان عربی (U+066C) نوشته می‌شوند، چون در ایران مبلغ این‌طور خوانده می‌شود.

#تهرانی‌های قابل دسترسی، با گروه تودرتو

JSON
{
  "definition": {
    "version": 1,
    "root": {
      "kind": "group",
      "op": "and",
      "children": [
        {"kind": "trait", "trait": "city", "operator": "eq", "value": {"type": "string", "str": "تهران"}},
        {
          "kind": "group",
          "op": "or",
          "children": [
            {"kind": "trait", "trait": "has_push", "operator": "eq", "value": {"type": "bool", "bool": true}},
            {"kind": "trait", "trait": "has_email", "operator": "eq", "value": {"type": "bool", "bool": true}}
          ]
        }
      ]
    }
  }
}
JSON
{"valid": true, "description_fa": "کاربرانی که شهر آن‌ها «تهران» است و (قابلیت دریافت پوش دارند یا داشتن ایمیل دارند)"}

فقط گروه تودرتو پرانتز می‌گیرد؛ سطح بالا بدون پرانتز بهتر خوانده می‌شود. این تعریف را پنل هم می‌تواند بسازد.

#عضو یک فهرست ثابت

JSON
{
  "definition": {
    "version": 1,
    "root": {"kind": "segment", "segment_id": 1234, "in_segment": true}
  }
}
JSON
{"valid": true, "description_fa": "کاربرانی که عضو سگمنت شماره ۱٬۲۳۴ هستند"}

اگر "in_segment": true را بردارید، معنی وارونه می‌شود:

کاربرانی که عضو سگمنت شماره ۱٬۲۳۴ نیستند

این تنها فیلدی در کل این زبان است که نبودنش شرط را وارونه می‌کند.

#خرید بالای پانصد هزار در یک دسته

JSON
{
  "definition": {
    "version": 1,
    "root": {
      "kind": "event",
      "event": "order_completed",
      "window": {"kind": "last", "amount": 7, "unit": "day"},
      "properties": [
        {"property": "revenue", "operator": "gt", "value": {"type": "number", "num": 500000}},
        {"property": "category", "operator": "eq", "value": {"type": "string", "str": "موبایل"}}
      ]
    }
  }
}
JSON
{"valid": true, "description_fa": "کاربرانی که در ۷ روز گذشته «خرید» انجام داده‌اند که مبلغ آن بیشتر از ۵۰۰٬۰۰۰ باشد و category آن «موبایل» باشد"}

revenue تنها ویژگی رویداد است که نام فارسی دارد و «مبلغ» خوانده می‌شود. هر کلید دیگری خام در وسط جمله فارسی می‌نشیند، چون برای فیلدی که مشتری اختراع کرده ترجمه‌ای وجود ندارد و ساختن ترجمه از نشان دادن چیزی که خودش تایپ کرده بدتر است.

#در آستانه ریزش

JSON
{"definition": {"version": 1, "root": {"kind": "churn", "band": "high"}}}
JSON
{"valid": true, "description_fa": "کاربرانی که در گروه «ریسک ریزش بالا» هستند"}

این و «خاموش‌شده‌ها» دو مخاطبی هستند که روز اول یکپارچه‌سازی هم کار می‌کنند، چون به نام هیچ رویدادی وابسته نیستند.

#ده پیام پشت‌سرهم بی‌پاسخ

JSON
{
  "definition": {
    "version": 1,
    "root": {
      "kind": "engagement",
      "metric": "ignored_streak",
      "operator": "gte",
      "value": {"type": "number", "num": 10}
    }
  }
}
JSON
{"valid": true, "description_fa": "کاربرانی که پیام‌های بی‌پاسخ پشت‌سرهم آن‌ها حداقل ۱۰ است"}

شکل سنجه همان قالب جمله‌ای را به کار می‌برد که هر مقایسه عددی دیگری به کار می‌برد، تا شرط تعامل مثل بقیه جمله خوانده شود و نه مثل چیزی که کنارش پیچ شده. در انگلیسی همین بازاستفاده باعث می‌شود حرف تعریف کمی ناجور بنشیند.

#سنجیدن مخاطب: کدام فراخوانی چه چیزی می‌دهد

شش مسیر اینجا هست و فقط دوتای اول از اینترنت قابل دسترسی‌اند.

مسیرمیزبانمجوزچه می‌دهدهزینه
POST /v1/audiences/validateapi.segmentic.netsegment.readمعتبر بودن و جمله فارسی۱ واحد
POST /v1/audiences/countapi.segmentic.netsegment.readشمارش دقیق۲۵ واحد
POST /v1/segments/estimateفقط پنلsegment.readتخمین نمونه‌گیری‌شدهندارد
POST /v1/segments/previewفقط پنلprofile.readچند پرونده واقعیندارد
POST /v1/segments/identifier-previewفقط پنلprofile.readتعداد تطبیق و حداکثر ۱۰۰ پرونده برای شناسه، ایمیل یا شمارهندارد
POST /v1/segments/describeفقط پنلsegment.readفقط جملهندارد

«فقط پنل» یعنی این مسیرها روی صفحه کنترل داشبورد ثبت شده‌اند، و آن پورت به عمد از اینترنت مسیریابی نمی‌شود. پروکسی فقط میزبان عمومی را به شنونده دوم API می‌برد. پس نمی‌توانید POST /v1/segments/preview را از سرور خودتان صدا بزنید؛ آن دکمه پیش‌نمایش داخل پنل است.

پیش‌نمایش شناسه‌ها بدنه‌ای مثل {"identifiers":["09123456789","user_42"],"limit":100} می‌گیرد. حداکثر پنجاه هزار ورودی را در یک کوئری محدود به همان tenant تطبیق می‌دهد و count، users، unmatched و در صورت بریده شدن ورودی truncated را برمی‌گرداند. این مسیر چیزی در عضویت سگمنت نمی‌نویسد.

پنج مسیر دیگر بدنه یکسانی می‌خواهند:

JSON
{"definition": { }, "limit": 10}

limit را فقط preview می‌خواند.

#validate

Shell
curl -X POST https://api.segmentic.net/v1/audiences/validate \
  -H "Authorization: Bearer sk_seg_..." \
  -H "Content-Type: application/json" \
  -d '{"definition":{"version":1,"root":{"kind":"trait","trait":"city","operator":"eq","value":{"type":"string","str":"تهران"}}}}'
JSON
{"valid": true, "description_fa": "کاربرانی که شهر آن‌ها «تهران» است"}

فیلتر نامعتبر با کد وضعیت ۴۲۲ و پاکت خطای API عمومی برمی‌گردد:

JSON
{"error": {"code": "filter_invalid", "message": "segment: group has no children"}}

این عمدی است و با نسخه داخل پنل فرق دارد: پنل به فیلتر نامعتبر ۲۰۰ با valid: false جواب می‌دهد، که برای فرمی که کاربر همان لحظه در آن تایپ می‌کند درست است و برای یکپارچه‌سازی‌ای که مدیریت خطایش روی کد وضعیت شاخه می‌زند غلط.

هیچ دیتابیسی لمس نمی‌شود. این ارزان‌ترین راه برای اطمینان از اینکه فیلترتان همان چیزی است که فکر می‌کنید.

#count

Shell
curl -X POST https://api.segmentic.net/v1/audiences/count \
  -H "Authorization: Bearer sk_seg_..." \
  -H "Content-Type: application/json" \
  -d '{"definition":{"version":1,"root":{"kind":"trait","trait":"city","operator":"eq","value":{"type":"string","str":"تهران"}}}}'
JSON
{"count": 114233, "approximate": false, "description": "کاربرانی که شهر آن‌ها «تهران» است", "took_ms": 812}

اینجا سه چیز غافلگیرکننده هست و هر سه از یک ریشه می‌آیند: این مسیر همان handler پنل را دوباره به کار می‌گیرد.

  • نام فیلد description است، نه description_fa، ولی محتوایش همیشه فارسی است.
  • خطاهایش پاکت مسطح پنل را دارند، نه پاکت API عمومی. فیلتر نامعتبر یعنی 400 {"error": "segment: ..."} و خرابی انبار داده یعنی 503 {"error": "count unavailable"}. این با وعده «یک شکل خطا در همه‌جا» روی API عمومی نمی‌خواند.
  • approximate همیشه false است.

هزینه‌اش ۲۵ واحد از بودجه است، چون یک اسکن کامل FINAL روی پرونده‌های شماست به‌علاوه هر زیرکوئری. مهلتش ۳۰ ثانیه است.

هیچ محدودکننده هم‌زمانی روی این مسیر وجود ندارد و هیچ ردی از «پنجره بی‌کران» گرفته نمی‌شود: فیلتری با شرط رویدادی بدون window کامپایل و اجرا می‌شود.

#estimate

شمارنده زنده زیر سازنده سگمنت در پنل. با هش کردن شناسه کاربر نمونه می‌گیرد و یکی از هر ۱۰۰ را می‌خواند، بعد عدد را ضرب می‌کند.

JSON
{"count": 2400000, "approximate": true, "sample_rate": 100, "description": "کاربرانی که شهر آن‌ها «تهران» است", "took_ms": 41}

مهلتش ۳ ثانیه است و در صورت سررسید 503 {"error": "estimate unavailable"} می‌دهد؛ منتظر نمی‌ماند.

یک بازگشت به شمارش دقیق دارد: اگر عدد نمونه‌گیری‌شده زیر ۳۰۰۰ باشد (یعنی زیر ۳۰ سطر واقعی در نمونه) کوئری دقیق دوباره اجرا می‌شود و پاسخ با approximate: false و بدون sample_rate برمی‌گردد. دلیلش این است که مخاطب ۴۰ نفره با نمونه یک در صد صفر خوانده می‌شود، و صفر غلط بدتر از عدد تقریبی است.

#preview

مجوزش profile.read است نه segment.read، چون این مسیر نام و شماره موبایل و شهر آدم‌های واقعی را برمی‌گرداند.

JSON
{"users": [{"user_id": "u_1", "email": "ali@example.ir", "phone": "+989120000000", "first_name": "علی", "city": "تهران", "last_seen": "2026-08-01T09:00:00Z"}]}

limit پیش‌فرض ۱۰ است و سقف ۱۰۰. سقف عمدی است، نه فقط پیش‌فرض: احترام گذاشتن به limit بدون سقف، «پیش‌نمایش» را به خروجی انبوه فهرست مشتری تبدیل می‌کند که هر کسی با profile.read به آن می‌رسد، و در لاگ ممیزی از نگاه انداختن به ده سطر قابل تشخیص نیست. بردن داده بیرون از ساختمان مجوز data.export است که جداست.

مرتب‌سازی last_seen DESC است، پس تازه‌ترین تطابق‌ها را می‌بینید نه نمونه تصادفی. فیلدهای تماس پوشانده نمی‌شوند.

#describe

فقط جمله را برمی‌گرداند و به هیچ دیتابیسی دست نمی‌زند، به همین دلیل سازنده سگمنت می‌تواند با هر ضربه کلید صدایش بزند.

JSON
{"description": "کاربرانی که شهر آن‌ها «تهران» است"}

زبانش از هدر Accept-Language می‌آید، چون میان‌افزار زبان روی mux پنل سوار است.

این مسیر اعتبارسنجی نمی‌کند. تعریفی که کامپایل نمی‌شود هم جمله می‌گیرد، و تعریف تهی جمله همه کاربران می‌گیرد. یعنی describe و validate درباره تعریف تهی با هم اختلاف دارند: describe می‌گوید «همه کاربران» و validate آن را رد می‌کند.

#سگمنت ذخیره‌شده: ساخت، ویرایش، حذف

سگمنت ذخیره‌شده یک سطر در پستگرس است با یک نام یکتا در هر حساب.

JSON
{
  "id": 11,
  "name": "خریداران تهران",
  "kind": "dynamic",
  "definition": {"version": 1, "root": { }},
  "description_fa": "کاربرانی که شهر آن‌ها «تهران» است",
  "last_size": 0,
  "updated_at": "2026-08-07T11:20:00Z"
}

description_fa همیشه سمت سرور محاسبه و در زمان ذخیره کش می‌شود؛ هر چیزی که در این فیلد بفرستید دور ریخته می‌شود.

سه نوع وجود دارد:

  • dynamic، پیش‌فرض. هیچ‌چیز مادی نمی‌شود. تعریف هر بار که کسی می‌شمارد یا کمپینی صفحه‌بندی می‌کند، تازه اجرا می‌شود.
  • static. عضویت، سطرهایی است که کسی وارد کرده. تعریفش کامپایل نمی‌شود و می‌تواند فقط {"version": 1} باشد.
  • realtime. هم API و هم قید دیتابیس این مقدار را می‌پذیرند و هیچ‌چیزی در بک‌اند آن را پیاده‌سازی نکرده است. تنها کدی که خاص برخوردش می‌کند، نوشتن مستقیم عضویت را دقیق مثل dynamic رد می‌کند. رزروشده حسابش کنید، نه کارآمد.

نوع بعد از ساخت قابل تغییر نیست. به‌روزرسانی، name و definition و description_fa را می‌نویسد و kind را به عمد دست نمی‌زند. تبدیل یک مخاطب ذخیره‌شده از ثابت به پویا، عضویتش را در محاسبه بعدی بی‌سروصدا دور می‌ریخت، و تبدیل برعکس، کوئری‌ای را منجمد می‌کرد که کسی هنوز فکر می‌کند زنده است.

#از API مدیریت

متد و مسیرمجوزپاسخ موفق
GET /v1/segmentssegment.read{"segments": [...]}
GET /v1/segments/{id}segment.readشیء سگمنت
POST /v1/segmentssegment.write۲۰۱
PUT /v1/segments/{id}segment.write۲۰۰
DELETE /v1/segments/{id}segment.delete۲۰۴ بدون بدنه

بدنه نوشتن فقط دو فیلد دارد:

Shell
curl -X POST https://api.segmentic.net/v1/segments \
  -H "Authorization: Bearer sk_seg_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"خریداران تهران","definition":{"version":1,"root":{"kind":"trait","trait":"city","operator":"eq","value":{"type":"string","str":"تهران"}}}}'
JSON
{"id": 11, "name": "خریداران تهران", "description_fa": "کاربرانی که شهر آن‌ها «تهران» است"}

فیلد kind روی این بدنه وجود ندارد، پس هر سگمنتی که API مدیریت بسازد dynamic است. فهرست ثابت را از این راه نمی‌شود ساخت.

خطاها:

وضعیتکدچه وقت
۴۰۰name_requiredname تهی یا فقط فاصله
۴۰۰bad_idشناسه در مسیر عدد مثبت نیست
۴۰۰malformed_jsonبدنه JSON معتبر نیست
۴۲۲filter_invalidتعریف کامپایل نمی‌شود، با متن دقیق کامپایلر
۴۰۴not_foundروی PUT: شناسه ناشناس، یا شناسه مشتری دیگر
۵۰۳segment_unavailableذخیره یا حذف ممکن نشد

در PUT، خواندن قبل از نوشتن عمدی است تا شناسه‌ای از URL مشتری دیگر ۴۰۴ بدهد و نه نوشتنی که بی‌سروصدا یک سگمنت روی حساب شما می‌سازد. name تهی در PUT یعنی «نام فعلی را نگه دار».

دو مسیر از این جدول مثل بقیه رفتار نمی‌کنند و دلیل هر دو یکی است: این‌ها handler پنل‌اند نه handler API مدیریت.

GET /v1/segments/{id} خطای ۴۰۴ خودش را در پاکت مسطح می‌دهد، {"error": "segment not found"}، بدون فیلد code. روی این مسیر error را هم رشته و هم شیء در نظر بگیرید.

DELETE /v1/segments/{id} اصلا ۴۰۴ نمی‌دهد. بایگانی یک UPDATE ... WHERE tenant_id = $1 AND id = $2 AND archived_at IS NULL است و تعداد سطر تغییرکرده خوانده نمی‌شود، پس حذف شناسه‌ای که وجود ندارد، شناسه‌ای که مال حساب دیگری است، و شناسه‌ای که قبلا حذفش کرده‌اید، هر سه دقیق مثل یک حذف واقعی 204 می‌گیرند. هیچ‌چیز در پاسخ این سه را از هم جدا نمی‌کند. اگر برایتان مهم است که سگمنت واقعا آنجا بوده، اول GET بگیرید.

سه چیزی که وجود ندارد و شاید انتظارش را داشته باشید. هیچ If-Match و هیچ نشانه نسخه‌ای نیست، پس دو نویسنده هم‌زمان بی‌سروصدا روی هم می‌نویسند. هیچ کلید یکتاسازی درخواست پذیرفته نمی‌شود. و حذف هیچ‌وقت به‌خاطر «در حال استفاده» رد نمی‌شود: حذف مخاطبی که یک کمپین زمان‌بندی‌شده به آن اشاره می‌کند، موفق می‌شود.

حذف در واقع بایگانی است. سطر می‌ماند و فقط archived_at پر می‌شود. کمپینی که پیش از این اجرا شده به این تعریف ارجاع می‌دهد، و گزارشی که نمی‌تواند بگوید یک ارسال به چه کسانی رفته، از یک فهرست کمی بلندتر بدتر است.

GET /v1/segments روی API مدیریت هم صفحه‌بندی ندارد. سقف ثابت ۲۰۰ سطر مرتب بر updated_at نزولی است و پارامترهای limit و cursor نادیده گرفته می‌شوند. چون همان handler پنل است، پاکت صفحه‌بندی استاندارد را هم ندارد و پاسخ {"segments": [...]} است. تعریف کامل هر سگمنت در هر سطر فهرست می‌آید.

این سقف بی‌سروصداست و همین از خود سقف بدتر است. در پاسخ نه has_more هست، نه next_cursor و نه تعداد کل. حسابی که ۲۵۰ سگمنت دارد ۲۰۰ تای تازه‌تر را می‌بیند و هرچه بگردد باز همان ۲۰۰ تاست. روی هیچ‌کدام از دو سطح مسیری وجود ندارد که به آن ۵۰ تای دیگر برسد. تنها چیزی که یکی از آن‌ها را برمی‌گرداند توی دید، ویرایش کردنش است، چون ذخیره updated_at را می‌نویسد. اگر بیشتر از ۲۰۰ مخاطب دارید، فهرست شناسه‌هایشان را خودتان نگه دارید: GET /v1/segments/{id} هرکدام را با شناسه می‌آورد و سقف ندارد.

#از پنل

پنل چهار مسیر جداگانه دارد که فقط از خود پنل قابل دسترسی‌اند. تفاوت‌های معنادارشان با API مدیریت:

  • POST /v1/segments یک upsert است: id غیرصفر یعنی به‌روزرسانی، id غایب یعنی ساخت. پاسخ در هر دو حالت 200 {"id": 11} است، نه ۲۰۱.
  • فیلد kind را می‌پذیرد، پس فهرست ثابت فقط از این راه ساخته می‌شود. مقدار خارج از سه‌تایی مجاز یعنی 400 {"error": "unknown segment kind"}.
  • برای نوع static، جمله توصیفی با یک متن ثابت جایگزین می‌شود: «فهرست دستی، اعضا را خودتان اضافه می‌کنید». اگر این کار را نمی‌کرد، تعریف تهی به «همه کاربران» توصیف می‌شد و یک فهرست دستی چهل هزار نفره روی صفحه به‌عنوان کل پایگاه کاربران برچسب می‌خورد.
  • نام تکراری خطای قید یکتایی می‌دهد که به شکل 503 {"error": "could not save segment"} برمی‌گردد، نه ۴۰۹ و نه ۴۰۰ راهنما.
  • سقف بدنه 1 MiB است، در حالی که روی API مدیریت 8 MiB است.

#فهرست ثابت و اعضایش

فهرست ثابت جایی است که کسی آدم‌ها را در آن گذاشته: فایل اکسل یک آژانس، گزارش تسویه، برنده‌های یک قرعه‌کشی. سه مسیر دارد و هیچ‌کدام روی API مدیریت نیستند؛ فقط از پنل.

متد و مسیرمجوز
GET /v1/segments/{id}/memberssegment.read
POST /v1/segments/{id}/memberssegment.write
DELETE /v1/segments/{id}/members/{user_id}segment.write

GET فقط {"size": 4670} می‌دهد و نوع سگمنت را بررسی نمی‌کند، پس یک سگمنت پویا اینجا size: 0 گزارش می‌شود و نه خطا.

بدنه افزودن یک فیلد دارد و شناسه کاربر و شماره موبایل و ایمیل را قاطی می‌پذیرد:

JSON
{"identifiers": ["09123456789", "ali@example.ir", "u-42", "۰۹۱۲۳۴۵۶۷۸۹"]}

قاطی بودن عمدی است: یک فایل اکسل یک ستون دارد و بازاریاب می‌داند کدام است؛ پرسیدنش یک فیلد اضافه می‌شد که اشتباه پر می‌کنند. ارقام فارسی به لاتین و شماره‌ها به E.164 نرمال می‌شوند. هر مقدار اول به‌عنوان شناسه کاربر و بعد به‌عنوان شماره یا ایمیل امتحان می‌شود، چون مشتری‌ای که شناسه کاربرهایش شماره موبایل است در ایران به‌اندازه کافی رایج هست.

پاسخ:

JSON
{"added": 38210, "unmatched": ["09120000000"], "truncated": false, "size": 41902}
  • سقف هر درخواست ۵۰۰۰۰ شناسه است. بیشتر از آن بریده می‌شود و truncated: true برمی‌گردد. فایل بزرگ‌تر از راه ورودی CSV می‌رود.
  • unmatched همان شناسه‌ها را برمی‌گرداند، نه شمارششان، چون «۳۴۱۲ تا از ۴۰۰۰۰ نخورد» عددی است که کسی باید رویش کاری بکند و نمی‌تواند؛ او سطرها را لازم دارد تا با فایل خودش بسنجد. سقف این فهرست ۱۰۰ عضو است.
  • شناسه‌ای که به بیش از یک پرونده بخورد رد می‌شود و در unmatched گزارش می‌شود.
  • تکراری‌های داخل یک درخواست روی هم می‌افتند.
  • افزودن به سگمنتی که ثابت نیست: 409 با متن «این سگمنت با شرط تعریف شده است؛ فقط به فهرست ثابت می‌شود کاربر اضافه کرد».
  • فهرست تهی: 400 با متن «فهرست خالی است».

حذف یک عضو، یک mutation از نوع ALTER TABLE ... DELETE روی ClickHouse است و طبق طراحی کند است.

#بازمحاسبه عضویت

برای سگمنت پویا هیچ عضویت ذخیره‌شده‌ای وجود ندارد و هیچ کار بازمحاسبه‌ای هم وجود ندارد. تعریف، همان سگمنت است و هر بار تازه اجرا می‌شود.

نتیجه‌های عملی این تصمیم:

  • last_size همیشه صفر است و last_computed_at همیشه غایب. تابعی که این دو را می‌نویسد در کد هست و هیچ فراخوانی‌ای ندارد، پس روی نصب واقعی این ستون‌ها تا ابد خالی می‌مانند. کارت سگمنت در پنل به همین دلیل همیشه «در انتظار محاسبه» می‌نویسد و انتخابگر مخاطب کمپین هیچ‌وقت تعداد نفرات را نشان نمی‌دهد.
  • ستون refresh_cron در اسکیمای دیتابیس هست و هیچ کدی آن را نه می‌خواند و نه می‌نویسد. زمان‌بندی بازمحاسبه وجود ندارد.
  • مصرف‌کننده‌ها تعریف را زنده حل می‌کنند. کمپین در لحظه ارسال، کوئری کامپایل‌شده را صفحه‌به‌صفحه می‌خواند و اندازه‌اش را با تخمین می‌گیرد و زیر ۵۰۰۰ نفر به شمارش دقیق برمی‌گردد. شرط داخل سناریو تعریف را روی یک شناسه کاربر باریک می‌کند و می‌شمارد.

تنها جایی که عضویت یک سگمنت پویا به‌خاطر سپرده می‌شود، تریگر سناریو است. اسکنر سگمنت را صفحه‌به‌صفحه می‌خواند، با اسکن قبلی تفاضل می‌گیرد و همان تفاضل را وارد سناریو می‌کند: segment_enter تازه‌واردها را و segment_exit خارج‌شده‌ها را. اولین اسکن بعد از انتشار سناریو فقط عضویت را ثبت می‌کند و هیچ‌کس را وارد نمی‌کند، وگرنه انتشار یک بازگردانی برای ۴۰۰۰۰۰ مشتری خفته یعنی هر ۴۰۰۰۰۰ نفر در پنج دقیقه بعد پیام می‌گیرند. سگمنت بزرگ‌تر از ۲۵۰۰۰۰ عضو بریده می‌شود و بریدگی هم در لاگ و هم روی سطر وضعیت تریگر ثبت می‌شود.

برای فهرست ثابت، عضویت همان سطرهایی است که نوشته‌اید. جدول یک ReplacingMergeTree است که روی یک ستون نسخه با دقت نانوثانیه کلید خورده و هر خواندنی FINAL دارد، پس افزودن تکراری روی هم می‌افتد.

#محدودیت‌ها و پیش‌فرض‌ها

چیزمقدار
بیشترین عمق تودرتویی۸ (ریشه عمق صفر است، پس ۹ سطح)
بیشترین تعداد گره۲۰۰، شامل هر عضو properties
بیشترین اعضای فهرست in۱۰۰۰
بیشترین طول نام ویژگی، رویداد و کلید۱۲۸ بایت، حدود ۶۴ حرف فارسی
amount در پنجره نسبیاز ۱ تا ۱۰۰۰۰
نرخ نمونه‌گیری تخمینیک در ۱۰۰
مهلت تخمین۳ ثانیه
مهلت کوئری (شمارش، پیش‌نمایش، ذخیره)۳۰ ثانیه
آستانه بازگشت به شمارش دقیقتخمین زیر ۳۰۰۰
سطرهای پیش‌نمایشپیش‌فرض ۱۰، سقف ۱۰۰
شناسه در هر درخواست افزودن عضو۵۰۰۰۰
شناسه‌های نخورده در پاسخ۱۰۰
سقف بدنه روی API مدیریت8 MiB
سقف بدنه روی مسیرهای پنل1 MiB
سقف فهرست سگمنت‌های ذخیره‌شده۲۰۰ سطر، بدون صفحه‌بندی
سقف اسکن تریگر سناریو۲۵۰۰۰۰ عضو
هزینه بودجه: validate و CRUD سگمنت۱ واحد
هزینه بودجه: count۲۵ واحد

مجوزهای مربوط: segment.read، segment.write، segment.delete، profile.read، event.read. نقش‌های مالک، ادمین و بازاریاب هر سه مجوز سگمنت را دارند. تحلیلگر، بیننده و تأییدکننده فقط segment.read دارند. بیننده profile.read ندارد، پس می‌تواند مخاطب را بشمارد ولی نمی‌تواند پیش‌نمایشش را ببیند. جزئیات محدودیت نرخ در سقف‌ها است.

#متن دقیق خطاهای کامپایلر

این نه خطا همه چیزی هستند که کامپایل می‌تواند برگرداند. متنشان انگلیسی است و ترجمه نمی‌شود، و بی‌کم‌وکاست در فیلد message به شما می‌رسد.

متن پایهچه وقت
segment: unknown node kindkind تهی یا ناشناس
segment: group has no childrenآرایه children خالی
segment: nesting too deepبیشتر از ۹ سطح
segment: too many conditionsبیشتر از ۲۰۰ گره و شرط ویژگی
segment: invalid identifierنام ویژگی، رویداد، کلید یا تابع تجمیع نامعتبر؛ یا segment_id صفر؛ یا گروه یا سنجه ناشناخته
segment: unsupported operatorعملگر خارج از فهرست، یا عملگر غیرعددی روی تعامل و ریزش، یا هر چیزی جز is_set روی birthday
segment: operator requires a valuevalue نیامده، یا فهرست in تهی است
segment: invalid time windowkind یا unit ناشناخته، amount بیرون از بازه، یا کران‌های ناقص
segment: list has too many valuesبیشتر از ۱۰۰۰ عضو در فهرست

اغلبشان با مقدار مقصر بسته‌بندی می‌شوند:

segment: unknown node kind: "wat"
segment: invalid identifier: trait "  "
segment: invalid time window: unit "fortnight"
segment: unsupported operator: engagement needs a numeric operator, got "contains"

اینها کد ماشینی پایدار نیستند. تنها کد پایداری که روی API مدیریت به آن تکیه کنید filter_invalid در پاکت خطاست. شرح کامل پاکت در خطاها است.

#چیزهایی که وجود ندارند

هر کدام از اینها چیزی است که یک مشتری منطقی دنبالش می‌گردد و در این محصول نیست. جمله موجه به‌جای این فهرست، یک بعدازظهر از وقت شما می‌گرفت.

  • مقایسه تاریخ روی مقدار یک ویژگی. type: "date" پذیرفته و نادیده گرفته می‌شود. به‌جایش days_until_birthday و days_until_signup_anniversary.
  • توابع first و last در تجمیع، و هر راهی برای شرط گذاشتن روی مقدار ویژگی در اولین یا آخرین رخداد یک رویداد. نزدیک‌ترین چیز موجود days_since_last_seen و days_since_last_order است که تازگی را جواب می‌دهند نه مقدار را.
  • مقایسه متنی دو ویژگی با هم. compare_trait هر دو طرف را عدد می‌گیرد و ستون متنی در هر طرف با نام خودش رد می‌شود. در مقایسه دو ویژگی.
  • شرط ترتیبی («اول A بعد B»). شرط‌های رویداد زیرکوئری‌های مستقل‌اند که با AND به هم وصل می‌شوند.
  • شمارش مقدارهای یکتا. count() سطر می‌شمارد.
  • منطقه زمانی روی پنجره. همه‌چیز UTC است.
  • تاریخ جلالی روی سیم. صریح رد شده است؛ جلالی فقط در جمله توصیفی.
  • not روی گره trait، event یا segment. فقط گروه و تعامل و ریزش این فیلد را می‌خوانند.
  • OR بین شرط‌های ویژگی یک رویداد. همیشه AND.
  • کار بازمحاسبه عضویت برای سگمنت پویا.
  • پیاده‌سازی نوع realtime. مقدار پذیرفته و ذخیره می‌شود و هیچ‌چیز به آن عمل نمی‌کند.
  • ساخت فهرست ثابت از API مدیریت. بدنه‌اش فیلد kind ندارد.
  • مسیرهای عضویت فهرست ثابت روی API مدیریت. فقط پنل.
  • مسیر تخمین و مسیر پیش‌نمایش فیلتر موقت روی API مدیریت.
  • صفحه‌بندی روی GET /v1/segments روی هیچ‌کدام از دو سطح.
  • هم‌زمانی خوش‌بینانه روی نوشتن سگمنت. نه If-Match، نه نشانه نسخه.
  • رد کردن حذف یا ویرایش سگمنتی که در حال استفاده است.
  • کلید یکتاسازی روی ساخت سگمنت.
  • فهرست قالب‌های آماده روی سرور. ۹ قالب پنل ثابت‌های TypeScript داخل بسته مرورگرند و هیچ مسیری آن‌ها را برنمی‌گرداند.
  • اعتبارسنجی نام رویداد در زمان کامپایل. همان تله است.
  • ویرایشگر ویژگی رویداد، ویرایشگر تجمیع، مقایسه یک ویژگی با ویژگی دیگر، و شرط عضویت در سگمنت، در پنل. هر چهار در زبان هستند و فقط از API نوشته می‌شوند.

اگر با ایجنت هوش مصنوعی کار می‌کنید، سه ابزار MCP برای همین صفحه وجود دارد: فهرست مخاطب‌ها، توصیف یک فیلتر و شمارش یک فیلتر. جزئیات در MCP.

قبلیکاتالوگ محصولاتبعدیسناریو

در این صفحه

  • شکل کلی یک تعریف
  • گروه: and، or و not
  • شرط روی ویژگی پرونده
  • شرط روی رویداد
  • عضویت در سگمنت دیگر
  • تعامل
  • ریسک ریزش
  • عملگرها
  • پنجره زمانی
  • تله: نام رویداد غلط، کامپایل تمیز و مخاطب صفر
  • هشت مثال کامل
  • سنجیدن مخاطب: کدام فراخوانی چه چیزی می‌دهد
  • سگمنت ذخیره‌شده: ساخت، ویرایش، حذف
  • فهرست ثابت و اعضایش
  • بازمحاسبه عضویت
  • محدودیت‌ها و پیش‌فرض‌ها
  • متن دقیق خطاهای کامپایلر
  • چیزهایی که وجود ندارند

سگمنتیک

این صفحه از روی کد نوشته شده است