스마트엠투엠
경력근거: 국내 주요항만에서 실제 운영 중인 물류 데이터 플랫폼을 함께 만들 분을 찾습니다.
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 국내 주요항만에서 실제 운영 중인 물류 데이터 플랫폼을 함께 만들 사람을 찾는다고 적고, 근무지도 본사가 아니라 부산항 옆 중앙동 사무소로 지정합니다. 스마트엠투엠은 부산항만공사 통합 물류 플랫폼인 체인포털을 운영하며 환적 모니터링과 전자 인수증, 통합 정보조회를 다룹니다. 예약과 선박 일정이 실제로 한 플랫폼 안에 함께 들어와 있다면 이 화면은 이미 있는 두 데이터를 잇는 일이고, 아직 갈라져 있다면 어느 시스템에서 어느 시각을 받아 올지가 첫 질문이 됩니다. 어느 쪽이든 공고가 말한 대용량 데이터 테이블과 실시간 모니터링 화면은 하루 수천 건의 예약을 한 표에 세우는 이 화면이 곧 그 문제입니다. 다만 이 포트폴리오는 두 흐름을 규칙으로 잇고 대용량 표를 화면으로 세우는 설계를 보여줍니다. 항만 운영 실무나 터미널별 요율 규정에 대한 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
주어진 화면을 구현하는 자리가 아니라고 못박고, 발주처·현장 사용자 미팅으로 요구사항을 듣고 정의한 뒤 우선순위와 영향범위를 판단하라고 적습니다. 무엇을 만들지부터 같이 정하는 자리입니다.
Figma로 화면을 설계하고 발주처 합의를 거쳐 구현까지 전 과정을 수행한다고 적습니다. 설계를 넘겨받는 자리가 아니라 그림을 그려 합의를 받아 오는 자리입니다.
대용량 데이터 테이블과 실시간 모니터링 대시보드 같은 복잡도 높은 화면 구현을 주요업무에 두고, 우대에 가상화·리렌더링 제어 같은 렌더링 성능 최적화를 답니다. 행이 많아질 때 화면이 버티는지가 일의 중심입니다.
배차 담당자가 오늘 잡아 둔 반출입 예약을 열면 컨테이너마다 그 시간대로 내보내도 되는지가 붙습니다. 예약 슬롯을 선박의 양하 완료·선적 마감 시각, 터미널 게이트 운영 시간과 최소 접수 시간에 견주는 분 단위 계산이 갈래와 걸린 기준 이름을 함께 정하고, 모델은 기사에게 그대로 전할 메모를 씁니다. 몇 시로 옮기라는 말까지 한 줄에 담는 일입니다.
부산항 터미널로 컨테이너를 실어 나를 트럭을 하루 단위로 배차하는 운송사 배차 담당자 — 예약 화면과 선박 일정 화면이 따로 놀아, 두 화면을 번갈아 열어 눈으로 맞춰 보고 배차를 확정한다
헛걸음이 생기는 이유는 담당자가 부주의해서가 아니라 예약 시각과 배 시각이 서로 다른 화면에 있기 때문입니다. 두 시각을 컨테이너 한 줄에 나란히 올려 두면 어느 예약을 몇 시로 옮겨야 하는지가 배차를 확정하기 전에 보입니다.
배차 담당자가 쓰는 화면 하나가 돕니다. 반출입 예약과 선박 일정, 터미널 기준표를 올리면 컨테이너마다 판정이 붙고 판정별 묶음과 근거 시각이 한 화면에 뜹니다. 1주차는 터미널 시스템에 붙지 않고 예약 화면에서 내려받은 파일을 그대로 읽는 것으로 시작합니다.
배차 담당자가 손으로 정리한 60건으로 판정 일치율을 자동으로 내고, 어긋난 건이 선박 일정에서 갈라졌는지 기준표에서 갈라졌는지 나눠 리포트로 냅니다. 예약 5,000행을 한 표에 올려도 스크롤이 끊기지 않도록 목록을 가상화합니다.
양하 완료와 선적 마감 시각이 바뀔 때마다 그 배에 걸린 예약만 다시 판정해 화면으로 밀어 보냅니다. 담당자가 새로고침하지 않아도 오전에 통과한 예약이 오후에 걸리면 목록 위로 올라옵니다.
배차 담당자가 오늘 예약과 선박 일정을 올리면 옮겨야 할 컨테이너가 판정별로 뜨고, 어느 시각이 그 판정을 만들었는지 그 자리에서 펼쳐집니다.
배를 지정하지 않은 반입, 양하 완료 시각이 안 들어온 배, 무료장치 종료 시각이 없는 선적을 섞은 입력 30종을 넣어도 화면이 죽지 않고 그 예약이 왜 판정 없이 남았는지 읽힙니다.
열 이름이 빠지거나 시각 칸이 비거나 인코딩이 깨진 파일 12종을 올려도 목록 대신 어느 줄이 왜 걸렸는지 이슈로 뜨고 나머지 예약은 그대로 판정됩니다.
예약 5,000행을 한 표에 올려 스크롤을 끝까지 내려도 줄이 밀리지 않고 그려집니다.
배차 담당자가 손으로 정리한 60건으로 판정 일치율을 냅니다. 단, 예약과 정답을 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 헛걸음 감소폭은 스마트엠투엠의 실 반출입 예약과 터미널 일정이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
판정 일치율은 제가 만든 예약과 선박 일정에 제가 정답을 붙여 낸 숫자라 성능 증명이 아닙니다. 면접에서는 실제 터미널 공지와 예약 파일을 받았을 때 기준표부터 어떻게 채울지로 이야기하겠습니다.
예약을 지어낸 사람이 정답도 매겼으니 맞는 것은 당연합니다. 배가 지정되지 않은 반입, 양하 완료 시각이 비어 있는 배, 접수 시간 제한이 0분인 터미널, 슬롯이 끝나는 시각과 문 닫는 시각이 똑같은 예약을 더미에 일부러 섞어 쉬운 예약만 골라 푸는 함정을 피합니다.
선박 일정은 기상과 앞 배 작업에 따라 시간 단위로 바뀝니다. 판정은 파일을 올린 시점의 일정을 말하므로 대조 시각을 화면에 적어 두고, 실시간에 가까워지려면 일정 변경을 받아 오는 3주차 연동이 있어야 합니다.
게이트 운영 시간과 최소 접수 시간은 터미널이 공지로 바꾸고 성수기에는 임시 운영이 붙습니다. 기준표를 코드에서 빼 화면에서 고치게 두었고, 기준표에 아직 없는 터미널·갈래 조합은 통과시키지 않고 목록에 남겨 담당자가 그 줄부터 채우게 합니다.
실제 도입에서는 터미널마다 무료장치 기산 시점과 요율표가 다르고, 같은 컨테이너가 반출과 선적으로 두 번 잡히기도 합니다. 요율 계산과 예약 묶음 처리는 도입 단계 과제로 분리합니다.
예약을 실제로 바꾸는 일은 터미널 시스템 계정이 있어야 하므로 이 포트폴리오의 범위 밖입니다. 여기서는 어느 예약을 몇 시로 옮길지 정한 목록을 내는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
오늘 잡아 둔 컨테이너 반출입 예약입니다. 판정 대상이며, 배를 지정하지 않은 반입과 무료장치 종료 시각이 이미 지난 예약을 일부러 섞었습니다.
| container_no | terminal_code | move_type | booked_at | slot_start | slot_end | vessel_call | free_time_until |
|---|---|---|---|---|---|---|---|
| KOCU4417820 | PNC3 | 반출 | 2026-09-08T06:40:00 | 2026-09-08T10:20:00 | 2026-09-08T11:20:00 | VC-2609E | 2026-09-12T00:00:00 |
| TGHU8802115 | PNC3 | 선적 | 2026-09-08T07:10:00 | 2026-09-08T16:30:00 | 2026-09-08T17:30:00 | VC-2609E | — |
| BSIU2213904 | PNC3 | 반출 | 2026-09-07T18:00:00 | 2026-09-08T05:10:00 | 2026-09-08T06:10:00 | VC-2611W | 2026-09-13T00:00:00 |
| HJCU7719033 | PNC3 | 반출 | 2026-09-08T06:50:00 | 2026-09-08T10:00:00 | 2026-09-08T11:00:00 | VC-2622E | 2026-09-08T00:00:00 |
| MSCU5561208 | BNCT | 반출 | 2026-09-08T07:20:00 | 2026-09-08T13:00:00 | 2026-09-08T14:00:00 | VC-2615E | 2026-09-14T00:00:00 |
아래 JSON Schema 에 맞는 반출입 예약 행을 더 만들어 주세요. {"type":"object","properties":{"container_no":{"type":"string"},"terminal_code":{"type":"string","enum":["PNC3","BNCT","HJNC","GWCT"]},"move_type":{"type":"string","enum":["반출","반입","선적"]},"booked_at":{"type":"string"},"slot_start":{"type":"string"},"slot_end":{"type":"string"},"vessel_call":{"type":["string","null"]},"free_time_until":{"type":["string","null"]}},"required":["container_no","terminal_code","move_type","booked_at","slot_start","slot_end","vessel_call","free_time_until"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) vessel_call 이 null 인 반입 예약, (2) free_time_until 이 null 인 선적 예약, (3) slot_end 가 게이트 닫는 시각과 정확히 같은 예약, (4) booked_at 과 slot_start 가 한 시간도 안 벌어진 예약. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"container_no":{"type":"string"},"terminal_code":{"type":"string","enum":["PNC3","BNCT","HJNC","GWCT"]},"move_type":{"type":"string","enum":["반출","반입","선적"]},"booked_at":{"type":"string"},"slot_start":{"type":"string"},"slot_end":{"type":"string"},"vessel_call":{"type":["string","null"]},"free_time_until":{"type":["string","null"]}},"required":["container_no","terminal_code","move_type","booked_at","slot_start","slot_end","vessel_call","free_time_until"]}터미널에 들어오는 배의 접안과 작업 시각입니다. 대조의 다른 쪽 축이며, 양하가 아직 진행 중이라 완료 시각이 비어 있는 배와 선적 마감이 안 잡힌 배를 담았습니다.
| vessel_call | terminal_code | vessel_name | berth_eta | berth_etd | discharge_done_at | cargo_cutoff_at |
|---|---|---|---|---|---|---|
| VC-2609E | PNC3 | 머스크 산 | 2026-09-08T04:10:00 | 2026-09-09T02:00:00 | 2026-09-08T12:40:00 | 2026-09-08T16:00:00 |
| VC-2611W | PNC3 | HMM 오션 | 2026-09-07T18:20:00 | 2026-09-08T09:00:00 | 2026-09-07T23:40:00 | 2026-09-07T20:00:00 |
| VC-2615E | BNCT | ONE 이글 | 2026-09-08T06:00:00 | 2026-09-08T23:00:00 | — | 2026-09-08T21:00:00 |
| VC-2620W | HJNC | 완하이 305 | 2026-09-04T03:00:00 | 2026-09-04T19:00:00 | 2026-09-04T15:00:00 | 2026-09-04T12:00:00 |
| VC-2622E | PNC3 | 에버그린 유니티 | 2026-09-02T10:00:00 | 2026-09-02T22:00:00 | 2026-09-02T18:00:00 | 2026-09-02T14:00:00 |
아래 JSON Schema 에 맞는 선박 일정 행을 더 만들어 주세요. {"type":"object","properties":{"vessel_call":{"type":"string"},"terminal_code":{"type":"string","enum":["PNC3","BNCT","HJNC","GWCT"]},"vessel_name":{"type":"string"},"berth_eta":{"type":"string"},"berth_etd":{"type":"string"},"discharge_done_at":{"type":["string","null"]},"cargo_cutoff_at":{"type":["string","null"]}},"required":["vessel_call","terminal_code","vessel_name","berth_eta","berth_etd","discharge_done_at","cargo_cutoff_at"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) discharge_done_at 이 null 인 작업 중인 배, (2) cargo_cutoff_at 이 null 인 배, (3) 접안이 자정을 넘겨 berth_etd 가 다음 날인 배, (4) 접안 종료가 일주일 전인 배. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"vessel_call":{"type":"string"},"terminal_code":{"type":"string","enum":["PNC3","BNCT","HJNC","GWCT"]},"vessel_name":{"type":"string"},"berth_eta":{"type":"string"},"berth_etd":{"type":"string"},"discharge_done_at":{"type":["string","null"]},"cargo_cutoff_at":{"type":["string","null"]}},"required":["vessel_call","terminal_code","vessel_name","berth_eta","berth_etd","discharge_done_at","cargo_cutoff_at"]}터미널이 공지한 게이트 운영 시간과 최소 접수 시간, 무료장치 일수입니다. 판정에 쓰이는 기준은 예약이나 선박 일정에 붙어 있지 않고 전부 이 표에서 오며, 터미널 넷과 반출·반입·선적 세 갈래의 열두 조합을 한 줄씩 채웁니다. 지원자는 터미널별 운영 공지와 요율 안내를 분기마다 옮겨 이 표를 만듭니다.
| terminal_code | move_type | gate_open_at | gate_close_at | min_lead_minutes | free_days |
|---|---|---|---|---|---|
| PNC3 | 반출 | 06:00 | 22:00 | 90 | 5 |
| PNC3 | 반입 | 06:00 | 22:00 | 0 | 5 |
| PNC3 | 선적 | 06:00 | 20:00 | 120 | 5 |
| BNCT | 반출 | 07:00 | 21:00 | 90 | 4 |
| BNCT | 반입 | 07:00 | 21:00 | 60 | 4 |
아래 JSON Schema 에 맞는 터미널 기준 행을 더 만들어 주세요. {"type":"object","properties":{"terminal_code":{"type":"string","enum":["PNC3","BNCT","HJNC","GWCT"]},"move_type":{"type":"string","enum":["반출","반입","선적"]},"gate_open_at":{"type":"string"},"gate_close_at":{"type":"string"},"min_lead_minutes":{"type":"integer","minimum":0},"free_days":{"type":"integer","minimum":0}},"required":["terminal_code","move_type","gate_open_at","gate_close_at","min_lead_minutes","free_days"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) min_lead_minutes 가 0 인 터미널, (2) gate_open_at 이 00:00 이고 gate_close_at 이 24:00 인 종일 운영 터미널, (3) 같은 터미널에서 반출과 선적의 닫는 시각이 다른 행, (4) free_days 가 사흘에 못 미치는 터미널. 터미널 넷과 반출·반입·선적 세 갈래의 열두 조합을 빠짐없이 채우고, 판정 시험용으로는 한 조합을 일부러 뺀 벌을 따로 하나 더 만들어 기준표에 없는 조합이 화면에서 어떻게 다뤄지는지 볼 수 있게 합니다. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"terminal_code":{"type":"string","enum":["PNC3","BNCT","HJNC","GWCT"]},"move_type":{"type":"string","enum":["반출","반입","선적"]},"gate_open_at":{"type":"string"},"gate_close_at":{"type":"string"},"min_lead_minutes":{"type":"integer","minimum":0},"free_days":{"type":"integer","minimum":0}},"required":["terminal_code","move_type","gate_open_at","gate_close_at","min_lead_minutes","free_days"]}| TypeScript | 판정 규칙부터 화면까지 한 언어로 씁니다. 분과 날수, 시각 문자열이 섞이지 않도록 계산 함수마다 단위를 타입으로 박습니다. |
| React | 배차 담당자가 보는 예약 점검 화면입니다. 판정별 묶음과 근거 시각을 한 화면에 둡니다. |
| Next.js | 예약 목록을 서버에서 미리 그려 아침에 화면을 열자마자 오늘 치가 보이게 합니다. |
| 가상화 | 예약 5,000행을 한 표에 세우고 보이는 줄만 그려 스크롤이 끊기지 않게 합니다. |
| 리렌더링 제어 | 선박 일정이 바뀌어 뒤집힌 예약만 다시 그리고 나머지 줄은 건드리지 않습니다. |
| Figma | 판정 카드와 근거 표의 배치를 먼저 그려 발주처와 합의한 뒤 구현으로 넘어갑니다. |
| i18n | 터미널과 선사 이름을 한국어와 영어로 나눠 두고, 해외 선사와 주고받는 화면에서는 영문 표기를 씁니다. |
| CI | 판정 규칙 시험과 타입 검사, 빌드를 올릴 때마다 자동으로 돌려 기준표가 바뀌어도 규칙이 깨진 것을 그 자리에서 잡습니다. |
| LLM | 판정이 정해진 뒤 기사에게 전할 메모를 씁니다. 다섯 판정 값에는 손대지 못합니다. |
| Claude Code | 시각 계산 시험과 더미데이터 증강 스크립트를 쓰는 데 씁니다. 생성한 코드는 시험을 돌려 확인한 뒤 넣습니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 배차 담당자가 정리한 60건과 판정을 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 선박 일정이 바뀔 때 걸린 예약만 다시 판정해 화면으로 밀어 보내는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · container_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
잡은 시간대로 나감그대로 진행양하 전 도착양하 전 도착선적 마감 넘김선적 마감 넘김게이트 닫힌 시간게이트·접수 시간보관료 구간 진입보관료 구간예약 한 건을 그 시간대로 내보내도 되는지 정한 판정과 그 판정을 만든 시각을 남기는 형식입니다. 판정 다섯 값과 걸린 기준 이름은 시각 비교 규칙이 정하고 기사 메모만 모델이 씁니다.
{"type":"object","properties":{"container_no":{"type":"string"},"terminal_code":{"type":"string","enum":["PNC3","BNCT","HJNC","GWCT"]},"move_type":{"type":"string","enum":["반출","반입","선적"]},"vessel_call":{"type":["string","null"]},"verdict":{"type":"string","enum":["잡은 시간대로 나감","양하 전 도착","선적 마감 넘김","게이트 닫힌 시간","보관료 구간 진입"]},"failed_rule":{"type":["string","null"],"enum":["양하 완료 시각","선적 마감 시각","게이트 운영 시간","최소 접수 시간","무료장치 종료",null],"description":"그 예약이 실제로 걸린 기준의 이름입니다. 「게이트 닫힌 시간」 갈래는 게이트 운영 시간과 최소 접수 시간 두 기준을 함께 담아, 어느 쪽에 걸렸는지를 이 칸이 가릅니다"},"minutes_early":{"type":["integer","null"],"minimum":0,"description":"vessel_schedule.discharge_done_at 에서 gate_booking.slot_start 를 뺀 분입니다. 양하가 끝난 뒤 도착하면 비웁니다"},"minutes_over":{"type":["integer","null"],"minimum":0,"description":"마감 시각이나 게이트 운영 시간을 넘긴 분입니다. 넘기지 않았으면 비웁니다"},"lead_minutes":{"type":["integer","null"],"minimum":0,"description":"gate_booking.booked_at 부터 slot_start 까지의 분입니다"},"days_over_free":{"type":["integer","null"],"minimum":0,"description":"free_time_until 을 지난 날수를 하루 단위로 올림한 값입니다. 아직 남았으면 비웁니다"},"dispatch_note":{"type":["string","null"],"description":"배차 담당자가 기사에게 그대로 전할 메모입니다"},"evidence":{"type":"array","items":{"type":"object","properties":{"dataset":{"type":"string"},"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{"type":["string","number","null"]}},"required":["dataset","field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["container_no","terminal_code","move_type","verdict","evidence","summary"]}[
{
"container_no": "SEGU9084712",
"terminal_code": "PNC3",
"move_type": "반출",
"vessel_call": "VC-2609E",
"verdict": "잡은 시간대로 나감",
"failed_rule": null,
"minutes_early": null,
"minutes_over": null,
"lead_minutes": 450,
"days_over_free": null,
"dispatch_note": null,
"evidence": [
{
"dataset": "gate_booking",
"field": "slot_start",
"value": "2026-09-08T14:00:00"
},
{
"dataset": "vessel_schedule",
"field": "discharge_done_at",
"value": "2026-09-08T12:40:00"
},
{
"dataset": "terminal_rule",
"field": "gate_open_at",
"value": "06:00"
},
{
"dataset": "terminal_rule",
"field": "gate_close_at",
"value": "22:00"
},
{
"dataset": "terminal_rule",
"field": "min_lead_minutes",
"value": 90
},
{
"dataset": "gate_booking",
"field": "free_time_until",
"value": "2026-09-13T00:00:00"
}
],
"summary": "양하가 12시 40분에 끝난 뒤인 14시 슬롯이고 게이트 운영 시간 안입니다. 접수까지 450분이 남아 최소 90분도 넘겼고 무료장치도 닷새 남아 그대로 보냅니다.",
"hedge": null
},
{
"container_no": "CMAU3391144",
"terminal_code": "BNCT",
"move_type": "반입",
"vessel_call": null,
"verdict": "잡은 시간대로 나감",
"failed_rule": null,
"minutes_early": null,
"minutes_over": null,
"lead_minutes": 180,
"days_over_free": null,
"dispatch_note": null,
"evidence": [
{
"dataset": "gate_booking",
"field": "vessel_call",
"value": null
},
{
"dataset": "gate_booking",
"field": "slot_start",
"value": "2026-09-08T09:00:00"
},
{
"dataset": "terminal_rule",
"field": "gate_open_at",
"value": "07:00"
},
{
"dataset": "terminal_rule",
"field": "min_lead_minutes",
"value": 60
}
],
"summary": "배를 지정하지 않은 반입이라 선박 일정 판정을 건너뜁니다. 9시 슬롯이 게이트 운영 시간 안이고 접수까지 180분이 남아 그대로 보냅니다.",
"hedge": null
},
{
"container_no": "KOCU4417820",
"terminal_code": "PNC3",
"move_type": "반출",
"vessel_call": "VC-2609E",
"verdict": "양하 전 도착",
"failed_rule": "양하 완료 시각",
"minutes_early": 140,
"minutes_over": null,
"lead_minutes": 220,
"days_over_free": null,
"dispatch_note": "머스크 산 양하가 12시 40분에 끝납니다. 13시 이후 슬롯으로 옮겨 보냅니다.",
"evidence": [
{
"dataset": "gate_booking",
"field": "slot_start",
"value": "2026-09-08T10:20:00"
},
{
"dataset": "vessel_schedule",
"field": "discharge_done_at",
"value": "2026-09-08T12:40:00"
},
{
"dataset": "vessel_schedule",
"field": "vessel_name",
"value": "머스크 산"
},
{
"dataset": "gate_booking",
"field": "booked_at",
"value": "2026-09-08T06:40:00"
}
],
"summary": "컨테이너가 배에서 내려오기 140분 전에 트럭이 도착하는 슬롯입니다. 기사는 기다렸다 돌아가고 컨테이너는 다음 차수로 밀립니다.",
"hedge": null
},
{
"container_no": "MSCU5561208",
"terminal_code": "BNCT",
"move_type": "반출",
"vessel_call": "VC-2615E",
"verdict": "양하 전 도착",
"failed_rule": "양하 완료 시각",
"minutes_early": null,
"minutes_over": null,
"lead_minutes": 340,
"days_over_free": null,
"dispatch_note": "ONE 이글 양하 완료 기록이 아직 없습니다. 터미널에 양하 진행을 확인하고 슬롯을 다시 잡습니다.",
"evidence": [
{
"dataset": "vessel_schedule",
"field": "discharge_done_at",
"value": null
},
{
"dataset": "vessel_schedule",
"field": "berth_eta",
"value": "2026-09-08T06:00:00"
},
{
"dataset": "gate_booking",
"field": "slot_start",
"value": "2026-09-08T13:00:00"
},
{
"dataset": "vessel_schedule",
"field": "vessel_name",
"value": "ONE 이글"
}
],
"summary": "양하 완료 시각이 아직 안 들어와 컨테이너가 내려왔다고 볼 근거가 없습니다. 이른 정도를 잴 수 없어 분은 비우고 슬롯만 다시 잡게 올려 둡니다.",
"hedge": "양하가 예정대로 끝났는데 기록만 늦게 들어온 경우일 수 있어 터미널 확인이 필요합니다."
},
{
"container_no": "TGHU8802115",
"terminal_code": "PNC3",
"move_type": "선적",
"vessel_call": "VC-2609E",
"verdict": "선적 마감 넘김",
"failed_rule": "선적 마감 시각",
"minutes_early": null,
"minutes_over": 90,
"lead_minutes": 560,
"days_over_free": null,
"dispatch_note": "머스크 산 선적 마감이 16시입니다. 오전 슬롯으로 당기지 못하면 다음 배로 넘깁니다.",
"evidence": [
{
"dataset": "gate_booking",
"field": "slot_end",
"value": "2026-09-08T17:30:00"
},
{
"dataset": "vessel_schedule",
"field": "cargo_cutoff_at",
"value": "2026-09-08T16:00:00"
},
{
"dataset": "gate_booking",
"field": "move_type",
"value": "선적"
},
{
"dataset": "terminal_rule",
"field": "gate_close_at",
"value": "20:00"
}
],
"summary": "슬롯이 끝나는 17시 30분이 선적 마감 16시를 90분 넘깁니다. 게이트는 열려 있어 들어가더라도 그 배에는 못 싣습니다.",
"hedge": null
},
{
"container_no": "BSIU2213904",
"terminal_code": "PNC3",
"move_type": "반출",
"vessel_call": "VC-2611W",
"verdict": "게이트 닫힌 시간",
"failed_rule": "게이트 운영 시간",
"minutes_early": null,
"minutes_over": 50,
"lead_minutes": 670,
"days_over_free": null,
"dispatch_note": "게이트가 6시에 열립니다. 6시 이후 슬롯으로 옮겨 보냅니다.",
"evidence": [
{
"dataset": "gate_booking",
"field": "slot_start",
"value": "2026-09-08T05:10:00"
},
{
"dataset": "terminal_rule",
"field": "gate_open_at",
"value": "06:00"
},
{
"dataset": "terminal_rule",
"field": "gate_close_at",
"value": "22:00"
},
{
"dataset": "vessel_schedule",
"field": "discharge_done_at",
"value": "2026-09-07T23:40:00"
}
],
"summary": "양하는 어젯밤에 끝났지만 슬롯이 게이트 여는 6시보다 50분 이릅니다. 기사가 문 앞에서 기다리게 되는 배차입니다.",
"hedge": null
},
{
"container_no": "TEMU4420931",
"terminal_code": "HJNC",
"move_type": "반출",
"vessel_call": "VC-2620W",
"verdict": "게이트 닫힌 시간",
"failed_rule": "최소 접수 시간",
"minutes_early": null,
"minutes_over": null,
"lead_minutes": 50,
"days_over_free": null,
"dispatch_note": "접수 마감까지 90분이 필요합니다. 새벽 4시 이후 슬롯으로 다시 잡습니다.",
"evidence": [
{
"dataset": "gate_booking",
"field": "booked_at",
"value": "2026-09-08T01:40:00"
},
{
"dataset": "gate_booking",
"field": "slot_start",
"value": "2026-09-08T02:30:00"
},
{
"dataset": "terminal_rule",
"field": "min_lead_minutes",
"value": 90
},
{
"dataset": "terminal_rule",
"field": "gate_open_at",
"value": "00:00"
},
{
"dataset": "terminal_rule",
"field": "gate_close_at",
"value": "24:00"
}
],
"summary": "HJNC 반출 게이트는 00시부터 24시까지 열려 있어 문 자체는 걸리지 않고, 걸린 기준은 최소 접수 시간입니다. 예약을 잡은 01시 40분부터 슬롯까지 50분뿐이라 90분에 못 미쳐 접수가 안 잡힌 채 트럭만 나갑니다.",
"hedge": null
},
{
"container_no": "HJCU7719033",
"terminal_code": "PNC3",
"move_type": "반출",
"vessel_call": "VC-2622E",
"verdict": "보관료 구간 진입",
"failed_rule": "무료장치 종료",
"minutes_early": null,
"minutes_over": null,
"lead_minutes": 190,
"days_over_free": 1,
"dispatch_note": "무료장치가 어제 자정에 끝났습니다. 오늘 안에 빼면 하루치 보관료로 끝납니다.",
"evidence": [
{
"dataset": "gate_booking",
"field": "free_time_until",
"value": "2026-09-08T00:00:00"
},
{
"dataset": "gate_booking",
"field": "slot_start",
"value": "2026-09-08T10:00:00"
},
{
"dataset": "vessel_schedule",
"field": "berth_etd",
"value": "2026-09-02T22:00:00"
},
{
"dataset": "terminal_rule",
"field": "free_days",
"value": 5
}
],
"summary": "무료장치가 끝난 자정을 지나 10시 슬롯을 잡아 하루치 보관료 구간에 들어갑니다. 접안이 끝난 9월 2일부터 닷새가 이미 지났습니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. verdict 가 enum 다섯 값(잡은 시간대로 나감·양하 전 도착·선적 마감 넘김·게이트 닫힌 시간·보관료 구간 진입) 중 하나인지 확인합니다. 판정은 시각 비교 규칙이 정하므로 모델이 다른 값을 쓰면 그 응답을 버립니다. 2. container_no·terminal_code·move_type·verdict·evidence·summary 여섯 필드가 모두 있는지 확인합니다. 3. minutes_early·minutes_over·lead_minutes·days_over_free 가 0 이상인지 범위를 확인합니다. 4. minutes_early 를 vessel_schedule.discharge_done_at 에서 gate_booking.slot_start 를 뺀 분으로, lead_minutes 를 booked_at 부터 slot_start 까지의 분으로 다시 계산해 값이 같은지 확인합니다. 5. minutes_over 를 선적이면 slot_end 와 cargo_cutoff_at 의 차로, 게이트면 slot_start 와 gate_open_at 또는 slot_end 와 gate_close_at 의 차로 다시 계산해 값이 같은지 확인합니다. days_over_free 는 free_time_until 부터 slot_start 까지를 하루 단위로 올림해 다시 계산합니다. 6. evidence 의 field 가 gate_booking·vessel_schedule·terminal_rule 에 실재하는 컬럼 이름인지 대조합니다. 없는 이름을 인용하면 메모가 지어낸 문장이라는 뜻이라 규칙이 만든 기본 메모로 되돌립니다. 7. agg 가 없는 evidence 값이 그 컬럼에 실제로 수록된 값과 같은지 대조합니다. 여러 예약에서 센 수치에는 agg 를 함께 적습니다. 8. summary 와 dispatch_note 가 존댓말로 끝나는 한국어 문장인지 확인합니다. 9. gate_booking.vessel_call 이 비어 있으면 선박 일정 판정을 건너뛰고 게이트 운영 시간과 접수 시간만 봅니다. CMAU3391144 가 조건이 성립한 경우이고, 같은 터미널의 MSCU5561208 은 배가 지정돼 있어 성립하지 않은 경우입니다. 10. vessel_schedule.discharge_done_at 이 비어 있으면 그 시점에 양하 완료 기록이 없다는 뜻이라 반출 예약을 양하 전 도착으로 봅니다. 이때 minutes_early 는 잴 수 없으므로 비웁니다. VC-2615E 가 조건이 성립한 경우이고, VC-2609E 는 12시 40분 기록이 있어 성립하지 않은 경우입니다. 11. gate_booking.free_time_until 이 비어 있으면 days_over_free 를 비우고 보관료 판정을 건너뜁니다. 선적 예약인 TGHU8802115 가 조건이 성립한 경우이고, HJCU7719033 은 무료장치 종료 시각이 적혀 있어 성립하지 않은 경우입니다. 12. failed_rule 을 갈래마다 다시 붙여 봅니다. 양하 전 도착은 「양하 완료 시각」, 선적 마감 넘김은 「선적 마감 시각」, 보관료 구간 진입은 「무료장치 종료」이고, 게이트 닫힌 시간은 슬롯이 게이트 여닫는 시각을 벗어났으면 「게이트 운영 시간」, 벗어나지 않았는데 lead_minutes 가 min_lead_minutes 에 못 미치면 「최소 접수 시간」입니다. 둘 다 걸리면 게이트 운영 시간을 먼저 적습니다. 접수 시간에 걸린 예약은 minutes_over 를 비우고 lead_minutes 와 기준표의 min_lead_minutes 를 나란히 남깁니다. 잡은 시간대로 나감은 failed_rule 을 비웁니다. 화면은 이 값을 판정 이름 옆에 함께 보여줍니다 — 게이트가 온종일 열려 있는 터미널에서 접수 시간에 걸린 예약이 「게이트 시간 밖」으로만 읽히면 담당자가 엉뚱한 곳을 고칩니다. 13. 판정 순서는 양하 전 도착, 선적 마감 넘김, 게이트 닫힌 시간, 보관료 구간 진입, 잡은 시간대로 나감 순으로 앞에서 걸리면 뒤를 보지 않습니다. 트럭이 헛걸음하는 쪽을 앞에 둔 순서입니다. 14. 양하 전 도착은 move_type 이 반출인 줄만 봅니다. 선적은 배에 싣는 방향이라 양하 완료와 상관이 없습니다. 15. 게이트 운영 시간은 경계에 정확히 닿은 슬롯을 걸지 않습니다. 슬롯이 끝나는 시각이 문 닫는 시각과 같은 OOLU7712233 이 그 경계 사례이고, 30분을 넘긴 GESU4413099 는 걸립니다. min_lead_minutes 가 0 인 터미널은 접수 시간 조건을 두지 않습니다. 미리 접수를 받지 않는 PNC3 반입이 그 경우입니다. 16. terminal_rule 에 그 (terminal_code, move_type) 조합이 없으면 판정을 세우지 않습니다. 게이트 시간도 접수 시간도 없는 채로 통과시키면 기준이 없어서 통과한 예약과 기준을 지켜서 통과한 예약이 한 묶음이 되므로, verdict 를 비우는 대신 그 예약은 목록에 남기고 hedge 에 기준표에 없는 조합이라고 적어 담당자가 기준표부터 채우게 합니다. 샘플 열두 조합에는 빠진 행이 없어 이 경우가 나오지 않지만, 터미널이 새 갈래를 열면 바로 생깁니다. [평가 러너 · 2주차] 1. 배차 담당자가 손으로 정리한 60건으로 판정 일치율을 냅니다. 어긋난 건은 선박 일정에서 갈라졌는지 터미널 기준표에서 갈라졌는지 나눠 리포트에 답니다. [수동 검증 · 사람이 봅니다] 1. 양하 전 도착으로 뜬 예약을 담당자가 실제로 옮기는지 표본 20건을 따라가 봅니다. 대기 시간을 감수하고 그대로 보내는 관행이 있는지 함께 봅니다. 2. 터미널마다 다른 무료장치 기산 시점을 발주처와 현장 사용자에게 듣고 기준표를 고칠지 정합니다.