অধ্যায় 1 · ঝুঁকি ও ফ্রেমওয়ার্ক
ঝুঁকির স্কোরিং, নিয়ন্ত্রণ ও সিদ্ধান্ত: একটা টয় রিস্ক রেজিস্টার বানানো
- পৃষ্ঠা 2 / 22
- 20 মিনিট পড়া
আগের পাতায় আপনি স্ক্রিনিং ফিচারের পরিধি ঠিক করেছিলেন এবং সাতটা ঝুঁকির একটা রেজিস্টার লিখেছিলেন। রেজিস্টার নিজে কোনো সিদ্ধান্ত নেয় না। কাউকে এখনো জিজ্ঞেস করতে হবে: কোন নিয়ন্ত্রণ (control) সত্যিই আছে, সহজ কোনো বিকল্পেই কাজ চলত কি না, কতটা ঝুঁকি মেনে নেওয়া যায় এবং কে তা বলবেন, আর প্রথম ছোট পরীক্ষার আগে কী সত্য হতে হবে। এই পাতা রেজিস্টারটাকে এমন একটা সিদ্ধান্তে পরিণত করে যা একটা দল যুক্তিসহ রক্ষা করতে পারে।
আপনার দরকার Python 3.12 এবং শুধু তার স্ট্যান্ডার্ড লাইব্রেরি। প্রতিটা পাতা নতুন ফোল্ডারে চলে, তাই নিচের প্রথম ব্লকগুলো ইমপ্যাক্ট অ্যাসেসমেন্ট: একটা সীমিত AI ফিচারের পরিধি, স্টেকহোল্ডার ও ক্ষতি পাতার রেজিস্টারটা সংক্ষেপে আবার বানায়। ব্লকগুলো ক্রমে একটা সেশনে লিখুন; প্রতিটা ব্লকের নিচের লেখাটা তার আসল আউটপুট।
সততার নিয়ম। প্রতিটা ডেটাসেট কৃত্রিম এবং
Toy data:লেবেল দেওয়া। প্রতিটা কোম্পানি, ব্যক্তি, নিয়ম ও সংখ্যা কাল্পনিক, সীমারেখাগুলোও (threshold)। ফলাফল শুধু দেখায় একটা হিসাব কীভাবে কাজ করে; বাস্তব মানুষ বা বাস্তব সিস্টেম সম্পর্কে এটা কিছুই বলে না। এখানে কিছুই আইনি পরামর্শ নয়।
যা শিখবেন
- বিদ্যমান নিয়ন্ত্রণগুলো সততার সঙ্গে তাদের সীমাবদ্ধতাসহ কীভাবে তালিকা করবেন, এবং পরিকল্পিত নিয়ন্ত্রণ আর চালু নিয়ন্ত্রণ কীভাবে আলাদা করবেন।
- বেছে নেওয়ার আগে কীভাবে AI-বহির্ভূত বিকল্প এবং শুধু-মানুষ, AI-সহায়তায় ও স্বয়ংক্রিয় কর্মপ্রবাহের তুলনা করবেন।
- ঝুঁকি মেনে নেওয়া ও ঊর্ধ্বতনের কাছে পাঠানোর নিয়ম, পাইলটের শর্ত, বিশেষজ্ঞ, মালিক ও পুনর্মূল্যায়নের ট্রিগার কীভাবে লিখবেন।
- ঝুঁকির সঙ্গে মিলিয়ে ইমপ্যাক্ট অ্যাসেসমেন্টের গভীরতা কীভাবে ঠিক করবেন, এবং স্ক্রিনিং ফিচারের জন্য কীভাবে লিখিত সিদ্ধান্তে পৌঁছাবেন।
রেজিস্টার থেকে শুরু
এই পাতা ১ নম্বর পাতার সিদ্ধান্ত-কাঠামোর শেষ ধাপগুলো ধরে: বিকল্প, শর্তসহ সিদ্ধান্ত, জবাবদিহি করবেন যিনি, পর্যালোচনার তারিখ। NIST AI RMF 1.0 (জানুয়ারি ২০২৩, স্বেচ্ছামূলক)-এর ভাষায় এটা Manage-এর সবচেয়ে কাছে (আমাদের ব্যাখ্যা: যে ঝুঁকিগুলো ম্যাপ ও পরিমাপ করেছেন সেগুলো নিয়ে কী করবেন তা ঠিক করা)।
প্রথমে রেকর্ডের ধরন: ১ নম্বর পাতার রেকর্ড, তবে নিয়ন্ত্রণগুলো নিজস্ব তালিকায় সরানো, আর যোগ হয়েছে মালিক ও ট্যাগ।
# file: risk.py
from dataclasses import dataclass
BANDS = ((16, "very high"), (10, "high"), (5, "medium"), (1, "low")) # our chosen cut-offs, as on page 1
def band(points):
return next(name for floor, name in BANDS if points >= floor)
@dataclass
class Risk:
id: str
title: str
level: str # individual or collective
likelihood: int
severity: int
reversibility: str # easy, hard, irreversible
exposed: int
evidence: str # none, opinion, internal-test, independent
res_likelihood: int = 0 # residual CLAIMED after controls; 0 = nothing claimed
res_severity: int = 0
owner: str = "" # a role, not "the team"
tags: tuple = () # what the risk touches: model, data, use, scale, vendor ...
def inherent(self):
return self.likelihood * self.severity
def claimed(self):
return self.res_likelihood * self.res_severity if self.res_likelihood else self.inherent()এবার ডেটা। সাতটা ঝুঁকি ১ নম্বর পাতারই, R7 ওই পাতার উত্তর অনুযায়ী সম্পূর্ণ করা।
টয় ডেটা: কাল্পনিক স্ক্রিনিং ফিচারের সাতটা ঝুঁকি ও আটটা নিয়ন্ত্রণ।
from collections import namedtuple
from risk import Risk, band
# Toy data: the register from page 1, now with an owner and tags per risk. All invented.
RISKS = [Risk(**d) for d in [
dict(id="R1", title="Score penalises career breaks", level="individual", likelihood=3, severity=4,
reversibility="hard", exposed=900, evidence="opinion", res_likelihood=2, res_severity=3,
owner="Head of recruitment", tags=("model", "group")),
dict(id="R2", title="Recruiters trust the ranking", level="individual", likelihood=4, severity=3,
reversibility="hard", exposed=4800, evidence="internal-test", res_likelihood=3, res_severity=3,
owner="Head of recruitment", tags=("model", "use")),
dict(id="R3", title="Unusual CV layouts parse badly", level="individual", likelihood=3, severity=3,
reversibility="easy", exposed=600, evidence="internal-test", res_likelihood=3, res_severity=2,
owner="Operations manager", tags=("model", "data")),
dict(id="R4", title="CV text kept beyond hiring", level="individual", likelihood=2, severity=4,
reversibility="irreversible", exposed=6000, evidence="opinion",
owner="Operations manager", tags=("personal-data", "vendor", "data")),
dict(id="R5", title="Score reused to rank staff", level="individual", likelihood=2, severity=3,
reversibility="hard", exposed=250, evidence="none", res_likelihood=1, res_severity=3,
owner="Agency owner", tags=("use", "employment")),
dict(id="R6", title="Shortlists drift toward past hires", level="collective", likelihood=3, severity=3,
reversibility="hard", exposed=6000, evidence="opinion", res_likelihood=3, res_severity=2,
owner="the team", tags=("group", "scale", "model")),
dict(id="R7", title="Appeal route unclear", level="individual", likelihood=3, severity=3,
reversibility="hard", exposed=4800, evidence="none",
owner="Head of recruitment", tags=("use", "employment")),
]]
Control = namedtuple("Control", "id risk kind what status tested limit")
CONTROLS = [ # Toy data. status: in place or planned. tested: none, internal-test or independent
Control("C1", "R1", "prevent", "remove the career-gap feature", "in place", "none",
"other CV fields can stand in for a gap"),
Control("C2", "R1", "detect", "quarterly score check by group", "planned", "none",
"needs group labels the agency does not hold"),
Control("C3", "R2", "detect", "read 50 low-ranked CVs a month", "in place", "internal-test",
"50 of about 400 a month"),
Control("C4", "R3", "detect", "parse warning on odd layouts", "in place", "internal-test",
"sees only failures the parser notices"),
Control("C5", "R3", "correct", "manual check path", "in place", "none",
"the applicant must know to ask"),
Control("C6", "R5", "prevent", "written purpose limit", "in place", "none",
"a rule on paper, not a block"),
Control("C7", "R5", "detect", "access log", "in place", "internal-test",
"shows misuse after the fact"),
Control("C8", "R6", "detect", "yearly shortlist vs pool check", "planned", "none",
"slow: once a year"),
]
print(len(RISKS), "risks,", len(CONTROLS), "controls,", sum(c.status == "planned" for c in CONTROLS), "of them only planned")7 risks, 8 controls, 2 of them only plannedবিদ্যমান নিয়ন্ত্রণ ও তাদের সীমাবদ্ধতা
বিদ্যমান নিয়ন্ত্রণ ও তাদের সীমাবদ্ধতা চিহ্নিত করুন। নিয়ন্ত্রণ (control) এমন কিছু যা ক্ষতি আটকায় (prevent), ধরে (detect) বা শোধরায় (correct)। তিনটা প্রশ্ন নিয়ন্ত্রণ আর আশাকে আলাদা করে: সেটা চালু আছে নাকি শুধু পরিকল্পিত; কেউ তা পরীক্ষা করেছেন কি না; আর তার সীমা কী, অর্থাৎ সে কী পারে না। যে ধরা-নিয়ন্ত্রণ কেউ পড়েন না সেটা নিয়ন্ত্রণ নয়।
for c in CONTROLS:
print(f"{c.id} {c.risk} {c.kind:<8}{c.status:<10}tested: {c.tested:<14}{c.what}")
print(f" limit: {c.limit}")C1 R1 prevent in place tested: none remove the career-gap feature
limit: other CV fields can stand in for a gap
C2 R1 detect planned tested: none quarterly score check by group
limit: needs group labels the agency does not hold
C3 R2 detect in place tested: internal-test read 50 low-ranked CVs a month
limit: 50 of about 400 a month
C4 R3 detect in place tested: internal-test parse warning on odd layouts
limit: sees only failures the parser notices
C5 R3 correct in place tested: none manual check path
limit: the applicant must know to ask
C6 R5 prevent in place tested: none written purpose limit
limit: a rule on paper, not a block
C7 R5 detect in place tested: internal-test access log
limit: shows misuse after the fact
C8 R6 detect planned tested: none yearly shortlist vs pool check
limit: slow: once a year১ নম্বর পাতা R1-এর জন্য দুটো আর R6-এর জন্য একটা নিয়ন্ত্রণ গুনেছিল। কিন্তু C2 ও C8 শুধু পরিকল্পিত, আর C1 কখনো পরীক্ষা হয়নি। পরিকল্পিত নিয়ন্ত্রণ আজ ঝুঁকি কমায় না, তাই পরের ব্লক শুধু চালু নিয়ন্ত্রণ ধরে অবশিষ্ট স্কোর আবার হিসাব করে, আর যেখানে দাবি টেকে না সেখানে সন্দেহ জানায়।
def in_place(risk):
return [c for c in CONTROLS if c.risk == risk.id and c.status == "in place"]
def supported(risk):
"""Residual points we can defend today: a planned control does not count."""
return risk.claimed() if in_place(risk) else risk.inherent()
def doubt(risk):
live = in_place(risk)
if risk.claimed() < risk.inherent() and not live:
return "claim rests on planned controls only"
if live and all(c.tested == "none" for c in live):
return "no in-place control has been tested"
return ""
print("id inherent claimed supported band doubt")
for r in RISKS:
s = supported(r)
print(f"{r.id:<4}{r.inherent():>6}{r.claimed():>9}{s:>10} {band(s):<11}{doubt(r)}")id inherent claimed supported band doubt
R1 12 6 6 medium no in-place control has been tested
R2 12 9 9 medium
R3 9 6 6 medium
R4 8 8 8 medium
R5 6 3 3 low
R6 9 6 9 medium claim rests on planned controls only
R7 9 9 9 mediumR6 তার সহজাত স্কোর ৯-এ ফিরে যায়, কারণ তার একমাত্র নিয়ন্ত্রণ পরিকল্পিত। R1 তার দাবি করা ৬ রাখে, তবে সন্দেহসহ: তার একমাত্র চালু নিয়ন্ত্রণ, একটা ফিল্ড সরানো, প্রক্সিতে (যে অন্য ফিল্ড সরানোটার জায়গা নেয়) ব্যর্থ হতে পারে। R4 ও R7-এর কোনো নিয়ন্ত্রণই নেই। এগুলো পর্যবেক্ষণ, অভিযোগ নয়; ভুল হবে পরিকল্পিত নিয়ন্ত্রণ গুনে ফেলা।
AI-বহির্ভূত বিকল্প ও তিন ধরনের কর্মপ্রবাহ
AI-বহির্ভূত বিকল্প মূল্যায়ন করুন AI-এর নকশা তুলনার আগে। সৎ প্রশ্নটা হলো: "আমরা কোন সমস্যা সমাধান করছি, আর সহজ কোনো উপায়ে কাজ চলবে কি না?" সেরা বিকল্প নিয়ে যুক্তিসংগত মানুষেরা একমত না-ও হতে পারেন, কারণ প্রতিটা বিকল্প খরচ ও ঝুঁকি অন্য জায়গায় সরায়।
| বিকল্প | কী দেয় | কী খরচ বা ঝুঁকি |
|---|---|---|
| হাতে পড়া চালিয়ে যাওয়া | একজন মানুষ প্রতিটা CV পড়েন; স্কোর থেকে নতুন কোনো ঝুঁকি নেই | ধীর; ক্লান্ত পাঠক অসংগত হন; উত্তর দিতে দেরি হয় |
| নির্দিষ্ট প্রশ্নের ছোট ফর্ম | কম পড়তে হয়; উত্তর তুলনা করা সহজ | কারও কাছে CV-র চেয়ে ফর্ম কঠিন লাগে |
| সহজ লিখিত নিয়ম (যেমন লাইসেন্স থাকতে হবে) | সবাই নিয়মটা দেখতে ও প্রশ্ন করতে পারেন | নিয়ম স্থূল হতে পারে; তারও পর্যালোচনা লাগে |
| AI র্যাংকিং (প্রস্তাবটা) | বেশি পরিমাণে পড়ার সময় বাঁচায় | রেজিস্টারের ঝুঁকিগুলো; চোখে না পড়া ভুল |
"AI ব্যবহার না করা" একটা বাস্তব বিকল্প, তার নিজেরও ক্ষতি আছে, যেমন ধীর উত্তর। তুলনাটা লিখে রাখুন; জেতার চেষ্টা করবেন না।
শুধু-মানুষ, AI-সহায়তায় ও স্বয়ংক্রিয় কর্মপ্রবাহের তুলনা করুন। একই AI স্কোর তিন রকম কর্মপ্রবাহে বসতে পারে। বদলায় সিদ্ধান্ত কে নেন, আর কতগুলো কেস কোনো মানুষ কখনো পড়েন না। পরের ব্লক কাল্পনিক সংখ্যায় এটা হিসাব করে: বছরে ৬০০০ আবেদন এবং একটা CV পুরো পড়তে ৫ মিনিট।
টয় ডেটা: একই ফিচার চালানোর তিনটা উপায়।
APPLICATIONS, MINUTES = 6000, 5 # Toy data: per year; minutes per CV
top = APPLICATIONS // 5
sample = (APPLICATIONS - top) // 20
WORKFLOWS = { # name: (CVs a person reads in full, who makes the call on a CV nobody read)
"human-only": (APPLICATIONS, "not applicable"),
"AI-assisted": (top + sample, "the ranking, by default"),
"automated": (0, "the cut-off, always"),
}
print(f"{'workflow':<13}{'read by a person':>17}{'hours':>7}{'never read':>12} who decides on those")
for name, (read, who) in WORKFLOWS.items():
print(f"{name:<13}{read:>17}{read * MINUTES / 60:>7.0f}{APPLICATIONS - read:>12} {who}")workflow read by a person hours never read who decides on those
human-only 6000 500 0 not applicable
AI-assisted 1440 120 4560 the ranking, by default
automated 0 0 6000 the cut-off, alwaysসহায়তায় কর্মপ্রবাহে পড়ার সময় ৫০০ ঘণ্টা থেকে ১২০ ঘণ্টায় নামে, যা বাস্তব সুবিধা। কিন্তু ৪৫৬০ জন আবেদনকারীর CV কেউ পড়েন না। এটা মেনে নেওয়া যায় কি না নির্ভর করে নিয়ন্ত্রণের ওপর (যেমন সাপ্তাহিক নমুনা), একজন ভালো প্রার্থী বাদ পড়ার খরচের ওপর, আর আবেদনকারীরা পর্যালোচনা চাইতে পারেন কি না তার ওপর। "সহায়তায়" শব্দটা আচরণের একটা প্রতিশ্রুতি: পাঠকেরা শুধু তালিকার ওপরটা খুললে কর্মপ্রবাহটা অন্য নামে স্বয়ংক্রিয়। মানুষ আসলে কী করে তা দেখুন, লেবেল নয়। বিগিনার পাতা আস্থা, যাচাই ও অটোমেশন বায়াস: AI-এর আউটপুটে কখন ভরসা করবেন কারণটা বলে।
ঝুঁকি মেনে নেওয়া ও ঊর্ধ্বতনের কাছে পাঠানো
ঝুঁকি মেনে নেওয়া ও এসকালেশনের মানদণ্ড ঠিক করুন। কোনো ঝুঁকি মেনে নেওয়া (accept) মানে জেনেশুনে, লিখিতভাবে, একটা নির্দিষ্ট সময়ের জন্য তার সঙ্গে থাকার সিদ্ধান্ত। এতে ঝুঁকি মিলিয়ে যায় না। এসকালেশন (escalation) মানে ঝুঁকি মালিকের একা মেনে নেওয়ার চেয়ে বড় হলে সিদ্ধান্তটা বেশি কর্তৃত্বের কারও কাছে পাঠানো। স্কোর দেখার আগেই নিয়মটা লিখুন, নইলে কারও চাওয়া উত্তরের সঙ্গে সেটা বেঁকে যাবে। এই কাল্পনিক নিয়মটা একটা বেছে নেওয়া সিদ্ধান্ত, আবিষ্কার নয়; আপনার দল অন্যভাবে বাছতে পারে।
def route(r):
"""Our invented policy. Returns (route, reasons)."""
points = supported(r)
why = []
if band(points) == "very high":
return "stop", ["residual very high"]
if band(points) == "high":
why.append("residual high")
if r.reversibility == "irreversible":
why.append("irreversible")
if r.severity >= 4 and r.evidence in ("none", "opinion"):
why.append("severe+weak evidence")
if r.level == "collective":
why.append("collective")
if r.exposed >= 3000 and band(points) != "low":
why.append("wide reach")
return ("escalate" if why else "owner may accept"), why
for r in RISKS:
decision, why = route(r)
print(f"{r.id} {band(supported(r)):<8}{decision:<18}{'; '.join(why)}")R1 medium escalate severe+weak evidence
R2 medium escalate wide reach
R3 medium owner may accept
R4 medium escalate irreversible; severe+weak evidence; wide reach
R5 low owner may accept
R6 medium escalate collective; wide reach
R7 medium escalate wide reachসাতটার মধ্যে পাঁচটা ওপরে যায়। শুধু R3 ও R5 মালিক নিজে মেনে নিতে পারেন। কেউ ভাবতে পারেন পাঁচটা বেশি বা কম; পাইলটের আগে এই তর্কটা করা কাজের। "stop" নিয়মটা কখনো চলেনি; তার কাজ হলো খুব উঁচু অবশিষ্ট স্কোরের একটা স্বয়ংক্রিয় পরিণতি রাখা।
সীমিত পাইলটের শর্ত
সীমিত পাইলটের শর্ত ঠিক করুন। পাইলট হলো ছোট, সময়-বাঁধা পরীক্ষা, যা শেখার সময় কারা ক্ষতিগ্রস্ত হতে পারেন তা সীমিত রাখে। দরকার সংকীর্ণ পরিধি, পর্যালোচনার তারিখ, আর প্রতিটা শর্তের জন্য একটা পরিমাপ, একটা থামার নিয়ম ও একজন মালিক। এগুলো ছাড়া শর্ত শুধু ইচ্ছা। নিচের চেকার ফাঁক জানায়, ভরে দেয় না।
টয় ডেটা: একটা কাল্পনিক পাইলট পরিকল্পনা, ইচ্ছে করে দুটো ফাঁক রেখে।
PILOT = { # Invented plan
"scope": "one job family, 8 weeks, at most 600 applications, no automatic rejection",
"review_date": "2027-01-11",
"conditions": [ # (condition, measure, stop if, owner)
("read a random sample of low-ranked CVs every week", "wrongly low-ranked, per 50 read",
"3 or more", "Head of recruitment"),
("read the vendor contract: how long is CV text kept", "retention period written in R4",
"unknown at start", "Operations manager"),
("tell applicants that a tool orders the CVs", "notice text approved", "", "Agency owner"),
("answer every query about a rank within 5 working days", "", "", ""),
],
}
print("scope:", PILOT["scope"])
print("review:", PILOT["review_date"])
for n, (what, measure, stop, owner) in enumerate(PILOT["conditions"], 1):
gaps = [name for name, value in (("measure", measure), ("stop rule", stop), ("owner", owner)) if not value]
print(n, "complete" if not gaps else "MISSING " + ", ".join(gaps), "-", what)scope: one job family, 8 weeks, at most 600 applications, no automatic rejection
review: 2027-01-11
1 complete - read a random sample of low-ranked CVs every week
2 complete - read the vendor contract: how long is CV text kept
3 MISSING stop rule - tell applicants that a tool orders the CVs
4 MISSING measure, stop rule, owner - answer every query about a rank within 5 working days"প্রতি ৫০টায় ৩ বা বেশি" থামার নিয়মটা দলের বেছে নেওয়া একটা সংখ্যা; এখানে কিছুই এটাকে সমর্থন করে না। রেকর্ডে তা লিখুন এবং প্রথম কয়েক সপ্তাহ পর আবার দেখুন। ৩ ও ৪ নম্বর শর্ত অসম্পূর্ণ, আর চেকার অনুমান করে না।
বিশেষজ্ঞ, মালিক ও ট্রিগার
যে ঝুঁকিতে বিশেষজ্ঞের মূল্যায়ন দরকার তা চিহ্নিত করুন। কিছু প্রশ্নের উত্তর পণ্য-দলের দেওয়ার কথা নয়, যেমন চুক্তির কোনো শর্ত গ্রহণযোগ্য কি না, ব্যক্তিগত ডেটা কতদিন রাখা যায়, বা একটা পরিসংখ্যানগত তুলনা সঠিক কি না। দলের কাজ প্রশ্নটা লক্ষ করা এবং তাড়াতাড়ি জিজ্ঞেস করা, নিজে উত্তর দেওয়া নয়। আইনের ক্ষেত্রে লিখুন "employment-law adviser: open", নিজের সিদ্ধান্ত নয়।
SPECIALISTS = {
"group": "statistician or fairness reviewer",
"personal-data": "privacy or data-protection adviser",
"vendor": "contract reviewer",
"employment": "employment-law adviser",
}
for r in RISKS:
need = [SPECIALISTS[t] for t in r.tags if t in SPECIALISTS]
print(r.id, "; ".join(need) if need else "-")R1 statistician or fairness reviewer
R2 -
R3 -
R4 privacy or data-protection adviser; contract reviewer
R5 employment-law adviser
R6 statistician or fairness reviewer
R7 employment-law adviserঝুঁকিগুলো দায়িত্বপ্রাপ্ত মালিকের সঙ্গে মেলান। ঝুঁকির মালিক হলেন তার জন্য জবাবদিহি করবেন যিনি: একটা ভূমিকা, যার পেছনে একটা নাম আছে, "দল" নয়। মালিক কাজ অন্যকে দিতে পারেন, জবাবদিহি নয়।
from collections import Counter
VAGUE = ("", "the team", "everyone", "tbd")
load = Counter(r.owner for r in RISKS)
for owner, n in sorted(load.items()):
print(f"{owner:<22}owns {n}")
print("no named owner:", [r.id for r in RISKS if r.owner.lower() in VAGUE])Agency owner owns 1
Head of recruitment owns 3
Operations manager owns 2
the team owns 1
no named owner: ['R6']R6-এর কোনো নির্দিষ্ট মালিক নেই, অর্থাৎ একমাত্র সমষ্টিগত ঝুঁকির জন্য কেউ জবাবদিহি করেন না, আর Head of recruitment তিনটা ঝুঁকির মালিক এবং পাইলটও চালান। দুটোই ঠিক করার বা খোলাখুলি ঠিক করার বিষয়।
পরিবর্তনের পর পুনর্মূল্যায়নের ট্রিগার ঠিক করুন। রেজিস্টার লেখার দিনেই শুধু সঠিক। কোনো স্কোর ভুল করে দিতে পারে এমন কিছু বদলালে তা আবার খুলতে হবে: নতুন মডেল সংস্করণ, নতুন ডেটার উৎস, নতুন উদ্দেশ্য বা ব্যবহারকারী-গোষ্ঠী, পরিমাণে বড় পরিবর্তন, ঘটনা বা অভিযোগ, নিয়ন্ত্রণের ব্যর্থতা, অথবা পর্যালোচনার তারিখ। পরিবর্তন যে ঝুঁকিগুলোকে ছোঁয় সেগুলো আবার খুলুন; সীমানাই সরে গেলে (নতুন উদ্দেশ্য) আবার পরিধি ঠিক করতে ফিরুন।
টয় ডেটা: চারটা কাল্পনিক পরিবর্তন এবং তাদের ট্যাগ যে ঝুঁকিগুলো আবার খোলে।
TRIGGERS = [ # Invented examples: (what changed, which tags it touches)
("vendor ships a new scoring model", {"model", "vendor"}),
("applications double after a new office opens", {"scale"}),
("a manager asks to rank current staff", {"use"}),
("a new CV source brings a different file type", {"data"}),
]
for change, touched in TRIGGERS:
reopen = [r.id for r in RISKS if touched & set(r.tags)]
print(f"{change:<46}-> {', '.join(reopen)}")vendor ships a new scoring model -> R1, R2, R3, R4, R6
applications double after a new office opens -> R6
a manager asks to rank current staff -> R2, R5, R7
a new CV source brings a different file type -> R3, R4নতুন ব্যবহার R5 খোলে, কিন্তু R2 ও R7-ও খোলে, কারণ সেটা বদলে দেয় কারা প্রভাবিত। মডেল আপডেট সাতটার মধ্যে পাঁচটা ঝুঁকি খোলে। আগে থেকে ঠিক করা ট্রিগার দলকে "এই পরিবর্তনটা খুব ছোট" বলে না দেখে সিদ্ধান্ত নিতে দেয় না।
অনুপাতসই ইমপ্যাক্ট-অ্যাসেসমেন্ট টেমপ্লেট
অনুপাতসই (proportionate) ইমপ্যাক্ট-অ্যাসেসমেন্ট টেমপ্লেট ব্যবহার করুন। অনুপাতসই মানে পরিশ্রম ঝুঁকির সঙ্গে মেলে: বানান-পরীক্ষকের জন্য একটা পাতা যথেষ্ট, স্ক্রিনিং টুলের জন্য বেশি লাগে। ঝুঁকি কম হলে যে অংশ বাদ দেওয়া যায় এমন একটা টেমপ্লেট কাজটাকে সংগত রাখে।
টেমপ্লেট:
IMPACT ASSESSMENT RECORD depth: light | standard | full version: __ date: ____-__-__
1 Situation feature, purpose in one sentence, boundary, out of scope
2 Who is affected users | affected non-users | other stakeholders
3 Benefits/harms benefits (how we would notice) | misuse | harms: individual and collective
4 Options do not use AI | non-AI alternative | human-only | AI-assisted | automated
5 Controls id | risk | kind | in place or planned | tested? | its limit
6 Risk register inherent | claimed residual | supported residual | evidence | assumptions
7 Decision approve | approve with conditions (pilot scope, measures, stop rules) | stop
8 Accountability escalation rule that fired | who decided, accepted until | specialists | owners
9 Review date | change triggers and the risks each one reopens| গভীরতা | কখন | কী সম্পূর্ণ করবেন |
|---|---|---|
| Light | মানুষ নিয়ে কোনো সিদ্ধান্ত নেই; শুধু কম ঝুঁকি | ১, ২, ৭ ও ৯ অংশ, এক পাতায় |
| Standard | মানুষ নিয়ে সিদ্ধান্তে তথ্য জোগায়, বা কিছু মাঝারি ঝুঁকি | সব অংশ; মালিক মেনে নিতে পারেন |
| Full | উঁচু অবশিষ্ট ঝুঁকি, বা মানুষ নিয়ে সিদ্ধান্তে অপরিবর্তনীয় বা সমষ্টিগত ক্ষতি | সব অংশ, সঙ্গে বিশেষজ্ঞের পর্যালোচনা ও মালিকের ওপরের স্তরের লিখিত অনুমোদন |
নিচের নিয়মটা অন্য সীমারেখাগুলোর মতোই কাল্পনিক।
def depth(risks, about_people):
"""Our invented rule for how much assessment a feature needs."""
bands = {band(supported(r)) for r in risks}
hard_cases = any(r.reversibility == "irreversible" or r.level == "collective" for r in risks)
if bands & {"high", "very high"} or (about_people and hard_cases):
return "full"
if about_people or "medium" in bands:
return "standard"
return "light"
print("ShortlistHelper:", depth(RISKS, about_people=True))
typo = Risk("X1", "Typo in a reminder email", "individual", 2, 1, "easy", 100, "opinion")
print("reminder-email helper (invented, one low risk):", depth([typo], about_people=False))ShortlistHelper: full
reminder-email helper (invented, one low risk): lightঅনুশীলন: স্ক্রিনিং ফিচারের মূল্যায়ন
পরের ব্লক ShortlistHelper-এর পর্যবেক্ষণগুলো এক জায়গায় জড়ো করে।
routes = {r.id: route(r) for r in RISKS}
gaps = [n for n, cond in enumerate(PILOT["conditions"], 1) if not all(cond)]
print("escalate:", [i for i, (d, _) in routes.items() if d == "escalate"])
print("owner may accept:", [i for i, (d, _) in routes.items() if d == "owner may accept"])
print("claims to re-check:", [r.id for r in RISKS if doubt(r)])
print("pilot conditions with gaps:", gaps)
print("risks without a named owner:", [r.id for r in RISKS if r.owner.lower() in VAGUE])
print("specialists to ask:", sorted({SPECIALISTS[t] for r in RISKS for t in r.tags if t in SPECIALISTS}))escalate: ['R1', 'R2', 'R4', 'R6', 'R7']
owner may accept: ['R3', 'R5']
claims to re-check: ['R1', 'R6']
pilot conditions with gaps: [3, 4]
risks without a named owner: ['R6']
specialists to ask: ['contract reviewer', 'employment-law adviser', 'privacy or data-protection adviser', 'statistician or fairness reviewer']এটা সিদ্ধান্তের প্রমাণ, রায় নয়। রিক্রুটাররা বাঁচানো সময়কে মূল্য দেন, আবেদনকারীরা চাইতে পারেন প্রতিটা CV একজন মানুষ পড়ুন, আর এজেন্সির মালিক আইনি ও সুনামের ঝুঁকি বহন করেন। সিদ্ধান্ত নেওয়া মানে এগুলোর মধ্যে খোলাখুলি, শর্তসহ বেছে নেওয়া। এটাই নিচের কঠিন অনুশীলন।
কাজের জায়গায় এটা কোথায় দেখা যায়
প্রোডাক্ট, রিস্ক ও প্রকিউরমেন্ট দল কোনো AI ফিচার অনুমোদনে গেলেই এই প্রক্রিয়া চালায়। "এসকালেশন" বড় সংস্থায় একটা কমিটি, ছোট সংস্থায় দুজন মানুষ হতে পারে। কাঠামো একই: লিখিত নিয়ম, নির্দিষ্ট ভূমিকা, আবার দেখার একটা তারিখ।
সাধারণ ভুল
- পরিকল্পিত নিয়ন্ত্রণ গুনে ফেলা। কাগজে স্কোর কমে, বাস্তবে কিছুই বদলায় না। যা চালু ও পরীক্ষিত তা গুনুন; বাকিটা পরিকল্পিত বলে তালিকা করুন।
- তারিখ ছাড়া ঝুঁকি মেনে নেওয়া। এটা অজান্তেই স্থায়ী হয়ে যায়। লিখুন কে মেনেছেন, কতদিন পর্যন্ত, এবং কী ঘটলে আবার খুলবে।
- মানুষ র্যাংকিং মানলেও কর্মপ্রবাহকে "সহায়তায়" বলা। পাঠকেরা আসলে কী খোলেন তা মাপুন; লেবেল কাউকে রক্ষা করে না।
- মালিক "দল"। দলের মালিকানাধীন ঝুঁকির জবাব কেউ দেন না। একটা ভূমিকার নাম দিন।
- থামার নিয়ম ছাড়া পাইলট। যে পাইলট থামতে পারে না সেটা চালু করে দেওয়া। শুরুর আগেই পরিমাপ ও সীমা লিখুন।
নিজে চেষ্টা করুন
এই পাতার নেমস্পেস ব্যবহার করুন। কঠিন অনুশীলন ১ নম্বর পাতার সিদ্ধান্ত-কাঠামো আবার ছোঁয়।
- সহজ। একটা নতুন ঝুঁকি R8, "Rejection emails sent by mistake": সম্ভাবনা ২, তীব্রতা ৫, ফেরানো কঠিন, ৩০০ জন প্রভাবিত, প্রমাণ মতামত, কোনো নিয়ন্ত্রণ নেই। হাতে তার পয়েন্ট, ব্যান্ড ও রুট বের করুন, তারপর কোডে মিলিয়ে নিন।
- মাঝারি। "an applicant says a rank was unfair" নামে একটা ট্রিগার যোগ করুন যা
modelওgroupট্যাগ ছোঁয়। কোন ঝুঁকিগুলো আবার খোলে? অভিযোগটা পড়ার পর কোন রেজিস্টার ফিল্ড হালনাগাদ করবেন? - কঠিন। অনুশীলন: ওপরের পর্যবেক্ষণ থেকে ShortlistHelper-এর সিদ্ধান্ত-রেকর্ড লিখুন: একটা সিদ্ধান্ত (অনুমোদন, শর্তসহ অনুমোদন, বা থামা), শর্তগুলো, কী মীমাংসা হয়নি, কে জবাবদিহি করবেন, আর কবে পর্যালোচনা। তারপর দুই বাক্যে বলুন, এজেন্সি সবচেয়ে নিচের ৮০% স্বয়ংক্রিয়ভাবে বাতিল করতে চাইলে কী বদলাত।
উত্তর
সহজ। ২ x ৫ = ১০ পয়েন্ট, যা "high" (১০ বা বেশি)। রুট "escalate", দুটো কারণসহ: অবশিষ্ট উঁচু, এবং তীব্র অথচ দুর্বল প্রমাণ (তীব্রতা ৫, শুধু মতামত)।
r8 = Risk("R8", "Rejection emails sent by mistake", "individual", 2, 5, "hard", 300, "opinion")
print(r8.inherent(), band(r8.inherent()), route(r8))10 high ('escalate', ['residual high', 'severe+weak evidence'])পাস চেকলিস্ট: ১০ পয়েন্ট, সঠিক ব্যান্ড, দুটো কারণই পাওয়া। দুর্বল উত্তর শুধু ৩০০ জন প্রভাবিত বলে "owner may accept" বলে, তীব্রতা উপেক্ষা করে।
মাঝারি।
TRIGGERS.append(("an applicant says a rank was unfair", {"model", "group"}))
change, touched = TRIGGERS[-1]
print(change, "->", [r.id for r in RISKS if touched & set(r.tags)])an applicant says a rank was unfair -> ['R1', 'R2', 'R3', 'R6']R1, R2, R3 ও R6 আবার খোলে। একটা অভিযোগ একটা ডেটা-বিন্দু। দল সমস্যাটা পুনরুৎপাদন করতে পারলে evidence মতামত থেকে internal-test-এ যেতে পারে। পাস চেকলিস্ট: চারটা ঝুঁকি; একটা ফিল্ডের নাম; একটা অভিযোগকে নকশার প্রমাণ ধরা হয়নি। দুর্বল উত্তর একজন অভিযোগ করেছেন বলে স্কোর বদলায়।
কঠিন। একটা শক্ত উত্তর শর্তসহ অনুমোদন, সর্বত্র চালু করা নয়। একটা যুক্তিসংগত রেকর্ড নিচে; অন্য একটা দল যুক্তিসংগতভাবে থামতে পারে, বা AI-বহির্ভূত বিকল্প দিয়ে শুরু করতে পারে।
Decision record: ShortlistHelper pilot (invented) depth: full date: 2026-10-12 (invented)
Decision: approve with conditions: an 8-week pilot, one job family, at most 600 applications.
Not approved: rollout to all jobs; any automatic rejection; any use on current staff.
Why: saves reading time (about 500 h to about 120 h a year at full volume, toy numbers);
the unread 4560 are the main harm, so a weekly sample of low-ranked CVs is a condition.
Not settled: R4 retention (contract unread), R6 owner, privacy and employment-law review,
tests for the career-gap control (C1) and the group check (C2, only planned).
Accountable: Agency owner for the decision; Head of recruitment for the pilot conditions.
Review: 2027-01-11, or earlier if the vendor changes the model or the pilot stops itself.সবচেয়ে নিচের ৮০% স্বয়ংক্রিয়ভাবে বাতিল হলে কর্মপ্রবাহ "স্বয়ংক্রিয়" হয়ে যায়: না-পড়া ৪৫৬০টা CV কোনো মানুষ ছাড়াই বাতিল হয়। পর্যালোচনার পথ, নমুনা ও বিশেষজ্ঞের মত না থাকা পর্যন্ত রেকর্ডে "stop" বা "আবার escalate" থাকা উচিত। পাস চেকলিস্ট: শর্তসহ স্পষ্ট সিদ্ধান্ত; নমুনার জন্য পরিমাপ ও থামার নিয়ম; কী মীমাংসা হয়নি; জবাবদিহি করবেন এমন একটা ভূমিকা; পর্যালোচনার তারিখ; স্বয়ংক্রিয় রূপটাকে নতুন মূল্যায়ন ধরা। দুর্বল উত্তর বিনা শর্তে অনুমোদন দেয়, বা কী ওজন করা হয়েছে না বলেই থামে।
সারসংক্ষেপ
- নিয়ন্ত্রণ তালিকা করুন অবস্থা, পরীক্ষার ফল ও সীমাসহ; শুধু চালু নিয়ন্ত্রণ গুনুন, আর দাবি ঠিক না করে সন্দেহ জানান।
- AI-বহির্ভূত বিকল্প এবং শুধু-মানুষ, AI-সহায়তায় ও স্বয়ংক্রিয় কর্মপ্রবাহের তুলনা করুন, কতগুলো কেস কোনো মানুষ পড়েন না সেটাসহ।
- স্কোর দেখার আগে মেনে নেওয়া ও এসকালেশনের নিয়ম লিখুন; সীমারেখা বেছে নেওয়া সিদ্ধান্ত, যার যুক্তি দিতে হয়।
- পাইলটের দরকার পরিধি, পর্যালোচনার তারিখ, আর প্রতিটা শর্তের জন্য পরিমাপ, থামার নিয়ম ও মালিক।
- বিশেষজ্ঞ, মালিক ও পুনর্মূল্যায়নের ট্রিগার নির্দিষ্ট করুন, এবং মূল্যায়নের গভীরতা ঝুঁকির সঙ্গে মেলান।
এরপর: নীতি ও ফ্রেমওয়ার্ক বাস্তবে: মূল্যবোধ থেকে পরীক্ষাযোগ্য নিয়ন্ত্রণ সাধারণ নীতিগুলোকে পরীক্ষা করা যায় এমন নিয়ন্ত্রণে বদলায়, এমন একটা ছোট ম্যাপিং দিয়ে যা কোডে যাচাই করা যায়।