콘텐츠로 이동

병원 예약 SaaS 개발기 Part 3: 병원마다 다른 업무시간과 자원으로 예약 가능 시간을 계산하기

작은 로봇 작업자들이 병원과 의사의 시간표를 겹치고 휴식과 휴진 시간을 걷어 낸 뒤 진료실과 장비 조건을 검사하는 어두운 미니어처 작업대
달력에서 비어 보이는 시간과 실제로 예약할 수 있는 시간은 다릅니다. 업무시간부터 장비 상태까지 모든 조건을 통과해야 하나의 예약 가능 시간이 됩니다.

Part 2에서는 예약 상태를 업무 계약으로 설계하고, 허용된 상태 변화와 변경 이력을 함께 관리하는 과정을 살펴봤습니다. 올바른 상태로 예약을 만들 준비가 됐더라도 아직 답하지 못한 질문이 있습니다.

이 환자를 정말 10시에 받을 수 있는가?

달력의 10시가 비어 있다는 사실만으로는 부족합니다. 병원은 문을 열었지만 담당 의사는 11시부터 진료할 수 있을지 모릅니다. 의사 일정은 비어 있어도 한 시간짜리 진료가 점심시간과 겹칠 수 있습니다. 같은 시간에 받을 수 있는 환자 수가 이미 찼거나, 검사 장비가 점검 중일 수도 있습니다.

이번 글에서는 이런 조건을 하나의 거대한 조건문으로 만들지 않고, 시간 범위를 좁혀 가는 계산 과정으로 설계합니다. 요구사항을 업무 언어로 정리하고, 구현과 테스트로 옮긴 뒤, 장비 점검·고장이라는 추가 요구사항이 들어왔을 때 무엇을 보강했는지도 살펴보겠습니다.

요구사항: 빈 시간이 아니라 예약 가능한 시간을 찾는다

섹션 제목: “요구사항: 빈 시간이 아니라 예약 가능한 시간을 찾는다”

병원 예약 SaaS에서는 병원마다 운영 방식이 다릅니다. 서울의 한 병원은 30분 간격으로 예약을 받고 점심시간에 문을 닫지만, 다른 병원은 15분 간격으로 예약을 받고 공휴일에도 운영할 수 있습니다. 같은 병원 안에서도 의사와 진료 유형, 장비에 따라 가능한 시간이 달라집니다.

이를 계산하려면 최소한 다음 정보를 함께 봐야 합니다.

구분예약 가능 시간에 영향을 주는 정보
병원표준 시간대, 요일별 업무시간, 휴식시간, 공휴일 운영 여부, 전일·부분 휴진, 예약 시작 간격, 기본 최대 동시 진료 환자 수
의사담당 병원, 의료진 유형, 요일별 진료시간, 전일·부분 부재, 의사별 최대 동시 진료 환자 수
진료 유형진료 소요시간, 진료를 맡을 의료진 유형, 진료별 최대 동시 환자 수, 필요한 장비와 수량
장비병원이 보유한 수량, 같은 시간대의 예약 사용량, 점검·고장으로 사용할 수 없는 시간

여기서 예약 시작 간격진료 소요시간은 서로 다른 값입니다. 예약을 30분 간격으로 받더라도 어떤 검사는 60분이 걸릴 수 있습니다. 이 경우 후보는 10시, 10시 30분, 11시처럼 30분마다 시작하지만, 각 후보가 차지하는 시간은 60분입니다. 10시에 시작한 진료는 11시까지 사용할 수 있는 의사와 장비가 있어야 합니다.

또 하나 중요한 기준은 날짜와 시간을 어느 지역의 시각으로 해석하느냐입니다. 병원은 자신의 표준 시간대를 가지며, 업무시간과 의사 일정은 그 병원의 현지 날짜와 시각으로 계산합니다. 서울 병원의 월요일 오전 9시와 뉴욕 병원의 월요일 오전 9시는 서로 다른 순간입니다. 사용자나 외부 시스템과 시간을 주고받거나 여러 병원의 일정을 비교할 때 표준 시간대를 적용하고, 한 병원 안의 일정 계산은 그 병원의 현지 시각을 기준으로 유지합니다.

설계: 전체를 막는 조건부터 빠르게 확인한다

섹션 제목: “설계: 전체를 막는 조건부터 빠르게 확인한다”

예약 가능 시간을 계산할 때 모든 조건을 모든 후보 시간에 반복해서 확인할 필요는 없습니다. 하루 전체를 예약할 수 없게 만드는 조건은 계산 초기에 확인할 수 있습니다.

예를 들어 병원이 공휴일에 운영하지 않는데 조회 날짜가 공휴일이라면 결과는 곧바로 빈 목록입니다. 전일 휴진이거나 그 요일의 병원 업무시간이 없을 때도 마찬가지입니다. 담당 의사의 근무 일정이 없거나 하루 전체를 비운 날에도 이후 계산은 의미가 없습니다.

이렇게 하루 전체에 적용되는 조건을 먼저 걸러 내면 남은 단계는 “오늘 예약할 수 있는 시간 범위를 어떻게 좁힐까?”라는 문제로 바뀝니다.

공휴일 운영 여부 · 전일 휴진 · 병원 업무시간 · 의사 근무 여부
→ 하나라도 하루 전체를 막으면 예약 가능 시간 없음
→ 통과하면 병원과 의사의 실제 공통 시간을 계산

이 순서는 성능을 위한 요령에 그치지 않습니다. 기획자와 개발자가 “오늘 예약을 하나도 받지 못하는 이유”와 “특정 시간만 예약하지 못하는 이유”를 구분해서 이야기하게 해 줍니다.

설계: 시간 범위의 교집합에서 사용할 수 없는 시간을 뺀다

섹션 제목: “설계: 시간 범위의 교집합에서 사용할 수 없는 시간을 뺀다”

하루 전체를 운영할 수 있다면 병원의 업무시간과 의사의 진료시간을 겹칩니다. 병원이 9시부터 18시까지 운영하고 의사가 10시부터 16시까지 근무한다면 출발점은 10시부터 16시까지입니다.

여기서 점심시간과 같은 휴식시간, 오후 부분 휴진, 의사의 부분 부재를 차례로 뺍니다. 그러면 하나의 긴 시간 범위가 여러 조각으로 나뉠 수 있습니다. TimeRange.ktcomputeEffectiveRanges는 이 계산을 한곳에 모읍니다.

val effectiveRanges = computeEffectiveRanges(
clinicOpen = opHours.openTime,
clinicClose = opHours.closeTime,
doctorStart = doctorSchedule.startTime,
doctorEnd = doctorSchedule.endTime,
breakTimes = breakTimeRanges,
partialClosures = partialClosureRanges,
doctorAbsences = doctorAbsenceRanges,
)
공휴일과 전일 휴진 같은 하루 전체 조건을 먼저 확인하고, 병원과 의사의 시간 범위에서 휴식·부분 휴진·의사 부재를 뺀 뒤, 진료 소요시간과 동시 진료 수, 장비 조건으로 예약 후보를 거르는 과정
예약 가능 시간은 한 번에 만들어지지 않습니다. 하루 전체 조건을 확인하고, 실제로 함께 일할 수 있는 시간 범위를 구한 뒤, 각 후보가 환자 수와 장비 조건을 만족하는지 검사합니다.

앞의 파이프라인은 시간이 어떤 조건을 통과하며 좁혀지는지 보여줍니다. 실제 요청이 들어왔을 때는 API와 계산 서비스, 병원·의사·예약·장비 정보가 다음 순서로 협력합니다. 하루 전체를 막는 조건은 자원 검사를 시작하기 전에 종료하고, 예약 후보가 만들어진 뒤에만 후보별 기존 예약과 장비 사용량을 반복해서 확인합니다.

사용자가 예약 가능 시간을 조회하면 API와 계산 서비스가 병원·휴일 정보, 의사·진료 정보, 유효 시간 범위, 기존 예약과 장비 상태를 차례로 확인하고 가능한 시간 목록을 반환하는 시퀀스 다이어그램
하루 전체를 막는 조건은 즉시 빈 목록을 반환하고, 가능한 날에만 시간 범위를 계산한 뒤 예약 후보별 환자 수와 장비 상태를 확인합니다. 전체 일정 최적화는 별도의 Solver 경로가 담당합니다.

이 설계의 장점은 규칙이 추가될 위치가 분명하다는 점입니다. 병원 점심시간은 유효 시간 범위를 나누는 규칙이고, 장비 점검은 특정 후보를 탈락시키는 규칙입니다. 둘을 같은 층위의 if 문으로 섞지 않으므로 요구사항이 바뀌어도 영향 범위를 찾기 쉽습니다.

구현: 예약 시작 간격으로 후보를 만들고 진료 소요시간을 보장한다

섹션 제목: “구현: 예약 시작 간격으로 후보를 만들고 진료 소요시간을 보장한다”

SlotCalculationService는 앞에서 구한 유효 시간 범위 안에 예약 후보를 만듭니다. 후보 시작 시각은 병원이 정한 예약 시작 간격에 맞춰 잡고, 종료 시각은 진료 소요시간으로 계산합니다.

for (range in effectiveRanges) {
var current = range.start
while (true) {
val slotEnd = current.plusMinutes(duration.toLong())
if (slotEnd > range.end) break
slotCandidates.add(TimeRange(current, slotEnd))
current = current.plusMinutes(clinic.slotDurationMinutes.toLong())
}
}

이 코드에서 마지막 후보가 유효 범위를 넘어가면 생성을 멈춥니다. 12시에 휴식이 시작하고 진료에 60분이 걸린다면 11시 30분 후보는 만들지 않습니다. 시작할 때는 의사가 비어 있어도 진료를 끝낼 때까지 시간을 보장하지 못하기 때문입니다.

진료 소요시간은 사용자가 별도로 요청한 값이 있으면 그 값을 사용하고, 그렇지 않으면 진료 유형에 설정된 기본 시간을 사용합니다. 담당 의사의 의료진 유형이 해당 진료에 필요한 유형과 맞지 않으면 예약 가능 시간을 반환하지 않습니다. 빈 시간만 찾아서는 이런 업무 조건을 표현할 수 없습니다.

구현: 각 후보에서 환자 수와 장비를 확인한다

섹션 제목: “구현: 각 후보에서 환자 수와 장비를 확인한다”

시간만 놓고 보면 가능한 후보도 실제 자원 때문에 탈락할 수 있습니다. 서비스는 각 후보와 겹치는 기존 예약 수를 세고 최대 동시 진료 환자 수와 비교합니다.

적용할 최대 인원은 진료 유형의 설정을 가장 먼저 보고, 없으면 의사 설정, 그것도 없으면 병원의 기본 설정을 사용합니다. 예를 들어 병원은 기본적으로 두 명까지 받을 수 있어도, 특정 시술은 한 번에 한 명만 받을 수 있습니다. 이 우선순위는 단순한 기본값 처리가 아니라 더 구체적인 업무 정책이 일반 정책을 덮어쓰는 규칙입니다.

장비가 필요한 진료라면 두 가지를 더 확인합니다.

  • 후보 시간이 장비의 점검·고장 시간과 겹치는가
  • 같은 시간대의 기존 예약이 사용 중인 수량을 빼고도 장비가 남아 있는가

장비가 세 대 있다고 해서 언제나 세 대를 쓸 수 있는 것은 아닙니다. 한 대가 점검 중이고 두 대가 다른 예약에 배정됐다면 새 예약을 받을 수 없습니다. 모든 조건을 통과한 결과에는 시작과 종료 시각뿐 아니라 사용할 수 있는 장비, 남은 동시 진료 여유도 함께 담습니다. 다음 단계에서 예약을 확정할 때 어떤 자원을 선택할지 판단할 수 있게 하기 위해서입니다.

테스트: 조건 하나보다 조건이 겹치는 장면을 검증한다

섹션 제목: “테스트: 조건 하나보다 조건이 겹치는 장면을 검증한다”

예약 계산은 정상적인 한 가지 예만으로 검증하기 어렵습니다. 각 규칙이 독립적으로 맞아도 여러 규칙이 겹치는 경계에서 잘못된 후보가 만들어질 수 있기 때문입니다.

SlotCalculationServiceTest는 다음과 같은 병원 업무 장면을 검증합니다.

  • 병원 업무시간 안에서 기본 예약 후보가 만들어지는가
  • 점심시간, 부분 휴진, 의사의 부분 부재를 통과하는 후보만 남는가
  • 병원보다 의사의 진료시간이 짧을 때 공통 시간만 사용하는가
  • 30분 간격으로 예약을 받아도 60분 진료가 끝날 시간을 확보하는가
  • 공휴일 운영 여부와 전일 휴진·부재가 하루 전체 결과에 반영되는가
  • 같은 시간대의 최대 환자 수와 장비 보유 수량을 넘지 않는가
  • 장비 점검·고장 시간과 겹치는 후보가 제외되는가

특히 “60분 진료와 30분 예약 시작 간격”은 기획 문장을 구현 계약으로 바꾸는 좋은 사례입니다. 단순히 후보 개수만 비교하지 않고 각 후보의 시작과 종료를 확인해야, 휴식 직전에 끝나지 못하는 예약을 잘못 노출하는 문제를 막을 수 있습니다.

개발 과정에서 보강한 요구사항: 장비는 보유 여부만으로 충분하지 않다

섹션 제목: “개발 과정에서 보강한 요구사항: 장비는 보유 여부만으로 충분하지 않다”

초기 장비 조건은 필요한 장비를 병원이 보유했는지, 같은 시간대에 사용할 수량이 남았는지를 확인하는 데 집중했습니다. 그러나 실제 병원에서는 장비를 보유하고 있어도 정기 점검이나 고장 때문에 일정 시간 사용할 수 없습니다.

그래서 요구사항을 다음처럼 보강했습니다.

장비가 필요한 진료는 장비의 보유 수량뿐 아니라 점검·고장으로 사용할 수 없는 시간도 피해야 한다.

이 요구사항은 저장할 정보만 추가한다고 끝나지 않았습니다. 장비 사용 불가 시간을 조회하는 EquipmentUnavailabilityService를 예약 가능 시간 계산에 연결하고, 해당 시간과 겹치는 후보를 제외했습니다. 테스트에는 장비 중단 시간이 후보의 일부와 겹치는 경우를 추가했습니다. 전체 일정을 다루는 최적화에서도 같은 정보를 이해해야 하므로 Timefold Solver의 제약 조건과 입력 데이터도 함께 보강해야 합니다.

이 과정은 추가 요구사항을 다루는 방법을 보여줍니다. 새 테이블을 만드는 것으로 완료하지 않고, 그 정보가 어떤 계산과 API, 테스트, 최적화 정책에 전달되어야 하는지 끝까지 추적해야 합니다.

한 건의 가능 시간 조회와 전체 일정 최적화는 다르다

섹션 제목: “한 건의 가능 시간 조회와 전체 일정 최적화는 다르다”

현재 계산 서비스는 병원, 날짜, 의사, 진료 유형이 주어졌을 때 가능한 후보를 빠르게 답합니다. 사용자가 특정 의사의 예약 시간을 조회하는 화면에는 잘 맞습니다. 하지만 여러 환자와 여러 의사, 한정된 장비를 하루 전체에 어떻게 배치해야 가장 좋은지는 답하지 않습니다.

지금 10시를 한 환자에게 배정할 수 있다는 사실과, 그 배정이 오늘 전체 일정에서 가장 좋은 선택이라는 판단은 다른 문제입니다. 먼저 온 요청에 가능한 시간을 하나씩 배정하면 뒤에 들어온 더 긴 진료가 들어갈 자리를 잃거나, 특정 장비의 빈 시간이 잘게 쪼개질 수 있습니다.

따라서 역할을 나눕니다.

예약 가능 시간 계산
→ 한 병원·날짜·의사·진료 유형에 대해 가능한 후보를 빠르게 조회
전체 일정 최적화
→ 여러 예약과 자원의 조합을 비교해 업무 규칙과 운영 목표에 더 잘 맞는 배치를 탐색

다음 편에서는 이 경계를 더 자세히 살펴보고, 어떤 문제부터 Timefold Solver로 넘겨야 하는지 설명하겠습니다.

댓글

GitHub 계정으로 의견을 남기거나 reaction을 남길 수 있습니다.