How modern AI assigns thousands of technicians to thousands of jobs — without breaking a single constraint, in seconds rather than centuries. كيف يوزّع الذكاء الاصطناعي الحديث آلاف الفنيين على آلاف المهام — دون كسر أي قيد، في ثوانٍ بدلاً من قرون.
A maintenance company has 30 technicians and 200 customer jobs spread across a city. Each technician has different skills, working hours, and a starting location. Each job requires a specific skill, fits within a time window, and takes a different amount of time to complete.
تخيّل شركة صيانة لديها ٣٠ فنياً و٢٠٠ مهمة موزّعة على المدينة. كل فني له مهارات مختلفة، وساعات عمل، وموقع انطلاق. وكل مهمة تتطلب مهارة محددة، وتقع ضمن نافذة زمنية، وتستغرق وقتاً مختلفاً.
"Who goes where? When? In what order?" «مَن يذهب إلى أين؟ متى؟ وبأي ترتيب؟»
This is the heart of Field Workforce Scheduling (FWS): a generalization of the classic Vehicle Routing Problem with Time Windows (VRPTW), augmented with skills, priorities, and resource constraints. It belongs to the class of NP-hard combinatorial optimization problems — meaning no known algorithm can solve every instance optimally in polynomial time.
هذي هي جوهر مشكلة Field Workforce Scheduling (FWS): تعميم لمشكلة توجيه المركبات بنوافذ زمنية (VRPTW) الكلاسيكية، مع إضافة المهارات والأولويات وقيود الموارد. وتنتمي إلى فئة مسائل التحسين التوافقية NP-hard — أي لا توجد خوارزمية معروفة تحل كل الحالات بشكل مثالي في زمن متعدد الحدود.
Before any algorithm can solve our problem, we must translate it into mathematical language. This is the most important skill in operations research: turning a messy real-world situation into a precise formulation.
قبل أن تحلّ أي خوارزمية مشكلتنا، يجب علينا ترجمتها إلى لغة رياضية. هذي أهم مهارة في بحوث العمليات: تحويل موقف واقعي معقد إلى صياغة دقيقة.
Now we have a Mixed Integer Linear Program (MILP). Solvers like Gurobi, CPLEX, or open-source OR-Tools CP-SAT can take this formulation and search for an optimal — or near-optimal — solution.
الآن لدينا برنامج خطي عدد صحيح مختلط (MILP). الحلّالات مثل Gurobi، أو CPLEX، أو OR-Tools CP-SAT مفتوح المصدر يمكنها أن تأخذ هذه الصياغة وتبحث عن حلّ أمثل — أو شبه أمثل.
Constraints come in two flavors. Hard constraints must never be violated — a plumber cannot fix a wiring fault. Soft constraints are preferences we'd like to honor but can sacrifice if they conflict — preferring morning appointments, or balancing workload across technicians.
القيود نوعان. القيود الصارمة (Hard) لا يجوز كسرها أبداً — السبّاك لا يمكنه إصلاح عطل كهربائي. القيود اللينة (Soft) هي تفضيلات نحب الالتزام بها، لكن يمكن التضحية بها عند التعارض — كتفضيل المواعيد الصباحية، أو موازنة الحمل بين الفنيين.
No single algorithm dominates. Real-world systems combine several techniques — exact methods for clean sub-problems, metaheuristics for the messy whole, and machine learning for forecasting and adaptation.
لا توجد خوارزمية واحدة تتفوّق على الجميع. الأنظمة الحقيقية تدمج عدة تقنيات — طرق دقيقة للمشاكل الفرعية الصغيرة، وخوارزميات استدلالية للمشكلة الكاملة، وتعلّم آلي للتنبؤ والتكيّف.
Three interactive simulators to build intuition. Move the sliders, watch the schedules form, and see how AI decisions ripple across the system.
ثلاثة محاكيات تفاعلية لبناء الحدس. حرّك المنزلقات، وشاهد الجداول تتكوّن، ولاحظ كيف تنتشر قرارات الذكاء الاصطناعي عبر النظام.
Below is a minimal but complete example using Google OR-Tools in Python. This code solves a small Field Workforce Scheduling instance with skills, time windows, and travel times.
أدناه مثال صغير لكنه كامل باستخدام Google OR-Tools بلغة Python. هذا الكود يحلّ حالة صغيرة من جدولة القوى العاملة الميدانية مع المهارات والنوافذ الزمنية وأزمنة التنقل.
from ortools.sat.python import cp_model
# --- Problem data ---
technicians = ["T1", "T2", "T3"]
jobs = [
# (id, skill, duration_min, earliest, latest)
("J1", "plumb", 60, 540, 720), # 9:00–12:00
("J2", "elec", 90, 600, 780),
("J3", "plumb", 45, 660, 900),
("J4", "hvac", 120, 540, 840),
]
skills = {"T1": ["plumb"], "T2": ["elec", "hvac"], "T3": ["plumb", "hvac"]}
# --- Build the model ---
model = cp_model.CpModel()
# Decision variables
x = {} # x[j, t] = 1 if job j assigned to tech t
start = {} # start time of job j
for j_id, skill, dur, e, l in jobs:
start[j_id] = model.NewIntVar(e, l, f"start_{j_id}")
for t in technicians:
x[j_id, t] = model.NewBoolVar(f"x_{j_id}_{t}")
# Skill matching constraint
if skill not in skills[t]:
model.Add(x[j_id, t] == 0)
# Each job assigned to exactly one technician
for j_id, _, _, _, _ in jobs:
model.Add(sum(x[j_id, t] for t in technicians) == 1)
# --- Solve ---
solver = cp_model.CpSolver()
solver.parameters.max_time_in_seconds = 10.0
status = solver.Solve(model)
if status == cp_model.OPTIMAL:
for j_id, _, _, _, _ in jobs:
for t in technicians:
if solver.Value(x[j_id, t]):
print(f"{j_id} → {t} at minute {solver.Value(start[j_id])}")
And here's a genetic algorithm approach in pure Python — no external libraries — to give you a feel for how metaheuristics search the solution space.
وأدناه نهج الخوارزمية الجينية بلغة Python نقية — بدون مكتبات خارجية — لتذوّق كيف تبحث الخوارزميات الاستدلالية في فضاء الحلول.
import random
def fitness(chromosome, jobs, distances):
# Lower is better: total travel + penalty for unassigned jobs
total = 0
for tech_route in chromosome:
for i in range(len(tech_route) - 1):
total += distances[tech_route[i]][tech_route[i+1]]
return total
def crossover(parent1, parent2):
# One-point crossover between two solutions
cut = random.randint(1, len(parent1) - 1)
return parent1[:cut] + parent2[cut:]
def mutate(chromosome, rate=0.1):
# Swap two random jobs between technicians
if random.random() < rate:
a, b = random.sample(range(len(chromosome)), 2)
chromosome[a], chromosome[b] = chromosome[b], chromosome[a]
return chromosome
def genetic_algorithm(jobs, distances, generations=500, pop_size=100):
# Initial random population
population = [random_solution(jobs) for _ in range(pop_size)]
for gen in range(generations):
# Evaluate fitness
scored = [(fitness(c, jobs, distances), c) for c in population]
scored.sort()
# Select top 50% as parents
parents = [c for _, c in scored[:pop_size // 2]]
# Create next generation via crossover and mutation
next_gen = parents[:]
while len(next_gen) < pop_size:
p1, p2 = random.sample(parents, 2)
child = mutate(crossover(p1, p2))
next_gen.append(child)
population = next_gen
return min(population, key=lambda c: fitness(c, jobs, distances))
These aren't textbook problems — they're systems running today, dispatching workers and saving billions in fuel and labor.
هذي ليست مشاكل في كتاب مدرسي — بل أنظمة تعمل اليوم، توزّع العمالة وتوفّر مليارات في الوقود والأجور.
Six questions, ranging from concept to application. Pick an answer to see whether it's correct — and why.
ست أسئلة، من المفاهيم إلى التطبيق. اختر إجابة لترى إن كانت صحيحة — ولماذا.