অধ্যায় 5 · পরিমাপ ও ঘটনা
পরিমাপ ও ঝুঁকি-নিয়ন্ত্রণের পরীক্ষা: নিয়ন্ত্রণগুলো আসলে কাজ করছে কি না
- পৃষ্ঠা 19 / 22
- 18 মিনিট পড়া
যে নিয়ন্ত্রণ কেউ পরীক্ষা করে না, তা একটা আশা মাত্র। আগের পাতাগুলোতে ঝুঁকির রেজিস্টার, ডকুমেন্টেশন ও গভর্ন্যান্সের রেকর্ড তৈরি হয়েছে; এই পাতায় প্রশ্ন: সেগুলোর ভেতরের নিয়ন্ত্রণ আসলে কাজ করছে কি না, আর আপনি তা কীভাবে জানবেন। এখানে অস্পষ্ট লক্ষ্যকে সংখ্যা ও মালিকসহ উদ্দেশ্যে পরিণত করা হবে, একটা ছোট পরীক্ষা পরিকল্পনা করা হবে, টয় ডেটাসেট বানানো হবে, গ্রুপভিত্তিক পরিমাপ হিসাব করা হবে, কাঠামোবদ্ধ রেকর্ড থেকে রেজিস্টার ও সিস্টেম কার্ডের খসড়া বানানো হবে, আর শেষে একটা ছোট টেস্ট হারনেস চালানো হবে, যার উত্তর PASS, FAIL বা NOT TESTED।
আপনার দরকার Python 3.12 এবং শুধু তার স্ট্যান্ডার্ড লাইব্রেরি: কোনো প্যাকেজ, নেটওয়ার্ক বা মডেল কল লাগবে না। ব্লকগুলো ক্রমে একটা সেশনে লিখুন; এগুলো ভেরিয়েবল ভাগ করে নেয়, আর প্রতিটা ব্লকের নিচের লেখাটা তার আসল আউটপুট।
সততার নিয়ম। প্রতিটা ডেটাসেট কৃত্রিম (synthetic) এবং
Toy data:লেবেল দেওয়া। প্রতিটা প্রতিষ্ঠান, ব্যক্তি, সীমা ও সংখ্যা কাল্পনিক। ফলাফল শুধু দেখায় একটা হিসাব বা পরীক্ষা কীভাবে কাজ করে; বাস্তব মানুষ বা বাস্তব সিস্টেম সম্পর্কে কিছু বলে না, আর টয় ডেটায় PASS মানে বাস্তব সার্ভিস সম্পর্কে কিছুই প্রমাণ হওয়া নয়। তথ্য না থাকলে স্ক্রিপ্ট MISSING বা NOT TESTED জানায়, বানায় না। এখানে কিছুই আইনি পরামর্শ নয়।
যা শিখবেন
- "ন্যায্য হও"-কে কীভাবে পরিমাপ, সীমা, মালিক ও না মিললে করণীয়সহ একটা পরিমাপযোগ্য উদ্দেশ্যে পরিণত করবেন, আর লিডিং ইন্ডিকেটর ও আউটকাম মেজারের পার্থক্য কী।
- একটা ছোট ঝুঁকি-নিয়ন্ত্রণ টেস্ট প্ল্যান কীভাবে লিখবেন, আর হাতে মিলিয়ে দেখা যায় এমন একটা অফলাইন টয় ডেটাসেট কীভাবে বানাবেন।
- স্ট্যান্ডার্ড লাইব্রেরি দিয়ে গ্রুপভিত্তিক পরিমাপ কীভাবে হিসাব করবেন, আর ছোট স্যাম্পল ও অনুপস্থিত গ্রুপ রিপোর্ট না করে কীভাবে চিহ্নিত করবেন।
- কাঠামোবদ্ধ রেকর্ড থেকে ঝুঁকির রেজিস্টার ও সিস্টেম কার্ডের খসড়া কীভাবে বানাবেন, আর ফিল্ড না বানিয়ে ইমপ্যাক্ট অ্যাসেসমেন্ট কীভাবে যাচাই করবেন।
- একটা নিয়ন্ত্রণ তার গ্রহণযোগ্যতার মানদণ্ড পূরণ করছে কি না তা কীভাবে পরীক্ষা করে PASS, FAIL বা NOT TESTED জানাবেন।
চলমান উদাহরণ
কাল্পনিক পরিস্থিতি:
Organisation: Orchid Training Fund (invented), a non-profit with 60 subsidised course places.
Feature: PlaceSorter reads applications and marks each "invite" or "hold"; a staff member
reads every "invite" first and decides.
Groups: applicants are grouped by region of application: north, south, east, west
(placeholders; the fund's policy names all four).
Label: "reviewers judged the applicant eligible" in a later full read (1 or 0).
Question: do the controls the fund wrote down work, and can a program show it?কার্ডের সারকথা: অর্কিড ট্রেনিং ফান্ড (কাল্পনিক) একটা অলাভজনক সংস্থা, যার ৬০টা ভর্তুকিপ্রাপ্ত কোর্সের আসন আছে। PlaceSorter আবেদনগুলো পড়ে প্রতিটাকে "invite" বা "hold" চিহ্নিত করে; একজন কর্মী আগে প্রতিটা "invite" পড়েন এবং সিদ্ধান্ত নেন। আবেদনকারীরা আবেদনের অঞ্চল অনুযায়ী ভাগ করা (north, south, east, west; এগুলো প্লেসহোল্ডার, আর সংস্থার নীতিতে চারটারই নাম আছে)। লেবেল: পরে পুরো পড়ার সময় রিভিউয়াররা আবেদনকারীকে যোগ্য বিবেচনা করেছেন কি না (১ বা ০)। প্রশ্ন: সংস্থা যে নিয়ন্ত্রণগুলো লিখে রেখেছে সেগুলো কাজ করছে কি না, আর একটা প্রোগ্রাম তা দেখাতে পারে কি না?
আগের পাতা রিভিউ, অডিট ও বাস্তব গভর্ন্যান্স: পরিবর্তনের অনুমোদন, ঝুঁকি গ্রহণ ও কর্মীদের উদ্বেগ প্রতিটা সিস্টেমকে একজন মালিক ও একটা পরিবর্তন-অনুমোদনের নিয়ম দিয়েছিল। এখানে সেই মালিকদের দেখার মতো সংখ্যা দরকার, আর অনুমোদনকারীদের জানা দরকার একটা ব্যর্থ পরীক্ষার অর্থ কী।
পরিমাপযোগ্য উদ্দেশ্য
পরিমাপযোগ্য দায়িত্বশীল-ব্যবহারের উদ্দেশ্য ঠিক করুন। "ন্যায্য হও" যাচাই করা যায় না। একটা পরিমাপযোগ্য উদ্দেশ্য বলে কী মাপা হচ্ছে, কাদের ওপর, কত সময়ে, কোন সীমার বিপরীতে, কে মালিক, আর সীমা মিস হলে কী হবে। সীমাটা মানুষের বেছে নেওয়া; এখানে কিছুই কোনো স্ট্যান্ডার্ড নয়।
| অস্পষ্ট লক্ষ্য | পরিমাপযোগ্য উদ্দেশ্য (আমাদের উদাহরণ) | মিস হলে |
|---|---|---|
| ন্যায্য হওয়া | প্রতি ত্রৈমাসিকে যেকোনো দুটো রিপোর্টযোগ্য গ্রুপের সিলেকশন-রেটের ব্যবধান সর্বোচ্চ ০.১০; যে গ্রুপে ১০ জনের কম যোগ্য বা ১০ জনের কম অযোগ্য আবেদনকারী, তা "not reported" চিহ্নিত। মালিক: সিলেকশন প্রধান | পরিবর্তন থামান, কারণ খুঁজুন, ফলাফল লিখে রাখুন |
| মানুষের নিয়ন্ত্রণ রাখা | কর্মীরা প্রতি মাসে টুলের কিছু মার্ক বদলান বা বাতিল করেন; ওভাররাইড রেট রিপোর্ট হয়, আর টানা দুই মাস প্রায় শূন্য থাকলে ৩০টা কেসের নমুনা দেখা হয়। মালিক: অপারেশনস | দেখুন কর্মীদের দ্বিমত করার সময় ও তথ্য আছে কি না |
| গোপনীয়তা রক্ষা | মাসিক ৫০টা আপলোড-করা ফাইলের নমুনায় কোনোটায় জাতীয় পরিচয়পত্র নম্বর নেই। মালিক: ডেটা লিড | ঘটনা-প্রক্রিয়া শুরু করুন |
| উদ্বেগ শোনা | কর্মীর প্রতিটা উদ্বেগ ২ কর্মদিবসের মধ্যে স্বীকার করা হয়। মালিক: পরিচালক | প্রক্রিয়া রিভিউ করুন |
দুটো সতর্কতা। একটা সংখ্যা মানুষকে সংখ্যার দিকেই ঠেলতে পারে: কর্মীদের বিচার যদি কম অভিযোগের সংখ্যায় হয়, অভিযোগ করা কঠিন হয়ে যেতে পারে। আর যা গুরুত্বপূর্ণ তার অনেকটাই ভালোভাবে গোনা যায় না, তাই পরিমাপের পাশে কয়েকটা লিখিত, গুণগত রিভিউ রাখুন।
লিডিং ইন্ডিকেটর ও আউটকাম মেজার
লিডিং ইন্ডিকেটর ও আউটকাম মেজারের পার্থক্য করুন। লিডিং ইন্ডিকেটর (leading indicator) আগেভাগেই জানায় ভালো ফলের শর্তগুলো আছে কি না। আউটকাম মেজার (outcome measure) জানায় মানুষের ওপর আসলে কী ঘটেছে। দুটোই দরকার: ফলাফলই মূল কথা, কিন্তু তা দেরিতে আসে ও প্রায়ই বিরল; লিডিং ইন্ডিকেটর দ্রুত, কিন্তু ফলাফল থেকে সরে যেতে পারে।
| লিডিং ইন্ডিকেটর | আউটকাম মেজার | |
|---|---|---|
| উদাহরণ | রিলিজের আগে অনুমোদিত পরিবর্তনের অনুপাত; রিভিউ মেয়াদোত্তীর্ণ সিস্টেম (আগের পাতার রেজিস্টার থেকে); কম র্যাংকের কেসের মাসিক নমুনা মানুষ পড়েছেন; উদ্বেগ স্বীকার করতে সময় | প্রতি ১০০০ সিদ্ধান্তে নিশ্চিত ভুল; গ্রুপের মধ্যে ট্রু-পজিটিভ-রেটের ব্যবধান; বহাল থাকা অভিযোগ; তীব্রতা অনুযায়ী ঘটনা |
| শক্তি | দ্রুত, দলের নিয়ন্ত্রণে, সস্তায় ট্র্যাক করা যায় | যে ক্ষতি গুরুত্বপূর্ণ তার কাছাকাছি |
| দুর্বলতা | মানুষের কোনো উপকার ছাড়াই পূরণ করা যায়; কারসাজি করা যায় | দেরিতে আসে, ছোট গ্রুপে অস্থির, একটা কারণে আরোপ করা কঠিন |
কম ওভাররাইড রেট ফাঁদটা দেখায়: এর মানে হতে পারে টুল ভালো, অথবা রিভিউয়াররা প্রশ্ন করা ছেড়ে দিয়েছেন (দেখুন মানুষের তদারকির ডিজাইন: অর্থবহ পর্যালোচনা, রাবার-স্ট্যাম্প নয়)। মাঝে মাঝে দেখুন প্রতিটা লিডিং ইন্ডিকেটর যে ফলাফলের প্রতিনিধি, তার সঙ্গে এখনও একসঙ্গে নড়ছে কি না।
একটা ছোট ঝুঁকি-নিয়ন্ত্রণ টেস্ট প্ল্যান
একটা ছোট ঝুঁকি-নিয়ন্ত্রণ টেস্ট প্ল্যান ডিজাইন করুন। প্ল্যান কোনো পরীক্ষা চালানোর আগেই বলে নিয়ন্ত্রণটা কী দাবি করে, কীভাবে যাচাই হবে, কোন ডেটায়, আর কী হলে পাস। মানদণ্ড আগে লিখলে ফল দেখার পর তা বেছে নেওয়া যায় না। এই নিয়ন্ত্রণগুলো সংস্থার ঝুঁকি-রেজিস্টার থেকে এসেছে।
| নিয়ন্ত্রণ | দাবি | পরীক্ষা ও গ্রহণযোগ্যতার মানদণ্ড | মালিক |
|---|---|---|---|
| C1 | খুব ছোট গ্রুপের তুলনা করা হয় না | টয় ডেটা দিন; ১০ জনের কম যোগ্য বা ১০ জনের কম অযোগ্য আবেদনকারীর গ্রুপের জন্য কোনো ব্যবধান রিপোর্ট হয় না | ডেটা লিড |
| C2 | আমন্ত্রণের হার গ্রুপভেদে খুব আলাদা নয় | রেফারেন্সের তুলনায় প্রতিটা রিপোর্ট-করা গ্রুপের সিলেকশন-রেটের ব্যবধান সর্বোচ্চ ০.১০ | সিলেকশন প্রধান |
| C3 | যোগ্য আবেদনকারীরা প্রতিটা গ্রুপে সমানভাবে আমন্ত্রিত হন | ট্রু-পজিটিভ-রেটের ব্যবধান সর্বোচ্চ ০.১০ | সিলেকশন প্রধান |
| C4 | নীতিতে নাম-থাকা প্রতিটা গ্রুপ পরীক্ষা হয় | নাম-থাকা প্রতিটা গ্রুপের টেস্ট ডেটায় সারি আছে | ডেটা লিড |
| C5 | প্রতিটা রিলিজ একজন নামধারী ব্যক্তি অনুমোদন করেন | রেজিস্টারে অনুমোদনের রেকর্ড (প্রোগ্রাম-টেস্ট এখনও লেখা হয়নি) | অপারেশনস |
| C6 | ইমপ্যাক্ট অ্যাসেসমেন্ট সম্পূর্ণ | কোনো আবশ্যিক ফিল্ড অনুপস্থিত নেই | পরিচালক |
| C7 | টেস্ট ডেটা ঠিক একইভাবে আবার বানানো যায় | একই সিড দিলে একই গ্রুপ-টেবিল আসে | ডেটা লিড |
সীমা ও রেফারেন্স গ্রুপ মানুষের সিদ্ধান্ত। কে এগুলো বদলাতে পারেন তা বলে দিন: পরীক্ষা পাস করাতে সীমা বাড়ানো একটা পরিবর্তন, যার জন্য আগের পাতার বর্ণিত অনুমোদন লাগে।
একটা অফলাইন টয় ডেটাসেট
ঝুঁকি দেখানোর জন্য একটা অফলাইন টয় ডেটাসেট বানান। ডেটাটা গণনা থেকে বানান, যাতে প্রতিটা সংখ্যা হাতে মিলিয়ে দেখা যায়; তারপর একটা স্থির সিড দিয়ে শাফল করুন, যাতে ক্রম বাস্তব ডেটার মতো দেখায় অথচ ঠিক একইভাবে পুনরাবৃত্ত হয়।
টয় ডেটা: ১১৬টা কাল্পনিক আবেদন; west-এ কোনো সারি নেই।
import random
OBJ = {"min_each": 10, "max_selection_gap": 0.10, "max_tpr_gap": 0.10} # chosen limits, not standards
CELLS = [ # Toy data: (group, label 1=eligible, pred 1=invite, how many people)
("north", 1, 1, 24), ("north", 1, 0, 6), ("north", 0, 1, 8), ("north", 0, 0, 32),
("south", 1, 1, 10), ("south", 1, 0, 8), ("south", 0, 1, 4), ("south", 0, 0, 18),
("east", 1, 1, 2), ("east", 1, 0, 1), ("east", 0, 1, 0), ("east", 0, 0, 3),
]
EXPECTED = ["north", "south", "east", "west"] # the groups the fund's policy names
def build(seed=7):
rows = [dict(group=g, label=l, pred=p) for g, l, p, n in CELLS for _ in range(n)]
random.Random(seed).shuffle(rows)
return rows
ROWS = build()
print(len(ROWS), "rows;", ROWS[0], "...")
print({g: sum(r["group"] == g for r in ROWS) for g in EXPECTED})116 rows; {'group': 'north', 'label': 0, 'pred': 0} ...
{'north': 70, 'south': 40, 'east': 6, 'west': 0}কয়েক লাইনে গ্রুপভিত্তিক পরিমাপ
স্ট্যান্ডার্ড লাইব্রেরি দিয়ে ন্যায্যতার মেট্রিক হিসাব করুন। এই পরিমাপগুলোর অর্থ, আর কোনটা বেছে নেওয়া উচিত তা নিয়ে তর্ক আছে টয় ডেটায় ন্যায্যতার মেট্রিক: Python-এ সিলেকশন রেট ও এরর রেট এবং ন্যায্যতার লক্ষ্য তুলনা ও তার সীমা: ছোট স্যাম্পল ও পরস্পরবিরোধী মেট্রিক পাতায়। এখানে শুধু হিসাবটা সংক্ষেপে নতুন করে বানানো হয়েছে, যাতে একটা টেস্ট তাকে ডাকতে পারে: সিলেকশন রেট (আমন্ত্রিতদের অনুপাত), ট্রু-পজিটিভ রেট (যোগ্য আবেদনকারীদের মধ্যে আমন্ত্রিতের অনুপাত) এবং ফল্স-পজিটিভ রেট (অযোগ্যদের মধ্যে আমন্ত্রিতের অনুপাত)। যে সংখ্যা দিয়ে ভাগ করা হয় তা শূন্য হলে রেট হয় None, কখনও শূন্য নয়।
def rates(rows):
out = {}
for g in sorted({r["group"] for r in rows}):
s = [r for r in rows if r["group"] == g]
tp = sum(r["label"] == 1 and r["pred"] == 1 for r in s)
fn = sum(r["label"] == 1 and r["pred"] == 0 for r in s)
fp = sum(r["label"] == 0 and r["pred"] == 1 for r in s)
neg = len(s) - tp - fn
out[g] = dict(n=len(s), pos=tp + fn, neg=neg, selection=(tp + fp) / len(s),
tpr=tp / (tp + fn) if tp + fn else None, fpr=fp / neg if neg else None)
return out
def f2(x):
return "n/a" if x is None else f"{x:.2f}"
R = rates(ROWS)
print(f"{'group':<7}{'n':>4}{'elig':>6}{'inelig':>8}{'selection':>11}{'tpr':>6}{'fpr':>6}")
for g, v in R.items():
print(f"{g:<7}{v['n']:>4}{v['pos']:>6}{v['neg']:>8}{v['selection']:>11.2f}{f2(v['tpr']):>6}{f2(v['fpr']):>6}")group n elig inelig selection tpr fpr
east 6 3 3 0.33 0.67 0.00
north 70 30 40 0.46 0.80 0.20
south 40 18 22 0.35 0.56 0.18ছোট স্যাম্পল ও অনুপস্থিত গ্রুপ
ছোট স্যাম্পল ও অনুপস্থিত গ্রুপ চিহ্নিত করুন। east-এ তিনজন যোগ্য ও তিনজন অযোগ্য মানুষ আছেন: একটা সিদ্ধান্ত অন্য হলে তার ট্রু-পজিটিভ রেট এক-তৃতীয়াংশ নড়ে যায়। west-এ কোনো সারিই নেই, যার মানে হতে পারে কেউ আবেদন করেননি, অথবা ডেটা কখনও পৌঁছায়নি; প্রোগ্রাম তা বলতে পারে না, তাই নীরবতাকে ন্যায্যতা ধরে নেওয়া চলবে না। নিচের নিয়ম কোনো ব্যবধান দেখানোর আগেই চলে। এর ন্যূনতম সংখ্যা বেছে নেওয়া min_each সীমা; ওই দুই পাতার দ্বিতীয়টা দেখায় ছোট একটা রেটের চারপাশে কীভাবে অনিশ্চয়তার পরিসর (interval) বসানো যায়।
def status(R, expected=EXPECTED, minimum=OBJ["min_each"]):
out = {}
for g in expected:
if g not in R:
out[g] = "MISSING: no rows for this group"
elif min(R[g]["pos"], R[g]["neg"]) < minimum:
out[g] = f"NOT REPORTED: {R[g]['pos']} eligible, {R[g]['neg']} ineligible (minimum {minimum})"
else:
out[g] = "ok"
return out
def gaps(R, st, ref="north"):
return {g: {k: R[g][k] - R[ref][k] for k in ("selection", "tpr", "fpr")}
for g in R if st.get(g) == "ok" and g != ref}
ST = status(R)
G = gaps(R, ST)
for g, s in ST.items():
print(f"{g:<7}{s}")
for g, d in G.items():
print(f"gap {g} - north:", ", ".join(f"{k} {v:+.3f}" for k, v in d.items()))north ok
south ok
east NOT REPORTED: 3 eligible, 3 ineligible (minimum 10)
west MISSING: no rows for this group
gap south - north: selection -0.107, tpr -0.244, fpr -0.018রেফারেন্স গ্রুপ (north, সবচেয়ে বড়) হিসাবের সুবিধার জন্য, গ্রুপদের কোনো র্যাংক নয়। south-এর ব্যবধান দেখা যাচ্ছে; east ও west-এর জন্য কোনো সংখ্যাই বের হয় না, আর সেই নীরবতা লুকানো হয় না, জানানো হয়।
কাঠামোবদ্ধ রেকর্ড থেকে ঝুঁকির রেজিস্টার
কাঠামোবদ্ধ রেকর্ড থেকে ঝুঁকির রেজিস্টার বানান। পরীক্ষা ও রিভিউয়ের ফলাফল রেকর্ড হয়ে আসে। প্রোগ্রাম সেগুলোকে রেজিস্টারে রূপ দিতে, সাজাতে ও কোনগুলো প্রস্তুত নয় তা চিহ্নিত করতে পারে, কিন্তু সম্ভাবনা ও তীব্রতা একজন মানুষকে রেট করতে হয়: প্রোগ্রাম রেটিং অনুমান করে না। স্কোর ও ব্যান্ড ইমপ্যাক্ট অ্যাসেসমেন্ট: একটা সীমিত AI ফিচারের পরিধি, স্টেকহোল্ডার ও ক্ষতি এবং ঝুঁকির স্কোরিং, নিয়ন্ত্রণ ও সিদ্ধান্ত: একটা টয় রিস্ক রেজিস্টার বানানো পাতার পদ্ধতি অনুসরণ করে; কাট-অফগুলো আবারও আমাদের বেছে নেওয়া।
FINDINGS = [ # Toy data: likelihood and severity (1-5) are rated by a person; None = not yet rated
dict(id="F1", text="Eligible south applicants invited less often (tpr gap)", likelihood=4,
severity=4, rated_by="Selection lead", owner="Head of Selection"),
dict(id="F2", text="East group too small to judge", likelihood=3, severity=3,
rated_by="Selection lead", owner=""),
dict(id="F3", text="No test rows for west", likelihood=None, severity=None, rated_by="",
owner="Data lead"),
]
BANDS = ((16, "very high"), (10, "high"), (5, "medium"), (1, "low"))
def register(findings):
rated, waiting = [], []
for f in findings:
if f["likelihood"] and f["severity"] and f["rated_by"]:
pts = f["likelihood"] * f["severity"]
rated.append((pts, f["severity"], f["id"], next(b for lo, b in BANDS if pts >= lo), f))
else:
waiting.append(f)
return sorted(rated, key=lambda x: (-x[0], -x[1], x[2])), waiting
rated, waiting = register(FINDINGS)
for pts, _, fid, band, f in rated:
note = "" if f["owner"] else " [MISSING owner]"
print(f"{fid} {pts:>2} pts {band:<9} {f['text']}{note}")
for f in waiting:
print(f"{f['id']} NOT RATED - a person must rate likelihood and severity: {f['text']}")F1 16 pts very high Eligible south applicants invited less often (tpr gap)
F2 9 pts medium East group too small to judge [MISSING owner]
F3 NOT RATED - a person must rate likelihood and severity: No test rows for westসিস্টেম কার্ডের খসড়া ও যাচাই-করা ইমপ্যাক্ট অ্যাসেসমেন্ট
Python ডিকশনারি থেকে সিস্টেম কার্ডের খসড়া বানান এবং ইমপ্যাক্ট অ্যাসেসমেন্টের আবশ্যিক ফিল্ড যাচাই করুন। একটা ডিকশনারিতে অ্যাসেসমেন্ট থাকে। ভ্যালিডেটর খালি আবশ্যিক ফিল্ডের তালিকা দেয়; কার্ডের খসড়া ডিকশনারি যা বলে তাই ছাপে, বাকিটা MISSING চিহ্নিত করে, আর পরিমাপের ফল বা ফল রিপোর্ট না করার কারণ জুড়ে দেয়। এই ধারণার আরও যত্নশীল, দীর্ঘ রূপ আছে প্রকাশ ও ডকুমেন্টেশন যাচাই: Python দিয়ে সিস্টেম কার্ড পরীক্ষা পাতায়।
IA = dict( # Toy data: one invented impact assessment, partly filled in
purpose="order applications so staff read the likeliest eligible first",
out_of_scope=["automatic rejection"], affected=["applicants", "selection staff"],
harms=["eligible applicants invited less often in some regions"],
controls=[], residual_risk="", owner="Head of Selection", review_date="2027-03-01")
REQUIRED = ("purpose", "out_of_scope", "affected", "harms", "controls", "residual_risk",
"owner", "review_date")
def missing(d, required=REQUIRED):
return [k for k in required if not d.get(k)]
def card(d, R, st):
lines = ["SYSTEM CARD DRAFT (not reviewed)"]
for k in REQUIRED:
v = d.get(k)
lines.append(f"{k}: " + ("MISSING" if not v else "; ".join(v) if isinstance(v, list) else v))
lines.append("measured on toy data:")
for g, s in st.items():
ok = f"selection {R[g]['selection']:.2f}, tpr {f2(R[g]['tpr'])}" if s == "ok" else s
lines.append(f" {g}: {ok}")
return "\n".join(lines)
gone = missing(IA)
print(f"{len(gone)} of {len(REQUIRED)} required fields missing: {', '.join(gone)}\n")
print(card(IA, R, ST))2 of 8 required fields missing: controls, residual_risk
SYSTEM CARD DRAFT (not reviewed)
purpose: order applications so staff read the likeliest eligible first
out_of_scope: automatic rejection
affected: applicants; selection staff
harms: eligible applicants invited less often in some regions
controls: MISSING
residual_risk: MISSING
owner: Head of Selection
review_date: 2027-03-01
measured on toy data:
north: selection 0.46, tpr 0.80
south: selection 0.35, tpr 0.56
east: NOT REPORTED: 3 eligible, 3 ineligible (minimum 10)
west: MISSING: no rows for this groupখসড়াটা নিজের ফাঁক সম্পর্কে সৎ: কোনো নিয়ন্ত্রণ বা অবশিষ্ট ঝুঁকি লেখা নেই, তাই কার্ড MISSING বলে, বিশ্বাসযোগ্য বাক্য বসায় না। ওই ফিল্ডগুলো পূরণ করতে সেই মানুষদের দরকার যাঁরা উত্তর জানেন।
নিয়ন্ত্রণ তার মানদণ্ড পূরণ করছে কি না
একটা নিয়ন্ত্রণ তার গ্রহণযোগ্যতার মানদণ্ড পূরণ করছে কি না পরীক্ষা করুন। নিচের প্রতিটা টেস্ট assert দিয়ে লেখা একটা ফাংশন: সে নীরবে পাস করে, নয়তো মেসেজসহ AssertionError তোলে। হারনেস সেগুলো চালিয়ে তিনটার একটা ফল জানায়। PASS মানে এই ডেটায় মানদণ্ড পূরণ হয়েছে। FAIL মানে হয়নি। NOT TESTED মানে কোনো টেস্ট নেই বা দরকারি ডেটা ছিল না; একে কখনও পাস গোনা হয় না। (Python -O অপশনে চললে assert স্টেটমেন্ট বাদ দেয়, তাই বাস্তব প্রকল্পে টেস্ট ফ্রেমওয়ার্ক ব্যবহার করুন বা স্পষ্টভাবে এরর তুলুন।)
class NotTested(Exception):
pass
def c1():
assert "east" not in G, "a gap was reported for a group below the minimum"
def c2():
worst = max(abs(d["selection"]) for d in G.values())
assert worst <= OBJ["max_selection_gap"], f"selection gap {worst:.3f} is above {OBJ['max_selection_gap']:.2f}"
def c3():
worst = max(abs(d["tpr"]) for d in G.values())
assert worst <= OBJ["max_tpr_gap"], f"true-positive-rate gap {worst:.3f} is above {OBJ['max_tpr_gap']:.2f}"
def c4():
absent = [g for g in EXPECTED if g not in R]
assert not absent, "no rows for: " + ", ".join(absent)
def c6():
gone = missing(IA)
assert not gone, "missing fields: " + ", ".join(gone)
def c7():
assert rates(build(7)) == rates(build(7)), "same seed gave different tables"
CONTROLS = [("C1", "Small groups are not compared", c1), ("C2", "Selection-rate gap at most 0.10", c2),
("C3", "True-positive-rate gap at most 0.10", c3), ("C4", "Every policy group is tested", c4),
("C5", "A named person approves each release", None), ("C6", "Impact assessment complete", c6),
("C7", "Test data rebuilds exactly", c7)]
def run(controls):
tally = {"PASS": 0, "FAIL": 0, "NOT TESTED": 0}
for cid, text, test in controls:
try:
if test is None:
raise NotTested("no program test written")
test()
result, why = "PASS", ""
except AssertionError as e:
result, why = "FAIL", str(e)
except NotTested as e:
result, why = "NOT TESTED", str(e)
tally[result] += 1
print(f"{cid} {result:<11}{text}" + (f"\n why: {why}" if why else ""))
print(tally)
run(CONTROLS)C1 PASS Small groups are not compared
C2 FAIL Selection-rate gap at most 0.10
why: selection gap 0.107 is above 0.10
C3 FAIL True-positive-rate gap at most 0.10
why: true-positive-rate gap 0.244 is above 0.10
C4 FAIL Every policy group is tested
why: no rows for: west
C5 NOT TESTED A named person approves each release
why: no program test written
C6 FAIL Impact assessment complete
why: missing fields: controls, residual_risk
C7 PASS Test data rebuilds exactly
{'PASS': 2, 'FAIL': 4, 'NOT TESTED': 1}প্রতিটা লাইনকে সিদ্ধান্তের ইঙ্গিত হিসেবে পড়ুন, টুলের ওপর রায় হিসেবে নয়। C2 সামান্য ব্যবধানে ফেল করেছে (০.১০-এর বিপরীতে ০.১০৭), কয়েক ডজন কাল্পনিক সারিতে: এটা আরও কাছ থেকে দেখার ও সংখ্যাটা কতটা নির্ভরযোগ্য তা জিজ্ঞেস করার কারণ, অন্যায্যতার প্রমাণ নয়। C3 ফেল করেছে বড় ব্যবধানে। C4 ফেল করেছে কারণ west-এর ডেটা নেই, আর C5 NOT TESTED কারণ অনুমোদন এমন রেকর্ড যা কাউকে পড়তে হবে। C1 ও C7 পাস করেছে, যার মানে শুধু এই দুটো যন্ত্রাংশ এই ডেটায় কাজ করে। ব্যর্থ নিয়ন্ত্রণ আগের পাতায় বর্ণিত পরিবর্তন-অনুমোদন ও রিভিউয়ের ধাপ চালু করে; ফল যা-ই হোক, লিখে রাখা হয়।
কাজের জায়গায় এটা কোথায় দেখা যায়
অ্যানালিস্ট ও প্রোডাক্ট দলগুলো প্রতিটা রিলিজের আগে আর প্রতি ত্রৈমাসিকে এমন কয়েকটা ছোট পরীক্ষা চালায় এবং আউটপুট সেই ভার্সনের পাশে রাখে যাকে তা পরীক্ষা করেছে। রিভিউয়ার ও অডিটররা তখন আস্থার সাধারণ বিবৃতি না পড়ে PASS, FAIL ও NOT TESTED-এর তালিকা পড়েন। সবচেয়ে জরুরি অভ্যাস: ডেটা দেখার আগে পাসের মানদণ্ড লিখে রাখা।
সাধারণ ভুল
- ফল দেখার পর সীমা ঠিক করা। সংখ্যার সঙ্গে মিলিয়ে বেছে নেওয়া সীমা কিছুই প্রমাণ করে না। আগে লিখুন, কে বদলাতে পারেন তাঁর নামসহ।
- NOT TESTED-কে পাস গোনা। পরীক্ষা-না-হওয়া নিয়ন্ত্রণ একটা অজানা। সেভাবেই রিপোর্ট করুন, আর পরীক্ষাটার পরিকল্পনা করুন।
- খুব ছোট বা খালি গ্রুপের রেট রিপোর্ট করা। তিনজনের এক-তৃতীয়াংশ কোনো সিদ্ধান্তের ভিত্তি নয়, আর অনুপস্থিত গ্রুপের নীরবতা ন্যায্যতা নয়। দুটোই চিহ্নিত করুন।
- শুধু একটা লিডিং ইন্ডিকেটরে ভরসা। পরিপাটি রিভিউ-লগের পাশেই প্রকৃত ক্ষতি থাকতে পারে। মাঝে মাঝে একটা আউটকাম মেজারের সঙ্গে মিলিয়ে দেখুন।
- টয় ফলকে বিশ্বের তথ্য ভাবা। এখানকার ডেটা একটা হিসাব দেখানোর জন্য বানানো। বাস্তব ডেটার জন্য দরকার বাস্তব গভর্ন্যান্স, বিষয়ভিত্তিক রিভিউ আর আগের পাতাগুলোর প্রশ্ন।
নিজে চেষ্টা করুন
এই পাতার নেমস্পেস ব্যবহার করুন। কাজগুলো রিভিউ, অডিট ও বাস্তব গভর্ন্যান্স: পরিবর্তনের অনুমোদন, ঝুঁকি গ্রহণ ও কর্মীদের উদ্বেগ পাতার গভর্ন্যান্স রেজিস্টার আবার ছোঁয়।
- সহজ।
max_selection_gap০.১২ করুন এবং শুধু C2 আবার চালান। কী বদলায়, আর ব্যর্থ পরীক্ষা ঠিক করার এটা কেন ভালো উপায় নয়? - মাঝারি। একটা নিয়ন্ত্রণ C8 যোগ করুন: "ফল্স-পজিটিভ-রেটের ব্যবধান সর্বোচ্চ ০.১০", এবং অন্যগুলোর সঙ্গে চালান।
- কঠিন। C9 যোগ করুন: "east-এর ট্রু-পজিটিভ-রেটের ব্যবধান সর্বোচ্চ ০.১০"। east-এ যোগ্য আবেদনকারী খুব কম থাকলে নিয়ন্ত্রণটাকে NOT TESTED জানাতে হবে, এবং কারণ বলতে হবে।
উত্তর
সহজ।
OBJ["max_selection_gap"] = 0.12
run([c for c in CONTROLS if c[0] == "C2"])
OBJ["max_selection_gap"] = 0.10 # put the chosen limit backC2 PASS Selection-rate gap at most 0.10
{'PASS': 1, 'FAIL': 0, 'NOT TESTED': 0}পাস চেকলিস্ট: C2 এখন পাস করে; উত্তরে বলা আছে ফল দেখার পর সীমা বদলানো হয়েছে, এমন বদলের জন্য লিখিত কারণ ও সিস্টেমের স্তরের অনুমোদন লাগে, ডেটা বদলায়নি, আর লেবেলে এখনও ০.১০ লেখা যদিও সীমা এখন ০.১২। দুর্বল উত্তর পরীক্ষা পাস করেছে বলে সিস্টেমকে ন্যায্য বলে দেয়।
মাঝারি।
def c8():
worst = max(abs(d["fpr"]) for d in G.values())
assert worst <= 0.10, f"false-positive-rate gap {worst:.3f} is above 0.10"
run([("C8", "False-positive-rate gap at most 0.10", c8)])C8 PASS False-positive-rate gap at most 0.10
{'PASS': 1, 'FAIL': 0, 'NOT TESTED': 0}পাস চেকলিস্ট: স্পষ্ট মেসেজসহ একটা টেস্ট ফাংশন; ফল PASS, সঙ্গে মন্তব্য যে এটা শুধু একটা রিপোর্ট-করা গ্রুপ ঢাকে। দুর্বল উত্তর হিসাব না করে উত্তরটা হাতে বসিয়ে দেয়।
কঠিন।
def c9():
if R["east"]["pos"] < OBJ["min_each"]:
raise NotTested(f"east has {R['east']['pos']} eligible applicants, minimum {OBJ['min_each']}")
assert abs(R["east"]["tpr"] - R["north"]["tpr"]) <= OBJ["max_tpr_gap"], "east tpr gap too large"
run([("C9", "East true-positive-rate gap at most 0.10", c9)])C9 NOT TESTED East true-positive-rate gap at most 0.10
why: east has 3 eligible applicants, minimum 10
{'PASS': 0, 'FAIL': 0, 'NOT TESTED': 1}পাস চেকলিস্ট: ফল NOT TESTED, PASS বা FAIL নয়, কারণ রেটটা তিনজনের ওপর দাঁড়াত; কারণের লেখায় সংখ্যাগুলো আছে। একটা ভালো উত্তর পরের ধাপও যোগ করে: আরও ডেটা সংগ্রহ করুন, অথবা মেনে নিন যে এই গ্রুপ এখনও পরীক্ষা করা যায় না, এবং সেই সিদ্ধান্ত ও কে নিলেন তা লিখে রাখুন।
সারসংক্ষেপ
- পরিমাপযোগ্য উদ্দেশ্যে পরিমাপ, সীমা, মালিক ও না মিললে করণীয় থাকে; লিডিং ইন্ডিকেটর ও আউটকাম মেজার একসঙ্গে ব্যবহার করুন।
- কিছু চালানোর আগে টেস্ট প্ল্যান ও তার পাসের মানদণ্ড লিখুন, আর সীমাগুলো পরিবর্তন-অনুমোদনের আওতায় রাখুন।
- ছোট স্যাম্পল ও অনুপস্থিত গ্রুপের রেট রিপোর্ট না করে চিহ্নিত করুন; নীরবতা ন্যায্যতার প্রমাণ নয়।
- রেকর্ড থেকে রেজিস্টার ও কার্ড বানান, কিন্তু রেট করা ও পূরণ করা মানুষের হাতে রাখুন; MISSING জানান, কখনও বানাবেন না।
- হারনেস PASS, FAIL বা NOT TESTED দেয়; NOT TESTED একটা অজানা, আর টয় ফল শুধু একটা হিসাব দেখায়।
এরপর: ঘটনা ও ধারাবাহিক উন্নতি: ট্রায়াজ, পোস্ট-মর্টেম ও রিভিউ চক্র পাতায় আছে বাস্তব ব্যবহারে একটা নিয়ন্ত্রণ ব্যর্থ হলে কী করবেন।