大厂真题 / 蚂蚁
蚂蚁 AI Coding 8.20 真题解析:医院门诊排队叫号系统
题面说明
这是 2026 年 8 月 20 日蚂蚁 AI Coding 笔试中披露的另一道工程题。当前能确认的题面来自考试环境截图,截图只展示了 README 前半部分;患者来源、完整业务规则、输出要求、验收命令和隐藏测试规则均未完整展示。
因此本文分成两部分:
- 题面还原:只记录截图中能够确认的内容;
- 解题建议:参考同类医院排队系统给出可落地的工程设计,但不会把推测内容包装成官方固定要求。
一、截图中可以确认的题面
项目标题
医院门诊排队叫号系统
背景
某医院门诊部需要一套信息化排队系统,替代现有的手工叫号方式。医院业务涉及多科室协作、多种患者类型和复杂的就诊流程,系统需要能够灵活应对实际运营中的各种情况。
开发时间
题面标注的开发时间为 2 小时。
系统需要支持的终端
医院方面要求系统支持以下角色。终端形式、访问方式、通信协议和部署方案由开发者自行设计:
- 患者端:供来院就诊的患者使用;
- 医生端:供各科室医生使用;
- 护士端:供分诊护士使用;
- 显示端:供候诊区展示信息;
- 管理端:供医院管理人员使用。
业务场景
医院有多个科室,每个科室有若干诊室和医生。患者来院后需要取号、排队和就诊。
题面当前可见的科室情况包括:
- 综合科室:患者流量较大,号源类型多样;
- 专科:可能有特殊就诊规则;
- 急诊:需要快速响应;
- 其他类型科室。
截图中“患者来源”标题下能够看到的条目是:
- 直接来院的患者。
患者来源部分在截图底部被截断,是否还有其他来源,不能仅根据当前图片确定。完整规则必须以考试环境中的 README 为准。
二、这道题的核心难点
这不是简单的“维护一个先进先出队列”。真正的难点在于:多个角色同时操作多个科室,患者可能有不同类型,医生和护士对队列的操作权限也不同。
1. 角色多,但核心状态应该统一
患者端、医生端、护士端、显示端和管理端只是不同视图与操作入口,不能为每个端维护一套独立状态。推荐由一个中心服务保存业务状态,所有终端通过 API 或事件订阅访问它。
患者端 ─┐
医生端 ─┤
护士端 ─┼─> 排队服务 ─> 数据库/事件流
显示端 ─┤ │
管理端 ─┘ └─> 状态变更通知
2 小时考试中,优先选择一个简单可靠的中心化架构,不要为了“高级”而引入不熟悉的微服务、消息队列或复杂权限系统。
2. 排队规则不能散落在界面代码里
患者取号、护士分诊、医生叫号、过号、完成就诊、取消和转科等行为都属于业务规则。它们应该由队列服务统一校验,而不是让患者端、医生端各自判断一遍。
3. 显示端需要看到稳定的状态
候诊区显示屏通常需要展示:当前科室、诊室、正在就诊号码、下一位或候诊列表。显示端不能通过“猜测最后一次操作”来更新画面,而应读取服务端的当前状态或订阅明确的状态变更事件。
三、推荐的最小可行架构
技术选型
在 2 小时限制下,可以采用:
- Python;
- FastAPI 或 Flask;
- SQLite;
- 简单 HTML/终端界面,或使用现成 TUI 库;
- HTTP API + 短轮询。
如果候选人更熟悉 Node.js,也可以使用 Express/NestJS + SQLite。技术栈不应成为重点,重点是核心流程能运行、状态可验证、异常不会破坏队列。
推荐目录
hospital_queue/
├── app.py # 服务启动与路由注册
├── models.py # 患者、科室、医生、队列模型
├── queue_service.py # 取号、叫号、过号、完成等业务逻辑
├── repository.py # SQLite 或内存存储
├── schemas.py # API 请求和响应结构
├── permissions.py # 角色权限校验
├── display_client.py # 显示端客户端(可选)
├── tests/
│ ├── test_queue.py
│ └── test_permissions.py
└── README.md
考试时间不足时,可以将多个文件合并,但不要把所有规则都写在路由函数里。至少要把“存储”“队列规则”和“接口”分开。
最小闭环
第一版只需要打通这条链路:
患者取号
-> 护士分诊到科室/号源类型
-> 医生叫下一位
-> 显示端刷新
-> 医生开始接诊
-> 医生完成或标记过号
-> 队列进入下一位
先保证这个闭环可运行,再补急诊优先级、转科、预约患者、统计报表等扩展能力。
四、核心领域模型
以下模型是解题建议,不代表截图中已经明确给出了这些字段。实际实现时要根据完整 README 调整字段名和接口格式。
患者与就诊记录
from dataclasses import dataclass
from enum import Enum
from typing import Optional
class PatientType(str, Enum):
NORMAL = "normal"
EMERGENCY = "emergency"
APPOINTMENT = "appointment"
PRIORITY = "priority"
class VisitStatus(str, Enum):
WAITING = "waiting"
CALLED = "called"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
SKIPPED = "skipped"
CANCELLED = "cancelled"
@dataclass
class Visit:
visit_id: str
queue_number: str
patient_type: PatientType
department_id: str
room_id: Optional[str]
doctor_id: Optional[str]
status: VisitStatus = VisitStatus.WAITING
priority: int = 0
created_at: str = ""
实现中还可以增加挂号类型、预约时间、创建时间和来源渠道。不要一开始添加无法验证用途的大量字段。
科室、诊室和医生
@dataclass
class Department:
department_id: str
name: str
department_type: str
enabled: bool = True
@dataclass
class Room:
room_id: str
department_id: str
name: str
doctor_id: Optional[str] = None
enabled: bool = True
@dataclass
class Doctor:
doctor_id: str
name: str
department_id: str
room_id: Optional[str] = None
online: bool = True
实际考试中,医生是否固定绑定诊室、一个医生能否服务多个科室,需要继续阅读 README 或通过业务规则确认,不应直接假设。
五、排队策略:先把规则显式化
基础 FIFO 队列
普通患者可以按照取号时间先进先出:
from collections import deque
class SimpleQueue:
def __init__(self):
self.items = deque()
def push(self, visit_id: str) -> None:
self.items.append(visit_id)
def pop(self) -> str | None:
return self.items.popleft() if self.items else None
但医院场景通常不只有一个普通队列。更好的抽象是“队列 + 调度策略”:
class QueuePolicy:
def choose_next(self, waiting_visits):
raise NotImplementedError
class PriorityPolicy(QueuePolicy):
def choose_next(self, waiting_visits):
return min(
waiting_visits,
key=lambda item: (-item.priority, item.created_at),
)
优先级不能只写在前端
急诊优先、预约优先或特殊患者优先,都必须在服务端统一计算。否则患者端显示的下一位和医生端叫出的下一位可能不一致。
建议将调度规则抽成可测试函数:
def choose_next(visits):
waiting = [v for v in visits if v.status == VisitStatus.WAITING]
if not waiting:
return None
return max(
waiting,
key=lambda v: (v.priority, -int(v.created_at_timestamp)),
)
考试实现时,需要特别注意时间戳比较方式。不要把格式不统一的时间字符串直接拿来排序。
防止普通患者无限等待
如果题面没有明确要求,不要擅自设计复杂的动态优先级。但可以在设计说明中指出:若急诊持续插队,普通患者可能饥饿;可通过优先级老化、每若干位优先患者服务一位普通患者等规则改善。
六、状态机设计
一个就诊记录的最小状态可以设计为:
WAITING
├── CALLED
│ ├── IN_PROGRESS
│ │ └── COMPLETED
│ └── SKIPPED
├── CANCELLED
└── TRANSFERRED(如果题面要求转科)
不要允许任意状态互相跳转。例如:
- 已完成的就诊不能再次变成等待中;
- 未叫号的患者不能直接完成就诊;
- 已取消的记录不能被医生叫号;
- 一个患者不能同时出现在两个科室的等待队列中。
状态转换表
VALID_TRANSITIONS = {
"waiting": {"called", "cancelled"},
"called": {"in_progress", "skipped", "cancelled"},
"in_progress": {"completed", "skipped"},
"completed": set(),
"skipped": {"called", "cancelled"},
"cancelled": set(),
}
def can_transition(current: str, target: str) -> bool:
return target in VALID_TRANSITIONS.get(current, set())
skipped -> called 是否允许,需要由完整题面决定。如果题面没有说明,可以选择“过号后重新排队”,并在 README 中明确记录该设计决策。
状态更新必须原子化
叫号操作至少应完成三件事:
- 找到符合规则的下一位等待患者;
- 将其状态改为
called; - 记录诊室、医生和操作时间。
这三步不能被多个医生请求交错执行,否则两个医生可能叫到同一个患者。SQLite 场景下可使用事务或服务层锁;单进程考试实现中至少要用互斥锁保护“选择 + 更新”过程。
七、API 设计建议
以下是推荐的最小接口集合,具体路径和字段以完整 README 为准。
| Method | Path | 作用 |
|---|---|---|
GET |
/departments |
查询启用的科室 |
GET |
/departments/{id}/queue |
查看候诊队列 |
POST |
/visits |
患者取号或创建就诊记录 |
POST |
/visits/{id}/triage |
护士分诊 |
POST |
/departments/{id}/call-next |
医生叫下一位 |
POST |
/visits/{id}/start |
开始接诊 |
POST |
/visits/{id}/complete |
完成就诊 |
POST |
/visits/{id}/skip |
过号 |
GET |
/display/{department_id} |
获取显示端数据 |
GET |
/health |
健康检查 |
取号接口
@app.post("/visits")
def create_visit(request: CreateVisitRequest):
department = department_repo.get(request.department_id)
if department is None or not department.enabled:
raise HTTPException(status_code=404, detail="department not found")
visit = queue_service.register(
department_id=request.department_id,
patient_type=request.patient_type,
)
return visit
叫号接口
@app.post("/departments/{department_id}/call-next")
def call_next(department_id: str, user=Depends(require_doctor)):
visit = queue_service.call_next(
department_id=department_id,
doctor_id=user.doctor_id,
)
if visit is None:
raise HTTPException(status_code=404, detail="no waiting patient")
return visit
不要把角色权限只放在前端按钮上。隐藏测试可以直接调用 API,服务端必须再次校验。
八、五类终端怎么做
1. 患者端
最小功能:
- 选择科室或就诊类型;
- 取号;
- 查看自己的号码、前方等待人数和当前叫号;
- 取消或确认就诊,若题面要求则实现。
患者端不应直接修改队列顺序,也不应决定自己的优先级。
2. 护士端
最小功能:
- 查看待分诊患者;
- 将患者分配到科室、诊室或号源类型;
- 修正明显的登记信息;
- 查看队列异常。
护士端是多科室协作的关键入口。分诊动作应记录操作者和时间,便于追溯。
3. 医生端
最小功能:
- 查看当前诊室队列;
- 叫下一位;
- 开始接诊;
- 完成、过号或转诊。
医生只能操作自己有权限的科室或诊室,不能从其他科室队列叫号。
4. 显示端
显示端优先保证清晰、稳定和自动刷新:
综合科 3 诊室
当前就诊:A023
请 A024 到 3 诊室
候诊人数:12
可以先用 2—5 秒轮询实现,避免在考试中为 WebSocket 处理连接管理、断线重连和广播一致性。
5. 管理端
最小功能:
- 配置科室、诊室和医生;
- 启停号源或诊室;
- 查询当日队列和就诊记录;
- 查看操作日志。
管理端不需要一开始就做完整后台页面。命令行或简易表单也可以,只要能证明核心管理能力。
九、数据存储与一致性
SQLite 优先
2 小时内,SQLite 通常比手写 JSON 文件更稳妥:
- 支持事务;
- 重启后数据仍在;
- 可以用唯一约束防止重复号;
- 查询和统计比内存结构更容易验证。
建议至少保存:
- 患者/就诊记录;
- 科室、诊室和医生配置;
- 状态变更记录;
- 取号和操作时间。
操作日志
@dataclass
class AuditLog:
log_id: str
actor_id: str
actor_role: str
action: str
target_id: str
before_status: str | None
after_status: str | None
created_at: str
出现“叫号后显示端没刷新”“过号后又被叫到”等问题时,没有操作日志就很难定位。
幂等性
网络重试可能导致同一个请求发送两次。例如患者端重复点击“取号”,应该通过请求 ID、业务唯一键或服务端去重,避免生成两个号码。
医生完成就诊的请求也应该尽量幂等:对已经完成的记录重复提交,不应产生第二次完成事件或重复扣减统计。
十、测试重点
队列基础测试
def test_normal_patient_gets_number():
visit = service.register("general", "normal")
assert visit.status == VisitStatus.WAITING
assert visit.queue_number
def test_call_next_returns_waiting_patient():
first = service.register("general", "normal")
second = service.register("general", "normal")
called = service.call_next("general", "doctor-1")
assert called.visit_id == first.visit_id
assert called.status == VisitStatus.CALLED
def test_completed_visit_is_not_called_again():
visit = service.register("general", "normal")
service.call_next("general", "doctor-1")
service.start(visit.visit_id, "doctor-1")
service.complete(visit.visit_id, "doctor-1")
assert service.call_next("general", "doctor-1") is None
优先级测试
- 急诊患者是否按题面规则优先;
- 普通患者是否仍能进入队列;
- 同优先级是否按取号时间稳定排序;
- 医生叫号后,其他医生是否不能重复领取同一患者。
权限测试
- 患者不能调用医生的完成接口;
- 医生不能操作不属于自己的科室;
- 普通医生不能修改科室配置;
- 管理员权限不能通过修改请求体伪造。
异常测试
- 不存在的科室;
- 已停用的诊室;
- 空队列叫号;
- 重复完成;
- 非法状态跳转;
- 患者重复取号;
- 并发叫号;
- 服务重启后的数据恢复。
显示端测试
显示端不应显示已取消或已完成的患者,也不应因为一个接口请求失败而退出。可以对读取接口做有限重试,并在页面上显示“服务暂时不可用”。
十一、两小时作答安排
0—10 分钟:读 README 和盘点规则
- 找出完整角色、患者来源、科室类型和状态要求;
- 确认交付物、启动方式、测试命令和接口格式;
- 把不明确的规则列成“待确认项”,不要让 Agent 自行脑补。
10—25 分钟:确定架构和状态机
- 选中心化服务;
- 定义就诊记录和角色;
- 画出合法状态流转;
- 确定队列调度规则和数据存储。
25—65 分钟:实现核心服务
优先完成:
- 科室和诊室配置;
- 患者取号;
- 护士分诊;
- 医生叫号;
- 开始接诊、完成和过号;
- 队列查询。
此时不做复杂页面,先用 API 或命令行验证闭环。
65—85 分钟:接入终端
先做最简单的患者端、医生端和显示端。护士端、管理端可以先用 API 或简易界面,只要功能可演示、权限可验证。
85—105 分钟:补一致性和权限
- 状态转换校验;
- 并发叫号保护;
- 角色权限;
- 重复请求幂等;
- 操作日志。
105—115 分钟:测试和文档
按 README 的命令完整运行,补充正常流程、空队列、非法状态和权限测试。
115—120 分钟:交付检查
- 服务能启动;
- 数据库或数据文件路径正确;
- README 中的命令可复现;
- 没有调试输出、硬编码密钥和无关临时文件;
- 确认“完成任务”前先运行最后一遍测试。
十二、CodeFuse Prompt 模板
Prompt 1:先读题和盘点业务
请先完整阅读当前目录的 README.md,不要修改任何文件。
请输出:
1. 所有终端、角色和权限;
2. 患者来源、科室类型、诊室和医生关系;
3. 患者从取号到完成就诊的完整状态流转;
4. 排队、优先级、过号、取消和转诊规则;
5. 题目要求的接口、输出字段、启动命令和验收命令;
6. README 中没有明确说明、需要我做设计决策的地方。
所有结论必须来自当前 README 或项目文件。无法确认的内容请标记为“待确认”,不要自行补全。
Prompt 2:实现核心领域服务
基于已经确认的题面,实现医院门诊排队系统的核心领域服务。
要求:
- 先定义患者、就诊记录、科室、诊室、医生和状态模型;
- 将排队和优先级规则放在服务层,不要散落在接口或界面代码中;
- 所有状态转换必须校验合法性;
- 叫号的“选择下一位 + 更新状态”必须是原子操作;
- 不允许同一就诊记录同时被两个医生叫到;
- 对空队列、非法科室、重复请求和非法状态返回明确错误;
- 遵守 README 中实际指定的字段和接口,不要新增无依据的固定规则。
先实现可测试的核心服务,再接入终端。
Prompt 3:接入终端和显示端
核心服务已经完成。请分别实现患者端、护士端、医生端、显示端和管理端的最小可用功能。
要求:
- 每个终端只展示和操作自己有权限的功能;
- 所有写操作必须由服务端再次校验权限;
- 显示端只读取服务端当前状态,不在本地复制一套队列;
- 优先使用简单 HTTP 轮询,不引入不必要的复杂基础设施;
- 网络错误时界面不崩溃,并显示可理解的错误;
- 先列出每个终端的验收步骤,再逐个实现。
Prompt 4:隐藏测试审查
请对当前项目做一次面向隐藏测试的审查,不要整体重写。
重点检查:
1. 多个科室、多个诊室和多个医生是否相互隔离;
2. 急诊、预约或其他患者类型是否按 README 规则处理;
3. 两个医生同时叫号时是否可能拿到同一患者;
4. 过号、取消、转诊和完成是否存在非法状态跳转;
5. 患者、医生、护士、显示端和管理员权限是否正确;
6. 重复点击取号或重复提交是否产生重复记录;
7. 服务重启后数据是否丢失;
8. 显示端是否会展示已完成或已取消的记录;
9. 空队列、无效 ID、停用科室和网络失败是否有明确处理;
10. 代码、测试和 README 的启动命令是否一致。
先输出问题列表和最小修复方案,再逐项修改并运行相关测试。
十三、常见失分点
- 把所有患者放进一个全局队列:多科室场景下会造成跨科室叫号。
- 只在前端做权限控制:隐藏测试直接请求服务端时会失效。
- 医生端各自维护本地队列:多个医生之间会出现重复叫号。
- 用数组下标表示状态:删除、过号和转诊后很容易产生错位。
- 任意状态都能修改:缺少状态机,完成后的记录可能再次被叫号。
- 先做复杂界面:2 小时内应优先完成服务层和核心流程。
- 没有明确优先级规则:急诊或特殊患者的调度会变成随机行为。
- 没有操作日志:出现叫号异常时无法复盘。
- 没有测试并发和重复请求:这是排队系统最容易暴露的问题。
- 根据截图补全不可见题面:截图之外的字段、患者来源和验收标准必须回到完整 README 确认。
小结
医院门诊排队系统的稳定解法可以概括为:
读 README
-> 明确角色和业务规则
-> 建模就诊状态
-> 中心化存储队列
-> 服务端统一调度与鉴权
-> 终端按角色读取和操作
-> 用状态机、并发和异常测试验收
这道题考的不是页面做得多漂亮,而是能否在 2 小时内把一个多角色、多科室、带状态流转的业务系统做出最小闭环。候选人应让 Agent 负责代码产出,但自己必须掌握状态模型、排队规则、权限边界和验收标准。