مصنوعی کنٹرول کے ساتھ پروڈکٹ کا تجربہ: ازگر میں عالمی ایل ایل ایم لانچ کے لیے وجہ کا اندازہ۔

LLM پر مبنی خصوصیات پر کارآمد اندازہ لگانے والی کوئی بھی مصنوعات کی تجربہ کار ٹیم بالآخر اسی دیوار سے ٹکرائے گی۔ اس کا مطلب یہ ہے کہ جب کوئی سپلائر نیا ماڈل ورژن جاری کرتا ہے، تو مزاحمت کی کوئی گنجائش نہیں ہوتی۔

انفراسٹرکچر ٹیم راتوں رات کلاڈ 4.5 سے کلاڈ 4.6 تک تمام ورک اسپیس کو اپ گریڈ کرتی ہے۔ نیا ماڈل تمام 50 پروڈکشن ورک اسپیس پر بیک وقت لاگو ہوتا ہے۔ ایک ہفتے کے بعد، کام کی تکمیل مجموعی طور پر بڑھ جاتی ہے۔ پروڈکٹ کا مالک اسے جیت کہتا ہے۔

لیکن میں جانتا ہوں کہ کچھ غلط ہے۔ اپ گریڈ ہفتے کے دوران 4.5 چلانے والے کوئی ہول آؤٹ گروپ نہیں تھے۔ پہلے/بعد میں سادہ لوح ہر وہ چیز اٹھا لیتا ہے جو اس ہفتے ماڈل کے ساتھ تبدیل ہوئی: نئی آن بورڈنگ فلو، موسمی ترقی، گرم کسٹمر آن بورڈنگ وغیرہ۔

یہ ایک عالمی رول آؤٹ مسئلہ ہے۔ ظاہر ہوتا ہے جب بھی کوئی ٹیم اپنے پورے صارف کی بنیاد پر ایک ساتھ ماڈل اپ گریڈ فراہم کرتی ہے۔ جنریٹیو AI صلاحیتوں کو چلانے والی پروڈکٹ ٹیموں کے لیے، یہ اسٹیک میں پیمائش کے سب سے عام نقصانات میں سے ایک ہے۔ ایک مرحلہ وار رول آؤٹ آپ کو کنٹرول گروپس حاصل کرنے میں مدد کر سکتا ہے اور عالمی رول آؤٹ انہیں ختم کر سکتا ہے۔

عالمی ماڈل اپ گریڈ 2026 میں معیاری ہوں گے۔ ہر API فراہم کنندہ ایک نیا ورژن پیش کرتا ہے، اور کلاڈ، GPT، یا جیمنی استعمال کرنے والی ہر ٹیم نے آپٹ آؤٹ کیے بغیر ایک ورژن سے دوسرے ورژن میں اچانک چھلانگ کا تجربہ کیا ہے۔

مصنوعی کنٹرول ایک ایسا آلہ ہے جو ڈیٹا سائنسدانوں کے ذریعہ استعمال ہوتا ہے جب کنٹرول گروپ غائب ہوتا ہے۔ غیر علاج شدہ یونٹس (اور دیگر کام کی جگہیں یا علاقے جو ایک ہی وقت میں اپ گریڈ نہیں کیے گئے تھے) کا وزنی مجموعہ بنائیں جن کا پری اپ گریڈ سلوک علاج شدہ یونٹس سے ملتا ہے۔ اگر ہم علاج شدہ ڈیوائس کا موازنہ اس کے مصنوعی جڑواں سے اپ گریڈ کرنے کے بعد کرتے ہیں، تو فرق ایک وجہ تخمینہ ہے جو تین شناختی مفروضوں پر مشروط ہے جنہیں ہم واضح طور پر نام دیں گے۔

اس ٹیوٹوریل میں، ہم استعمال کرتے ہوئے ازگر میں شروع سے ایک جامع کنٹرول بناتے ہیں: scipy.optimizeہم اسے 50,000 صارفین کے ساتھ مصنوعی SaaS ڈیٹاسیٹ پر لاگو کرتے ہیں اور پلیسبو پرموٹیشن ٹیسٹ، ایک بار ڈونر کی حساسیت، اور کلسٹر بوٹسٹریپ 95 فیصد اعتماد کے وقفوں کا استعمال کرتے ہوئے اس کی توثیق کرتے ہیں۔

ساتھی کوڈ: تمام کوڈ بلاکس ساتھی نوٹ بک میں github.com/RudrenduPaul/product-experimentation-causal-inference-genai-llm/tree/main/04_synthetic_control پر اینڈ ٹو اینڈ چلائے جاتے ہیں۔ لیپ ٹاپ (synthetic_control_demo.ipynb) میں تمام آؤٹ پٹ پری رن ہے، لہذا آپ اسے مقامی طور پر چلانے سے پہلے GitHub سے پڑھ سکتے ہیں۔

انڈیکس

کیوں گلوبل لانچ نے بولی پیمائش کو توڑ دیا۔

A/B ٹیسٹنگ کی ریاضی ایک مفروضے کی وجہ سے خوبصورت ہے: پروسیسنگ مختص ہر چیز سے آزاد ہے۔ ایک سکے کو پلٹائیں: آپ کے ورک اسپیس کا آدھا حصہ Claude 4.6 استعمال کرتا ہے اور باقی آدھا 4.5 کے ساتھ رہتا ہے۔ سکے کو پلٹنے سے تمام ممکنہ الجھنیں ٹوٹ جاتی ہیں۔ عالمی لانچ کی دنیا میں کوئی سکے نہیں ہیں۔

تین طریقہ کار پہلے اور بعد کے بارے میں غلط فہمی کا باعث بن سکتے ہیں۔

  1. سمورتی مصنوعات کی تبدیلیاں: ماڈل اپ گریڈ کی ترسیل شاذ و نادر ہی تنہائی میں کی جاتی ہے۔ اسی ہفتے میں، آن بورڈنگ ٹیم دوبارہ ڈیزائن کردہ سبق فراہم کرتی ہے، قیمتوں کا تعین کرنے والی ٹیم پروموشنز چلاتی ہے، اور کسٹمر کی کامیابی کی کہانیاں نئی ​​خصوصیات کے بارے میں انٹرپرائز اکاؤنٹس تک پہنچتی ہیں۔ اپنے کل سے پہلے اور بعد کا انتخاب کریں۔

  2. موسمی اور مارکیٹ کے اتار چڑھاو: ہفتہ وار استعمال کے پیٹرن، ماہانہ بلنگ سائیکل، اور سہ ماہی پروکیورمنٹ سائیکل نتائج کے میٹرکس کو منتقل کرتے ہیں۔ ہفتہ 20 میں 3pp کا اضافہ ایک ماڈل اپ گریڈ کی طرح لگتا ہے، لیکن یہ دراصل صرف موسم بہار کے وقفے سے واپس آنے والے صارفین ہیں۔

  3. کولیگ کمپنی ڈائنامکس: جب کوئی مدمقابل ایک چھوٹی چھوٹی اپڈیٹ جاری کرتا ہے، تو صارفین ایک ہفتے کے لیے ہجرت کرتے ہیں۔ ملازمت کی تکمیل کی شرحیں آسمان کو چھوتی ہیں کیونکہ نئے صارفین خود ماڈل کے کسی تعاون کے بغیر زیادہ آسانی سے سوالات کر سکتے ہیں۔

تینوں ایک جیسی علامات کا سبب بنتے ہیں۔ پرائمل پہلے/بعد میں اپ گریڈ کے سبب اثرات اور دو ہفتہ وار 20-ہفتوں کے واقعات کے سبب اثرات کو جوڑ دیتے ہیں۔

اس ٹیوٹوریل کے ڈیٹا سیٹ میں، Naïve gap +0.0515 ہے، جو کہ زمینی سچائی +0.05 کے تقریباً برابر ہے۔ یہ اتفاقات سب سے خوفناک ناکامی کا موڈ ہیں۔ بے ہودہ نمبر کبھی کبھی اتفاقاً درست طریقے سے پہنچ جاتے ہیں، اور جوابی حقائق کے بغیر سچائی اور قسمت میں فرق کرنا ناممکن ہے۔

مصنوعی کنٹرول اصل میں کیا کرتا ہے؟

شکل 1 (اوپر): مصنوعی کنٹرول کنفیگریشن کا اسکیمیٹک۔ گرے کریو فراہم کنندہ کی ورک اسپیس ہے جو پچھلے ماڈل سے باقی ہے۔ نیلے نقطے والا وکر عطیہ دہندگان کا وزنی مجموعہ ہے جو ایکس محور کے نیچے نیلے بریکٹ میں اشارہ کردہ پری ٹریٹمنٹ مدت کے دوران علاج شدہ یونٹس (سرخ) کو بہترین طریقے سے ٹریک کرتا ہے۔

علاج کی تاریخ (ہفتہ 20، ڈیشڈ عمودی لکیر) کے بعد، وزن مقرر رہتا ہے اور نقطے والا منحنی خطوط متضاد کے طور پر آگے بڑھتا ہے، جبکہ علاج شدہ یونٹ اوپر کی طرف بڑھتے ہیں۔ علاج کے بعد کی کھڑکی میں دو منحنی خطوط کے درمیان وقفہ وجہ اثر کا تخمینہ ہے۔

اعداد و شمار میں واضح کردہ کلیدی ڈیزائن کا انتخاب یہ ہے کہ وزن صرف پہلے سے تیار کردہ ڈیٹا کا استعمال کرتے ہوئے ایک بار فٹ ہوجاتا ہے اور دوبارہ پوسٹ پروسیس شدہ ڈیٹا کا استعمال نہیں کرتا ہے۔

جامع کنٹرول غیر پروسیس شدہ اکائیوں کا ایک وزنی امتزاج تلاش کرتا ہے جس کے نتیجے میں چلنے والی رفتار پروسیسنگ مدت میں پروسیس شدہ اکائیوں سے قریب سے ملتی ہے۔ ایک بار وزن طے ہوجانے کے بعد، ہم جامع اکائیوں کی رفتار کو پوسٹ پروسیسنگ پیریڈ میں پیش کرتے ہیں اور دو لائنوں کے درمیان فرق کو پڑھتے ہیں۔

AI پروڈکٹ سیاق و سباق میں: ہر ویو-2 ورک اسپیس امیدوار ڈونر ہے اگر ویو-2 ورک اسپیس کو ویو-1 ورک اسپیس کے ساتھ ہی ماڈل اپ گریڈ نہیں ملتا ہے۔ آپٹمائزر لہر 2 ورک اسپیس کا مجموعہ تلاش کرتا ہے جن کے وزنی پری اپ گریڈ ٹریجیکٹرز لہر 1 سے بہترین میچ کرتے ہیں۔ ہفتہ 20 کے بعد (جب پہلی لہر کو اپ گریڈ کیا جاتا ہے)، پہلی لہر اور جامع جڑواں کے درمیان وقفہ کارآمد اثر کا تخمینہ ہے اگر مندرجہ ذیل تین شناختی مفروضوں کو برقرار رکھا جائے:

یہ شناختی مفروضے مل کر کام کرتے ہیں۔

  • سب سے پہلے حیض سے پہلے موزوں ہے۔ (Convex Hull Condition): علاج شدہ ڈیوائس کی پہلے سے پروسیس شدہ رفتار عطیہ کنندہ کے محدب ہول کے اندر ہونی چاہیے، غیر منفی اور جمع سے ایک کی رکاوٹوں کے ساتھ۔

  • دوسرا عطیہ دہندگان کے ساتھ کوئی مداخلت نہیں۔ (ڈونر پول کے لیے SUTVA): علاج شدہ یونٹس کے علاج سے عطیہ دہندگان کو متاثر نہیں کرنا چاہیے۔ مشترکہ API کی شرح کو محدود کرنے والے پول یا ورک اسپیس کے درمیان منتقل ہونے والے کسی بھی صارف کو اس مسئلے کا سامنا کرنا پڑے گا۔

  • تیسرا مستحکم ڈونر کمپوزیشن: عطیہ دہندگان کو مدت کے بعد علاج سے غیر متعلق ساختی نقصان کا سامنا نہیں کرنا چاہئے۔ اگر دونوں میں سے کسی ایک کی بھی خلاف ورزی کی جاتی ہے، تو اس فرق کو یک طرفہ کر دیا جائے گا چاہے ماہواری سے پہلے کا فٹ بالکل درست نظر آئے۔ ناکامی کے طریقوں کا سیکشن ہر ایک کے بارے میں آپ کی رہنمائی کرتا ہے۔

ایک ہندسی نوٹ: T₀ سے پہلے علاج کی مدت اور J عطیہ دہندگان کے استعمال کے نتیجے میں J T₀ کے قریب آنے پر شدید پری پیریڈ اوور فٹنگ کا نتیجہ ہوتا ہے۔ یہ ٹیوٹوریل T₀ = 20 اور J = 25 کے ساتھ چلتا ہے، جو خطرے کے علاقے میں ہے۔ LOO کی حساسیت کے بعد کے اقدامات اس بات کی ایک اچھی تشخیص ہیں کہ آیا فٹ صحیح موازنہ کی عکاسی کرتا ہے یا زیادہ فٹنگ۔

شرائط

Python 3.11 یا اس سے زیادہ، پانڈا اور numpy سے واقفیت، اور بنیادی محدود اصلاح سے واقفیت کی ضرورت ہے۔

اس ٹیوٹوریل کے لیے پیکجز انسٹال کریں۔

pip install numpy pandas scipy matplotlib

موجودہ صورتحال کچھ یوں ہے: چار پیکج پوری پائپ لائن کا احاطہ کرتے ہیں۔ پانڈاس صارف کی سطح کے لوگارتھمز کو لوڈ کرتا ہے، NumPy پینل ریاضی کو ہینڈل کرتا ہے، SciPy عطیہ دہندگان کے وزن پر محدب امتزاج کی رکاوٹوں کو نافذ کرنے کے لیے ایک SLSQP حل فراہم کرتا ہے، اور matplotlib ٹریکٹری پلاٹ اور پلیسبو تقسیم کو پیش کرتا ہے۔

مصنوعی ڈیٹاسیٹ حاصل کرنے کے لیے، ساتھی ریپوزٹری کو کلون کریں۔

git clone https://github.com/RudrenduPaul/product-experimentation-causal-inference-genai-llm.git
cd product-experimentation-causal-inference-genai-llm
python data/generate_data.py --seed 42 --n-users 50000 --out data/synthetic_llm_logs.csv

موجودہ صورتحال کچھ یوں ہے: کلون ساتھی ذخیرہ درآمد کرتا ہے اور generate_data.py ایک مشترکہ مصنوعی ڈیٹاسیٹ تخلیق کرتا ہے جو پوری سیریز میں استعمال ہوتا ہے۔ سیڈ 42 ڈیٹاسیٹ کو دوبارہ پیدا کرنے کے قابل رکھتا ہے اور 50,000 صارفین اس ٹیوٹوریل میں تخمینہ لگانے والے کے لیے واضح اشارہ فراہم کرتے ہیں۔ آؤٹ پٹ CSV اس پر پہنچے گا: data/synthetic_llm_logs.csv.

ورکنگ مثال سیٹ اپ

مصنوعی ڈیٹاسیٹ 50 ورک اسپیس میں تقسیم کیے گئے 50,000 صارفین کے ساتھ SaaS پروڈکٹ کی نقل کرتا ہے۔ ورک اسپیسز 0-24 ویو 1 میں ہیں، جس نے ہفتہ 20 میں ماڈل اپ گریڈ کیا ہے۔ ورک اسپیسز 25 سے 49 ویو 2 میں ہیں، پچھلے ماڈل کو ہفتہ 29 تک برقرار رکھا ہوا ہے۔

ڈیٹا جنریٹر میں ظاہر ہونے والا اصل کازل اثر علاج کے بعد کی مدت میں Wave-1 کے صارفین کے لیے کام کی تکمیل کی شرح میں +5 فیصد پوائنٹ اضافہ تھا۔ چونکہ آپ سچ جانتے ہیں، آپ دیکھ سکتے ہیں کہ مصنوعی کنٹرول کیا بحال کرتے ہیں۔

ڈیٹا لوڈ کریں اور اسے ہفتہ وار ورک اسپیس پینل میں جمع کریں۔

import numpy as np
import pandas as pd

df = pd.read_csv("data/synthetic_llm_logs.csv")

PRE = 20         # weeks 0-19 are pre-treatment
WINDOW = 30      # analysis window weeks 0-29

df_window = df[df.signup_week < WINDOW].copy()

panel = (
    df_window.groupby(["workspace_id", "signup_week"])
    ["task_completed"].mean().reset_index()
)
panel.columns = ["workspace_id", "week", "task_completed"]

pivot = panel.pivot(
    index="week", columns="workspace_id", values="task_completed"
)
pivot = pivot.interpolate(method="linear", axis=0).ffill().bfill()

ws_wave = df.groupby("workspace_id").wave.first()
wave1_ws = sorted(ws_wave[ws_wave == 1].index.tolist())
wave2_ws = sorted(ws_wave[ws_wave == 2].index.tolist())

treated_series = pivot[wave1_ws].mean(axis=1).values
donor_matrix = pivot[wave2_ws].values

print(f"Treated series shape: {treated_series.shape}")
print(f"Donor matrix shape:   {donor_matrix.shape}")
print(f"Users per workspace-week: ~{len(df_window) / (50 * WINDOW):.1f}")
print(f"Pre-period treated mean  (weeks 0-19):  {treated_series[:PRE].mean():.4f}")
print(f"Post-period treated mean (weeks 20-29): {treated_series[PRE:].mean():.4f}")

متوقع پیداوار:

Treated series shape: (30,)
Donor matrix shape:   (30, 25)
Users per workspace-week: ~19.2
Pre-period treated mean  (weeks 0-19):  0.5927
Post-period treated mean (weeks 20-29): 0.6421

موجودہ صورتحال کچھ یوں ہے: اسے 30 ہفتہ کی مدت تک محدود کریں، صارف کی قطاروں کو ہفتہ وار ورک اسپیس پینل میں جمع کریں، اور اس کی نئی شکل دیں تاکہ قطاریں ہفتے ہوں اور کالم ورک اسپیس ہوں۔ انٹرپولیشن غائب خلیوں میں بھرتا ہے (ہر سیل میں صارفین کی اوسط تعداد تقریباً 19 ہے)۔ پروسیس شدہ سیریز 25 Wave-1 ورک اسپیس میں اوسط ہے، جو سیل کی سطح کے شور کو کم کرنے کے لیے فی ہفتہ تقریباً 480 صارفین کو جمع کرتی ہے۔

ڈونر میٹرکس ہر لہر 2 ورک اسپیس کو الگ کالم کے طور پر برقرار رکھتا ہے۔ ہر کالم 0 سے 29 ہفتوں پر محیط 25 ٹائم سیریز ہے۔ پری پیریڈ ٹریٹمنٹ کا مطلب 0.5927 اور پوسٹ پیریڈ ٹریٹمنٹ کا مطلب 0.6421 +5.15 ​​پی پی کا خام پری پوسٹ وقفہ پیدا کرتا ہے۔ یہ اصل +5pp کے قریب ہوتا ہے اور 20-29 ہفتوں میں منتقل ہونے والی ہر چیز سے آلودہ ہوتا ہے۔

9b5d9711-9632-41ec-9c38-5ad531ca676f

شکل 2: حقیقی 50,000 صارف ڈیٹاسیٹ پر تشخیص۔ ٹاپ پینل: نیوی ڈیشڈ لائن میں دکھایا گیا سرخ اور مصنوعی کنٹرول میں لہر 1 کی رفتار، مدت سے پہلے RMSE 3.74pp اور بعد از علاج وقفہ اوسط +8.29pp ہے۔ نیچے کا پینل: پلیسبو کی تقسیم 25 ڈونر ورک سٹیشنوں میں سے ہر ایک میں مصنوعی کنٹرول کو دوبارہ ترتیب دے کر بنائی گئی ہے جو پلیسبو ٹریٹمنٹ یونٹ کے طور پر کھڑے ہیں۔ مشاہدہ کیا گیا خلا مکمل پلیسبو رینج سے باہر ہے، جو مرحلہ 3 میں ایک جعلی p-ویلیو کی طرف لے جاتا ہے۔

شکل 1 منصوبہ بندی کے طور پر طریقہ کو ظاہر کرتا ہے، لیکن اعداد و شمار سے پتہ چلتا ہے کہ یہ ایک پلیسبو تقسیم پیدا کرتا ہے جو مشاہدہ شدہ اثر کو پری پیریڈ فٹ کے ساتھ شور سے ممتاز کرتا ہے جو بعد کے وقفوں کی تشریح کی اجازت دینے کے لیے کافی قریب ہے۔

مرحلہ 1: ڈونر کے وزن کو SLSQP کے ساتھ جوڑیں۔

جامع کنٹرول وزن ویکٹر w ایک محدود اصلاحی مسئلہ کا حل۔ علاج شدہ اور عطیہ دہندگان کی سیریز کے وزنی امتزاج کے درمیان پری پیریڈ یعنی مربع غلطی کو کم کرتا ہے۔ [0, 1] تمام وزنوں کا مجموعہ 1 ہے۔ غیر منفی حالت اور sum-1 رکاوٹ ایک ساتھ مل کر محدب مجموعہ کی وضاحت کرتے ہیں۔ یہ ڈونر پول کے تعاون سے باہر ایکسٹرپولیشن کو روکنے کے لئے ہے۔

from scipy.optimize import minimize

n_donors = len(wave2_ws)
Y_pre = treated_series[:PRE]
D_pre = donor_matrix[:PRE, :]

def objective(w):
    return np.mean((Y_pre - D_pre @ w) ** 2)

w0 = np.ones(n_donors) / n_donors
bounds = [(0, 1)] * n_donors
constraints = [{"type": "eq", "fun": lambda w: w.sum() - 1}]

result = minimize(
    objective, w0, method="SLSQP", bounds=bounds,
    constraints=constraints,
    options={"ftol": 1e-12, "maxiter": 5000},
)
w_opt = result.x

pre_mse = float(np.mean((Y_pre - D_pre @ w_opt) ** 2))
pre_rmse = float(np.sqrt(pre_mse))
nz = int((w_opt > 0.001).sum())

print(f"Optimization converged: {result.success}")
print(f"Non-zero donor weights (|w| > 0.001): {nz}")
print(f"Pre-period MSE:  {pre_mse:.6f}")
print(f"Pre-period RMSE: {pre_rmse:.4f}  "
      f"({pre_rmse * 100:.2f} percentage points)")

synth_full = donor_matrix @ w_opt
gap = float((treated_series[PRE:] - synth_full[PRE:]).mean())
print(f"nObserved post-period gap: {gap:+.4f}  (ground truth = +0.0500)")

nz_pairs = sorted(
    [(ws, w_opt[i]) for i, ws in enumerate(wave2_ws) if w_opt[i] > 0.001],
    key=lambda x: -x[1]
)
print("nTop 5 donor weights:")
for ws_id, weight in nz_pairs[:5]:
    print(f"  workspace {ws_id}: w = {weight:.4f}")

متوقع پیداوار:

Optimization converged: True
Non-zero donor weights (|w| > 0.001): 12
Pre-period MSE:  0.001400
Pre-period RMSE: 0.0374  (3.74 percentage points)

Observed post-period gap: +0.0829  (ground truth = +0.0500)

Top 5 donor weights:
  workspace 35: w = 0.2016
  workspace 40: w = 0.1900
  workspace 25: w = 0.1638
  workspace 32: w = 0.0872
  workspace 36: w = 0.0784

موجودہ صورتحال کچھ یوں ہے: کہ objective فنکشن وزن کے ویکٹر کا استعمال کرتے ہوئے پروسیس شدہ مدت سے پہلے سیریز اور ڈونر میٹرکس کے ڈاٹ پروڈکٹ کے درمیان اوسط مربع غلطی کا حساب لگاتا ہے۔

SLSQP بیک وقت غیر منفی رینج اور سم ٹو ون برابری کی رکاوٹوں کو ہینڈل کرتا ہے۔ کہ w > 0.001 حد 12 عطیہ دہندگان کو غیر صفر کے طور پر درجہ بندی کرتی ہے۔ SLSQP غیرفعالیت کی رکاوٹوں میں قطعی صفر کی ضمانت نہیں دیتا، لہذا حد ایک نشان زد اصول ہے۔ 3.74 پی پی کا پری پیریڈ RMSE پیمائش کرتا ہے کہ وزن والے عطیہ دہندگان نے اپ گریڈ سے پہلے پروسیس شدہ یونٹس کو کتنی باریک بینی سے ٹریک کیا۔ مشاہدہ شدہ مدت کے بعد وقفہ +0.0829 ہیڈ لائن کا تخمینہ ہے، جو کہ مرحلہ 5 میں اعتماد کے وقفہ کے ذریعہ مقدار کے مطابق اصل +5pp سے زیادہ ہے۔

وزن پہلے کی مدت کے اختتام پر طے کیا جاتا ہے اور پوسٹ پروسیسنگ ڈیٹا کا استعمال کرتے ہوئے دوبارہ تخمینہ نہیں لگایا جاتا ہے۔ ہفتہ 20 کے بعد کوئی بھی فرق ان حرکات کی عکاسی کرتا ہے کہ اصلاح کار کے پاس فٹ ہونے کا موقع نہیں تھا۔

مرحلہ 2: پروسیس شدہ پلاٹوں کا مصنوعی کنٹرول ٹریجیکٹریز سے موازنہ کریں۔

مصنوعی کنٹرول کے لیے بنیادی بصری تشخیصی ٹریجیکٹری اوورلے ہے۔ دونوں سیریز کو ایک ساتھ پلاٹ کریں، علاج کی تاریخوں کو پلاٹ کریں، اور اس بات کو یقینی بنائیں کہ کمپوزٹ کنٹرول ٹریکس یونٹس جن کا علاج پری پیریڈ میں کیا گیا تھا اور بعد کی مدت میں خلا کو کھولتا ہے۔

مدت سے پہلے سخت فٹ ہونا ایک واضح علامت ہے جو شناخت کی شرائط کو برقرار رکھتی ہے۔ بے قاعدہ فٹ ہونے کا مطلب ہے کہ علاج شدہ آلہ عطیہ کنندہ کے محدب ہول سے باہر ہے اور اس کی مجموعی حرکت قابل اعتراض ہے۔

import matplotlib.pyplot as plt

weeks = np.arange(WINDOW)

fig, ax = plt.subplots(figsize=(9, 4.5))
ax.plot(weeks, treated_series, marker="o", linewidth=1.8,
        color="#C44E52", label="Wave 1 (treated)")
ax.plot(weeks, synth_full, marker="s", linestyle="--",
        linewidth=1.8, color="#4C72B0", label="Synthetic control")
ax.axvline(PRE, color="#555555", linestyle=":", linewidth=1.4,
           label="Model upgrade (week 20)")
ax.set_xlabel("Signup week")
ax.set_ylabel("Mean task completion rate")
ax.set_title("Treated unit vs synthetic control")
ax.legend(frameon=False)
plt.tight_layout()
plt.show()

post_gap = treated_series[PRE:] - synth_full[PRE:]
print("Post-period weekly gaps (treated minus synthetic):")
for wk, g in zip(range(PRE, WINDOW), post_gap):
    print(f"  week {wk}: {g:+.4f}")
print(f"nMean gap: {post_gap.mean():+.4f}")

متوقع پیداوار:

Post-period weekly gaps (treated minus synthetic):
  week 20: +0.0398
  week 21: +0.1663
  week 22: +0.1019
  week 23: +0.1535
  week 24: +0.1071
  week 25: +0.1047
  week 26: +0.0424
  week 27: +0.0326
  week 28: +0.0327
  week 29: +0.0479

Mean gap: +0.0829

موجودہ صورتحال کچھ یوں ہے: فٹ مفروضوں کو جانچنے کے لیے دونوں لائنیں پہلے کی مدت میں ایک دوسرے کو ٹریک کرتی ہیں۔ ہفتہ 20 کے بعد، علاج شدہ لائنیں مصنوعی کنٹرول سے زیادہ بڑھ جاتی ہیں اور ہفتہ وار وقفے تمام مثبت ہوتے ہیں، اوسطاً +8.29 pp۔

ہفتہ وار پھیلاؤ (+3.26pp سے +16.63pp تک) ہفتہ وار شور کی مقدار ہے جسے تخمینہ لگانے والا جذب کرتا ہے۔ صرف ایک برا ہفتہ اوسط کو ایک فیصد پوائنٹ تک بدل سکتا ہے۔ یہی وجہ ہے کہ پلیسبو اور فالو اپ اقدامات سنگل پوائنٹ کے تخمینے سے زیادہ اہم ہیں۔

مرحلہ 3: خلا میں پلیسبو پرموٹیشن ٹیسٹ

ایک معیاری ٹی ٹیسٹ ایک واحد پروسیس شدہ یونٹ پر نہیں چلایا جا سکتا۔ جامع کنٹرول میں ایک علاج شدہ مشاہدہ (لہر 1) اور 25 ڈونر مشاہدات ہیں، جو کہ ایسی ترتیب نہیں ہے جس کے لیے روایتی p-values ​​کا اطلاق ہوتا ہے۔

معیاری توثیق خلا میں پلیسبو پرموٹیشن ٹیسٹ ہے۔ ہر عطیہ دہندہ کے ساتھ بدلے میں ایسا سلوک کریں جیسے یہ ایک "علاج شدہ" یونٹ ہو، بقیہ 24 عطیہ دہندگان کو پلیسبو پول کے طور پر استعمال کرتے ہوئے مصنوعی کنٹرول گروپ کو ریفٹ کریں، پلیسبو مدت کے بعد وقفوں کو ریکارڈ کریں، اور مشاہدہ شدہ وقفوں کا پلیسبو تقسیم سے موازنہ کریں۔

placebo_gaps = []

for j in range(n_donors):
    placebo_treated = donor_matrix[:, j]
    placebo_pool = np.delete(donor_matrix, j, axis=1)
    n_p = placebo_pool.shape[1]

    def obj_p(w):
        return np.mean((placebo_treated[:PRE] - placebo_pool[:PRE] @ w) ** 2)

    res_p = minimize(
        obj_p, np.ones(n_p) / n_p, method="SLSQP",
        bounds=[(0, 1)] * n_p,
        constraints=[{"type": "eq", "fun": lambda w: w.sum() - 1}],
        options={"ftol": 1e-12, "maxiter": 5000},
    )
    synth_p = placebo_pool @ res_p.x
    placebo_gaps.append((placebo_treated[PRE:] - synth_p[PRE:]).mean())

placebo_gaps = np.array(placebo_gaps)
observed_gap = gap

rank = int((np.abs(placebo_gaps) >= abs(observed_gap)).sum())
pseudo_p = (rank + 1) / (len(placebo_gaps) + 1)

print(f"Observed gap:      {observed_gap:+.4f}")
print(f"Placebo mean gap:  {placebo_gaps.mean():+.4f}")
print(f"Placebo std gap:   {placebo_gaps.std():.4f}")
print(f"Placebo gap range: [{placebo_gaps.min():+.4f}, "
      f"{placebo_gaps.max():+.4f}]")
print(f"|placebo| >= |observed|: {rank} of {len(placebo_gaps)}")
print(f"Pseudo p-value: {pseudo_p:.4f}")

متوقع پیداوار:

Observed gap:      +0.0829
Placebo mean gap:  -0.0008
Placebo std gap:   0.0380
Placebo gap range: [-0.0748, +0.0707]
|placebo| >= |observed|: 0 of 25
Pseudo p-value: 0.0385

موجودہ صورتحال کچھ یوں ہے: لوپ تمام 25 Wave-2 ورک اسپیس کے ذریعے اعادہ ہوتا ہے۔ ہر ایک کے لیے، ہم انہیں ڈونر پول سے ہٹاتے ہیں، ان کو پلیسبو ٹریٹمنٹ یونٹ کے طور پر دیکھتے ہیں، اور پھر SLSQP آپٹیمائزیشن کو دوبارہ چلاتے ہیں۔ 25 پلیسبو رن کے بعد، ہم حساب لگاتے ہیں کہ کتنے پلیسبو گیپس مطلق قدر میں مشاہدہ شدہ خلا کو پورا کرتے ہیں یا اس سے زیادہ ہوتے ہیں اور ایک قدامت پسند (نمبر + 1) / (N + 1) اصلاح کا اطلاق کرتے ہیں۔

25 پلیسبوس میں سے کسی نے بھی اتنا زیادہ فرق نہیں دکھایا جتنا کہ مشاہدہ کیا گیا +0.0829 ہے، جس سے 0.0385 کی سیوڈو پی ویلیو حاصل ہوتی ہے۔ یہ مسترد کرتا ہے کہ 5% کی سطح پر کوئی اثر نہیں ہے۔ پلیسبو ڈسٹری بیوشن 0 کے قریب مرکوز ہے (مطلب -0.0008، معیاری 3.80pp)، جو شور کا فلور ہے جس کے خلاف مشاہدہ شدہ خلا کا موازنہ کیا جائے گا۔

درست اعدادوشمار کی تفصیل کچھ یوں ہے: مشاہدہ شدہ فرق 5% کی سطح پر علاج نہ کیے جانے والے عطیہ دہندگان سے حاصل کردہ پلیسبو سے زیادہ ہے۔ پرموٹیشن ٹیسٹ کی طاقت کا انحصار ڈونر پول کے سائز پر ہوتا ہے۔ 25 عطیہ دہندگان کے ساتھ، سب سے چھوٹا ممکنہ pseudo-p 1/26 = 0.0385 ہے، لہذا عطیہ دہندگان کی اس تعداد سے چھوٹی p-value حاصل نہیں کی جا سکتی۔ وسیع پلیسبو تقسیم یا چھوٹے مشاہدے کے وقفے پلیسبو بلک کے اندر مشاہدات کی درجہ بندی کرتے ہیں اور سیوڈاپ کو مفید حد سے آگے بڑھاتے ہیں۔

مرحلہ 4: ایک بار ڈونر کی حساسیت

اگر صرف ایک عطیہ دہندہ تک محدود ہو تو سخت نکتہ کا تخمینہ اب بھی کمزور ہو سکتا ہے۔ ایک لیو ون آؤٹ (LOO) حساسیت کا ٹیسٹ بدلے میں ہر ایک غیر صفر عطیہ دہندہ کو حذف کر دیتا ہے، باقی عطیہ دہندگان کے لیے جامع کنٹرول کو ریفٹ کرتا ہے، اور نئے وقفوں کو ریکارڈ کرتا ہے۔

Abadi (2021) اسے پہلے آرڈر کی مضبوطی کی جانچ کے طور پر تجویز کرتا ہے۔ اگر کسی ایک ڈونر کو ہٹانے پر فرق نمایاں طور پر اتار چڑھاؤ آتا ہے تو کوئی جامع تضاد نہیں ہے۔ اضافی وزن کے ساتھ ایک واحد ڈونر کا موازنہ ہے۔

def fit_and_gap(treated, donors, pre=PRE):
    n = donors.shape[1]
    def obj(w):
        return np.mean((treated[:pre] - donors[:pre] @ w) ** 2)
    res = minimize(
        obj, np.ones(n) / n, method="SLSQP",
        bounds=[(0, 1)] * n,
        constraints=[{"type": "eq", "fun": lambda w: w.sum() - 1}],
        options={"ftol": 1e-12, "maxiter": 5000},
    )
    synth = donors @ res.x
    return float((treated[pre:] - synth[pre:]).mean())


nz_idx = np.where(w_opt > 0.001)[0]
loo_rows = []
for j in nz_idx:
    kept = np.delete(donor_matrix, j, axis=1)
    gap_new = fit_and_gap(treated_series, kept)
    loo_rows.append({
        "dropped_workspace": int(wave2_ws[j]),
        "dropped_weight": float(w_opt[j]),
        "new_gap": gap_new,
    })
loo_df = pd.DataFrame(loo_rows).sort_values("dropped_weight", ascending=False)
print(loo_df.round(4).to_string(index=False))
print(f"nLOO gap range: [{loo_df.new_gap.min():+.4f}, "
      f"{loo_df.new_gap.max():+.4f}]")
print(f"Original gap:  {gap:+.4f}")

متوقع پیداوار:

 dropped_workspace  dropped_weight  new_gap
                35          0.2016   0.0945
                40          0.1900   0.0756
                25          0.1638   0.0932
                32          0.0872   0.0868
                36          0.0784   0.0739
                31          0.0718   0.0858
                29          0.0648   0.0782
                26          0.0439   0.0786
                27          0.0364   0.0867
                46          0.0350   0.0794
                39          0.0192   0.0848
                42          0.0078   0.0839

LOO gap range: [+0.0739, +0.0945]
Original gap:  +0.0829

موجودہ صورتحال کچھ یوں ہے: لوپ ایک وقت میں غیر صفر وزن والے عطیہ دہندگان کو حذف اور ریفٹ کرتا ہے۔ LOO کے تمام 12 تخمینے مثبت رہے۔ [+7.39 pp, +9.45 pp] اصل +8.29 پی پی کو کسی بھی سمت میں تقریباً 1 فیصد پوائنٹ کھولتا ہے۔

کوئی ایک ڈونر نتائج نہیں چلاتا۔ یہاں تک کہ اگر ہم ورک اسپیس 35 کو حذف کر دیتے ہیں (0.2016 پر سب سے بڑا وزن)، فرق +9.45pp تک بڑھ جاتا ہے کیونکہ آپٹمائزر وزن کو باقی ڈونرز میں دوبارہ تقسیم کرتا ہے۔

یہ دوبارہ تقسیم محدب مجموعہ وزن کی کلید ہے۔ تقریباً ایک جیسے عطیہ دہندگان کا مرکب اسی طرح کے متضاد پیدا کرتا ہے۔

مرحلہ 5: کلسٹر بوٹسٹریپ 95% اعتماد کا وقفہ

نقطہ تخمینہ صرف آدھی کہانی ہے۔ اسٹیک ہولڈرز جو پوچھتے ہیں "آپ کتنے یقینی ہیں؟" فاصلہ چاہتے ہیں؟ روایتی نان پیرامیٹرک بوٹسٹریپنگ واحد پروسیس شدہ یونٹس کے مصنوعی کنٹرول پر صاف طور پر لاگو نہیں ہوتی ہے۔ اس کی وجہ یہ ہے کہ پروسیس شدہ ٹائم سیریز کو متبادل کے ساتھ دوبارہ نمونہ کرنے سے وقت کی ترتیب کو ختم کر دیا جاتا ہے جس پر تخمینہ لگانے والا انحصار کرتا ہے۔

ایک درست متبادل صارف کی سطح کی کلسٹر بوٹسٹریپنگ ہے۔ متبادل کے ساتھ استعمال کنندگان کو دوبارہ نمونہ بنائیں، دوبارہ نمونے والے صارف لاگز سے ورک اسپیس کے لیے مخصوص پینل دوبارہ بنائیں، مدت سے پہلے ڈونر کے وزن کو درست کریں، اور مدت کے بعد وقفہ ریکارڈ کریں۔

500 بار دہرائیں۔ نتیجہ خیز تقسیم کا 2.5 واں اور 97.5 فیصد فیصد 95% CI ہے۔

def build_panel(df_inner):
    dfw = df_inner[df_inner.signup_week < WINDOW].copy()
    panel = (dfw.groupby(["workspace_id", "signup_week"])
             ["task_completed"].mean().reset_index())
    panel.columns = ["workspace_id", "week", "task_completed"]
    piv = panel.pivot(index="week", columns="workspace_id",
                      values="task_completed")
    piv = piv.interpolate(method="linear", axis=0).ffill().bfill()
    ws_wave_b = df_inner.groupby("workspace_id").wave.first()
    w1 = sorted(ws_wave_b[ws_wave_b == 1].index.tolist())
    w2 = sorted(ws_wave_b[ws_wave_b == 2].index.tolist())
    return piv[w1].mean(axis=1).values, piv[w2].values


rng = np.random.default_rng(7)
n = len(df)
n_reps = 500
gaps_boot = np.empty(n_reps)
for i in range(n_reps):
    sample = df.iloc[rng.integers(0, n, size=n)]
    t_b, d_b = build_panel(sample)
    gaps_boot[i] = fit_and_gap(t_b, d_b)

lo = float(np.percentile(gaps_boot, 2.5))
hi = float(np.percentile(gaps_boot, 97.5))
print(f"Post-period gap 95% CI: [{lo:+.4f}, {hi:+.4f}]")
print(f"Observed point estimate: {gap:+.4f}")
print(f"Ground truth +0.0500 inside CI: "
      f"{'YES' if lo <= 0.05 <= hi else 'NO'}")
print(f"Zero inside CI: {'YES' if lo <= 0 <= hi else 'NO'}")

متوقع پیداوار:

Post-period gap 95% CI: [+0.0511, +0.1215]
Observed point estimate: +0.0829
Ground truth +0.0500 inside CI: NO
Zero inside CI: NO

موجودہ صورتحال کچھ یوں ہے: ہم صارف لاگ کو 500 بار دوبارہ نمونہ بناتے ہیں، ہر ایک نمونے پر پینلز کو دوبارہ بناتے ہیں، سابقہ ​​مدت کو دوبارہ وزن دیتے ہیں، اور 500 کے نتیجے میں آنے والے وقفوں کے 2.5ویں اور 97.5ویں پرسنٹائل لیتے ہیں۔ 95% CI ہے۔ [+5.11 pp, +12.15 pp]. چونکہ 0 کے علاوہ کسی بھی چیز کی گنجائش ہے، اس لیے اثر شماریاتی لحاظ سے اہم ہے۔

نچلی حد +5pp زمینی سچائی کے بالکل اوپر واقع ہے۔ یعنی، چھوٹے ڈونر پینلز کے ساتھ مصنوعی کنٹرول کا محدود نمونہ اوپر کی طرف تعصب، جہاں ہر ڈونر ورک اسپیس (تقریباً 19 صارفین فی ہفتہ) 25 ورک اسپیس پروسیسنگ اوسط سے زیادہ شور اٹھاتا ہے۔

پلیسبو، ایل او او، اور بوٹسٹریپنگ ایک ساتھ دراصل مثبت اثرات دیکھتے ہیں۔ ایک واحد ورک اسپیس ڈونر کا استعمال کرتے وقت پوائنٹ تخمینہ تعصب ایک تجارتی بند ہے۔

اسٹیک ہولڈر کی رپورٹس کے لیے، پوائنٹ کے تخمینے کے ساتھ وقفوں کا حوالہ دیں اور تعصب کی سمت کو نوٹ کریں تاکہ ٹیم صحیح کیلیبریشن کے ساتھ نمبروں کو پڑھ سکے۔

جب جامع کنٹرول ناکام ہوجاتا ہے۔

ٹوٹل کنٹرول تنگ ناکامی کے طریقوں کے ساتھ ایک درست ٹول ہے۔ چار سب سے زیادہ عام شناخت کرنے والے تین مفروضوں سے براہ راست جڑے ہوئے ہیں۔

1. ڈونر پول کی آلودگی (عدم مداخلت کی خلاف ورزی)

اگر لہر 1 میں فراہم کردہ اپ گریڈ لہر 2 تک پھیل جاتے ہیں (مشترکہ API کی شرح کو محدود کرنے والے پولز، مشترکہ پرامپٹ کیش، ورک اسپیس کے درمیان صارفین کی منتقلی)، عطیہ دہندگان آلودہ ہوتے ہیں اور فرق حقیقی اثر کو کم کرتا ہے۔

دفاع ادارہ جاتی ہے۔ ہم واضح طور پر ماڈل سطح کے چینلز کو شامل کرتے ہیں جیسے مشترکہ روٹنگ، مشترکہ کیشنگ، اور مشترکہ نگرانی تاکہ علاج کی تاریخوں کے ارد گرد ڈونر یونٹس میں تبدیلیوں کا آڈٹ کیا جا سکے۔

2. بنیادی طور پر مختلف اکائیاں (مدت سے پہلے مطابقت کی خلاف ورزی)

محدب ہل کی حالت اس بات کی نشاندہی کرتی ہے کہ پروسیس شدہ آلہ عطیہ دہندہ کی مدد کے اندر ہونا چاہیے۔ اگر علاج کی جانے والی اکائیاں ساختی طور پر مختلف ہیں (مثال کے طور پر، کارپوریٹ صارفین جہاں تمام شراکت کنندگان SMBs ہیں)، کوئی بھی وزن کی اسکیم قابل بھروسہ جوابی حقیقت پیدا نہیں کرے گی، اس سے کوئی فرق نہیں پڑتا ہے کہ پری پیریڈ فٹ کتنا ہی قریب نظر آتا ہے۔

وزن چیک کریں۔ اگر آپٹیمائزر کسی ایک ڈونر کو 80% مختص کر رہا ہے، تو آپ کو یہ پوچھنا ہوگا کہ کیا وہ ڈونر اصل میں پورا کام کر رہا ہے، تو وہ واقعی موازنہ کر سکتے ہیں۔

3. علاج کے بعد عطیہ دہندہ کو جھٹکا (مستقل ڈونر کی ساخت کی خلاف ورزی)

مصنوعی کنٹراسٹ پری پیریڈ وزن سے ڈونر کے رویے کی پیش گوئی کرتا ہے۔ اگر کسی بڑے عطیہ دہندہ کو علاج کے بعد ایک بڑا جھٹکا لگتا ہے (کسٹمر منتھن، منقطع ہونا، مسابقتی آغاز)، علاج کے بعد کی رفتار اب مکمل طور پر متضاد نہیں ہے۔ زیادہ وزن والے عطیہ دہندگان کی ٹائم سیریز کو غیر معمولی پوسٹ پروسیسنگ پیٹرن کے لیے جانچا جاتا ہے۔

4. J T₀ کے قریب آتے ہی اوور فٹنگ کا خطرہ (دراصل مدت سے پہلے فٹ کا بگڑ جانا)

اصلاح کار صرف شور سے پہلے کی مدت میں فٹ ہو سکتا ہے جب J ≥ T₀، موازنہ کا بھرم پیدا کرتا ہے۔ یہ ٹیوٹوریل اہم زون میں T₀/J = 20/25 = 0.8 کے ساتھ چلتا ہے۔ LOO حساسیت کی جانچ ایک عملی دفاع ہے۔ اگر عطیہ دہندگان کے قطروں میں فرق کو برقرار رکھا جاتا ہے، تو فٹ حقیقی موازنہ کی عکاسی کرتا ہے۔

یہ ناکامی کے طریقے پوائنٹ تخمینہ میں پوشیدہ رہتے ہیں۔ یہ ایک مصنوعی کنٹرول کے طور پر ظاہر ہوتا ہے جو کاغذ پر اچھی طرح فٹ ہوتا ہے اور ایسے خلا پیدا کرتا ہے جو اگلے مرحلے تک پروسیسنگ کے دوران برقرار نہیں رہتے ہیں۔ پلیسبو ٹیسٹنگ، LOO حساسیت، اور بوٹسٹریپنگ کا مجموعہ تحفظ فراہم کرتا ہے۔

آگے کیا کرنا ہے۔

جامع کنٹرول ایک مناسب ٹول ہے جب فیچر عالمی سطح پر جاری کیا جا رہا ہو اور غیر پروسیس شدہ اکائیوں کا ایک پول موجود ہو جو پروسیس شدہ یونٹوں سے ملتا جلتا ہو۔

اگر پروسیس شدہ یونٹ اور ڈونر یونٹ مختلف پیمانے پر کام کرتے ہیں؛ بڑھا ہوا ترکیب کنٹرول لکیری نتائج کے ماڈل میں تعصب کی اصلاح کی اصطلاح شامل کریں۔ اگر مختلف اوقات میں کئی اکائیاں اختیار کی گئی ہیں، عام مصنوعی کنٹرول (وہ gsynth R پیکجز) فریم ورک کو بڑھاتے ہیں۔

پروڈکشن ازگر کے کاموں کے لیے: pysyncon ہم مکمل Abadi-Diamond-Hainmueller تخمینہ لگانے والے کو V-matrix بیرونی لوپ کے ذریعے پیشین گوئی کے وزن کے ساتھ لاگو کرتے ہیں، اور ان ٹائم پلیسبو ٹیسٹنگ شامل کرتے ہیں (پیریڈ سے کچھ دن پہلے علاج تفویض کرنا اور جعلی وقفوں کی جانچ کرنا)، جس کا اس ٹیوٹوریل میں احاطہ نہیں کیا گیا ہے۔ اگر ہم اسے دوبارہ شروع سے یہاں نافذ کریں تو حرکیات اس طرح نظر آئیں گی: pysyncon یہ جائزہ لینے والے کو بھیجا جاتا ہے۔

اس ٹیوٹوریل کے لیے ساتھی نوٹ بک github.com/RudrenduPaul/product-experimentation-causal-inference-genai-llm/tree/main/04_synthetic_control پر ہے۔ ریپوزٹری کو کلون کریں، ایک مصنوعی ڈیٹاسیٹ بنائیں، اور اسے چلائیں۔ synthetic_control_demo.ipynb (یا synthetic_control_demo.py) اس ٹیوٹوریل میں ہر کوڈ بلاک، ہر نمبر، اور ہر تصویر کو دوبارہ پیش کرتا ہے۔

اگر ماڈل اپ گریڈ تمام صارفین کو بیک وقت پیش کیا جاتا ہے، تو عام طور پر غلط نمبر سے پہلے/بعد میں بولی ہوتی ہے۔ مصنوعی کنٹرولز آپ کے پاس پہلے سے موجود ڈیٹا سے "غیر اپ گریڈ شدہ صارفین" بناتے ہیں، علاج کے ہفتے سے پہلے وزن ٹھیک کرتے ہیں، اور ایک پلیسبو ڈسٹری بیوشن اور بوٹسٹریپ وقفہ فراہم کرتے ہیں جس کے خلاف آپ دفاع کر سکتے ہیں جب اسٹیک ہولڈرز پوچھیں کہ آپ کتنے پر اعتماد ہیں۔

اوپر تک سکرول کریں۔