04 · Kỹ thuật

Phân tích BA cho dev

Đặc tả kỹ thuật để hiện thực bộ rule: mô hình dữ liệu, thuật toán kèm pseudocode, API và edge case. Trang Rule điều phối là nguồn chân lý nghiệp vụ; trang này tập trung cách hiện thực.

Mô hình dữ liệu

Các thực thể cốt lõi. Kiểu dữ liệu mang tính gợi ý, đội dev điều chỉnh theo stack thực tế.

Employee (Nhân viên)

TrườngKiểuGhi chú
employee_idstring (PK)Mã nhân viên
areasarray<string>Khu vực đăng ký được phép làm
skillsset<skill_id>Tập kỹ năng (binary — có mặt = có kỹ năng)
performance_scoredecimalĐiểm hiệu suất tổng hợp 0–5
statusenumavailable / busy / on_leave
monthly_service_paydecimalLương dịch vụ lũy kế tháng (tính band)

Service & Step

TrườngKiểuGhi chú
service_idstring (PK)
typeenumregular / branch_opening
urgencyenumnormal / urgent
deadlinedatetimeTính tải & cảnh báo trễ
steps[]array<Step>Thứ tự = thứ tự thực hiện
step.skill_idskill_idKỹ năng bắt buộc của bước
step.std_minutesintĐịnh biên thời gian
step.unit_pricedecimalĐơn giá bước
step.crew_sizeintSố người cần (1 hoặc 2)
step.split_ratioarray<decimal>Tỉ lệ chia khi crew_size=2, vd [0.6,0.4]

Skill & Assignment

TrườngKiểuGhi chú
skill.is_uniqueboolTrue nếu ≤1 người sở hữu → tách flow + cảnh báo HR
assignment.flagenumauto_in_band / auto_out_of_band / self_picked / urgent
assignment.statusenumassigned / in_progress / done / returned
assignment.clusterarray<int>Các step thuộc cùng cụm

Thuật toán điều phối

Phân loại đầu vào

function dispatch(service):
  if service.urgency == 'urgent':
      return dispatchUrgent(service)
  if service.type == 'branch_opening':
      return dispatchBranchProject(service)
  return dispatchRegular(service)   // việc lẻ

Việc lẻ — gom cụm

function buildCluster(emp, service, startSeq):
  cluster = []; total = 0
  for step in service.steps[startSeq:]:
      if step.skill_id not in emp.skills: break   // thiếu KN
      if total + step.std_minutes > session_cap: break // trần buổi
      cluster.add(step); total += step.std_minutes
  return cluster, total

Việc lẻ — chọn người

function dispatchRegular(service):
  startSeq = firstUnassignedStep(service)
  skill = service.steps[startSeq].skill_id
  // B1: lọc cứng
  cands = employees.filter(e =>
      skill in e.skills
      AND service.branch.area in e.areas
      AND e.status == 'available'
      AND remainingSessionTime(e) > 0)

  if cands.isEmpty():            // B4 van an toàn
      cands = anyAvailableWithSkill(skill, service.branch.area)
      flag = 'auto_out_of_band'
  else:
      flag = 'auto_in_band'

  // B2-B3: chấm điểm (band qua w4, KHÔNG phải bộ lọc)
  minPay = min(e.monthly_service_pay for e in allActive)
  for e in cands:
      cluster, mins = buildCluster(e, service, startSeq)
      payGap = e.monthly_service_pay - minPay
      e.score = w1*e.performance_score
              + w2*len(e.skills)
              - w3*currentLoadHours(e)
              + w4*max(0, band_limit - payGap)/100000
              + clusterBonus(cluster)
  winner = argmax(cands, by=score)
  return createAssignment(winner, cluster, flag)

Việc khẩn & dự án

function dispatchUrgent(service):
  target = pick(employeesOnSite(service.branch), by=nearest)
  // KHÔNG cắt cụm; giao thêm, cho vượt trần buổi
  a = createAssignment(target, service, flag='urgent',
                       allowOverCap=true, maxMinutes=urgent_handle)
  if spikeDetected() OR overdue(service): escalateAddPeople(service)
  return a

function dispatchBranchProject(service):
  for s in uniqueSkillSteps(service):
      pinAssign(soleOwner(s.skill), s)   // ràng buộc cứng
  crew = []
  while not covers(crew.skills, remainingSkills(service)):
      crew.add(roundRobinNext(eligiblePool(service.area)))
  return assignCrew(crew, service)
Ghi chú thiết kế Band là tiêu chí ưu tiên (cộng điểm w4), không phải bộ lọc — người vượt band vẫn nằm trong cands, chỉ bị điểm w4 thấp hơn. Van an toàn ở nhánh cands rỗng đảm bảo không bao giờ pending.

Gợi ý API / sự kiện

Endpoint / EventMô tả
POST /servicesTiếp nhận việc mới → trigger dispatch()
POST /steps/{id}/completeNV cập nhật step xong → tính lại tải, mở step kế
GET /queue?area=&skill=Hàng đợi việc pending cho NV xem & tự nhận
POST /assignments/{id}/self-pickNV chủ động nhận thêm
POST /assignments/{id}/returnTrả phần cụm còn lại vào hàng đợi
EVENT urgent.createdKích hoạt dispatchUrgent
EVENT skill.unique.detectedCảnh báo HR đào tạo dự phòng

Edge case

Tình huốngXử lý mong đợi
Không ai trong band rảnhGiao cho bất kỳ ai đủ KN rảnh, gắn cờ out_of_band
Không ai có kỹ năng của stepĐẩy cảnh báo điều phối thủ công; không tự gán sai người
NV làm xong sớm, còn việc tồnCho self-pick kể cả ngoài band
Khẩn khi NV đang giữa cụmGiao thêm, không cắt; NV được quyền trả phần còn lại
Step cần 2 ngườiGán 2 người đủ KN, chia lương theo split_ratio
Kỹ năng độc nhất, người đó nghỉDự án đứng; alert HR; không có backup hợp lệ
Cụm vượt 4hCắt tại step làm tổng ≤ trần; phần dư về hàng đợi
Nhiều người cùng được gán đồng thờiCần transaction/lock (optimistic locking hoặc last-write-wins)
Bản đặc tả đầy đủ kèm pseudocode chi tiết có trong tài liệu Word/PDF. PDF Word