포트로직스
경력근거: 디지털 포워더로서 물류 시장의 네트워크에 IT 기술력을 더해
포트로직스는 이메일에 의존하던 수출입 업무와 비효율적인 정보 관리를 자사 솔루션 TOMS 로 푸는 디지털 포워더이고, 이 자리는 현장에 들어가 비표준 원천인 첨부 PDF 와 이메일 본문과 메신저 로그를 구조화된 데이터로 끌어오며 본인이 떠나도 굴러가도록 인계까지 마치는 역할입니다. 선하증권 번호가 메일에서는 붙여 쓰이고 서류에서는 하이픈으로 끊기며 스캔본에서는 숫자 0 과 영문 O 가 섞이는 모양은, 포워딩 현장의 실제 문서를 전제해야 나오는 문제입니다. 현장에 밀착해 직접 만드는 방식에서는 처음부터 여러 사람이 쓰는 시스템이 아니라 담당자 한 명이 터미널에서 바로 돌려 보는 CLI로 기능을 검증하는 편이 자연스러워, 이 포트폴리오도 그 형태로 끝까지 갑니다. 이 포트폴리오가 보여주는 것은 비표준 문서를 식별자 규칙으로 연결하는 기술과 정합성 검증과 문서 자동 생성 기술이며, 수출입 물류나 포워딩 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
포워딩 현장이 여전히 이메일과 전화와 수기 입력에 의존한다고 공고가 못박고 비표준 원천을 구조화된 데이터로 끌어오는 일을 주요 업무로 세워서, 메일 본문과 첨부 서류 텍스트를 선적 건 단위 구조로 옮기는 것을 제품의 골조로 둡니다.
다중 원천을 정합성 검증까지 포함해 통합하고 체크룰과 diff 를 자작하라고 공고가 적어서, 같은 선적을 가리키는 서로 다른 문서에서 금액과 일자가 어긋나는 지점을 규칙으로 잡아 인계 문서의 별도 절로 세웁니다.
본인이 떠나도 운영이 지속되도록 문서를 인계하는 것이 이 자리의 종료선이고 인계 흔적이 곧 산출물이라고 공고가 말해서, 이 도구의 최종 산출물 자체를 다음 담당자가 그대로 받아 읽는 인수인계 문서로 잡습니다.
포워딩 현장에 들어가 화주 담당자의 메일함에서 내려받은 이메일 본문과 첨부 서류 텍스트와 메신저 대화 내보내기를 폴더째 읽어, 같은 선적을 가리키는 조각을 선하증권 번호와 컨테이너 번호의 표기 규칙으로 한 건씩 묶는 CLI입니다. 터미널에서 handover build 명령에 폴더 경로를 주면 선적 건마다 어디까지 진행됐고 무엇이 미결인지가 원문 인용을 달고 절 단위로 정리된 인수인계 Markdown 파일이 생기고, 함께 나오는 CSV 는 사내 물류 시스템에 그대로 올릴 수 있는 형태입니다. 표준출력에는 묶인 선적 건 수와 어느 문서에도 붙지 못한 조각 수가 요약 표로 찍혀, 담당자가 붙지 못한 조각부터 손으로 확인하게 합니다.
휴가나 인사이동으로 담당 화주를 넘겨야 할 때, 진행 중인 선적 스무 건이 각각 어디까지 갔고 무엇이 미결인지가 오직 본인 메일함에만 있어서 후임이 스레드를 처음부터 거슬러 읽어야 했던 수출입 담당자입니다. 메일함에서 내려받은 파일 묶음을 폴더에 두고 명령 한 번을 쳐서, 선적 건별 진행 상황과 아직 회신하지 않은 요청이 원문 인용을 달고 정리된 인계 문서를 받고 싶은 사람입니다.
선하증권 번호와 컨테이너 번호와 인보이스 번호는 표기가 흔들려도 정규화 규칙으로 같은 값에 모이는 식별자라서, 흩어진 메일과 첨부를 이 식별자로 묶으면 선적 건별 진행 상황이 사람의 기억이 아니라 문서 근거로 서고, 담당자는 붙지 못한 조각과 값이 어긋난 지점만 확인하면 된다는 가정입니다.
이메일 본문과 첨부 서류 텍스트와 메신저 대화 더미가 담긴 폴더를 놓고 터미널에서 handover build 명령을 치면, 표기가 흔들리는 선하증권 번호와 컨테이너 번호를 정규화해 같은 선적끼리 묶고, 건별 진행 상황과 미결 요청이 원문 인용을 달고 실린 인수인계 Markdown 파일과 등록용 CSV 를 쓰며, 표준출력에 묶인 건수와 붙지 못한 조각 수를 찍는 CLI가 도는 완결품입니다. 만드는 덩어리는 식별자 정규화와 묶기 규칙 그리고 인계 문서 생성 둘뿐이고 서버와 화면과 배포가 없어 평일 5일에 혼자 끝나는 범위이며, 데이터는 실제 고객 자료 대신 작성자가 만든 더미를 씁니다.
1주차 결과를 입력으로, 한 선적 건에 묶인 문서들 사이에서 금액과 출항일과 도착일과 컨테이너 대수가 서로 어긋나는 지점을 규칙으로 찾아 인계 문서의 불일치 절로 세우고, 표준출력에는 어느 문서의 어느 값이 어떻게 다른지 diff 형태로 찍는 정합성 검증 계층입니다. 통화가 다른 금액과 연도가 빠진 날짜처럼 비교 자체가 성립하지 않는 짝은 판정하지 않고 확인 필요로 돌립니다.
메일 본문과 메신저 대화에서 아직 회신하지 않은 요청과 약속한 일정을 언어 모델로 뽑아 원문에 실재하는 문자열인지 대조한 뒤 인계 문서의 다음 담당자 할 일 절에 붙이고, 이 도구를 MCP 서버로 감싸 옆자리 담당자가 자기 에디터에서 선적 건 상태를 물어보게 만드는 확장입니다. 인용이 원문에 없으면 그 항목은 확인 필요 절로 돌려 지어낸 문장이 인계 문서에 실리지 않게 막습니다.
이메일 본문과 첨부 서류와 메신저 대화 더미 24건이 담긴 폴더에 handover build 명령을 치면, 선적 3건으로 묶인 인수인계 Markdown 파일과 등록용 CSV 가 생기고 표준출력에 묶인 건수와 붙지 못한 조각 수가 찍히는 흐름을 시연합니다.
선하증권 번호가 하이픈으로 끊긴 서류와 스캔 글자가 깨진 첨부와 식별자가 아예 없는 메신저 대화와 제목만 같고 실제로는 다른 선적인 메일을 섞은 입력 30종을 넣어도 도구가 죽지 않고 미연결 목록으로 분리하는 것을 보입니다. 다만 데이터와 정답을 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
같은 폴더에서는 언제나 같은 인계 문서가 나오는 결정론을 테스트로 보이고, 붙지 못한 조각이 하나라도 있으면 종료 코드로 알려 다른 스크립트가 이어받게 합니다.
2주차에는 작성자가 손으로 매긴 문서와 선적의 정답표를 기준으로 묶기의 정밀도와 재현율을 재고, 잘못 붙인 건은 어느 규칙이 과하게 잡았는지 기록합니다. 이 수치 역시 작성자가 만든 정답표 기준입니다.
위 수치는 더미데이터 자가채점이며, 실제 묶기 품질은 포트로직스 현장의 실제 메일함과 첨부 서류와 담당자의 검토가 있어야 측정 가능합니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모가 도는 상태와 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 하이픈으로 끊긴 번호와 스캔 글자가 깨진 첨부와 식별자 없는 대화와 제목만 같은 다른 선적을 더미에 일부러 섞어 두었습니다.
서로 다른 선적을 한 건으로 합치는 오류가 가장 위험합니다. 잘못 합쳐진 인계 문서는 다음 담당자를 조용히 오도하기 때문에, 근거가 약한 연결은 붙이지 않고 미연결 목록에 남겨 사람이 확인하게 하는 쪽으로 규칙을 기울였습니다. 붙지 못한 조각이 늘어나는 것은 감수하는 비용입니다.
이 포트폴리오가 보여주는 것은 비표준 문서를 식별자 규칙으로 연결하는 기술과 정합성 검증과 문서 자동 생성 기술이며, 수출입 통관 절차나 운임 정산 실무 같은 물류 포워딩 도메인 깊이는 주장하지 않습니다. 모르는 부분은 면접에서 모른다고 말합니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
화주 담당자 메일함에서 내려받은 이메일 본문과 첨부 서류 텍스트와 메신저 대화 내보내기 더미입니다. CLI가 읽어 같은 선적끼리 묶는 입력 파일이 됩니다.
| doc_id | source | thread_subject | received_at | sender | text | ref_raw | attach_of |
|---|---|---|---|---|---|---|---|
| D-001 | email_body | [부킹확정] 부산-롱비치 7/10 출항 건 | 2026-07-03T10:12:00 | hj.kim@sunrisefwd.co.kr | 안녕하세요. 7월 10일 부산 출항 건 부킹 확정되었습니다. B/L … | COSU6301234560 | — |
| D-002 | pdf_attachment | [부킹확정] 부산-롱비치 7/10 출항 건 | 2026-07-03T10:12:00 | hj.kim@sunrisefwd.co.kr | BILL OF LADING B/L NO : COSU-630-12345… | COSU-630-1234560 | D-001 |
| D-003 | email_body | RE: [부킹확정] 부산-롱비치 7/10 출항 건 | 2026-07-04T09:41:00 | trade@daejin.co.kr | 네 확인했습니다. 그런데 인보이스 금액이 USD 12,450 로 되어… | COSU6301234560 | — |
| D-004 | pdf_attachment | [부킹확정] 부산-롱비치 7/10 출항 건 | 2026-07-03T10:12:00 | hj.kim@sunrisefwd.co.kr | COMMERCIAL INVOICE INVOICE NO : DJ-260… | COSU6301234560 | D-001 |
| D-005 | messenger | 카카오톡 대화 내보내기 - 대진트레이딩 김과장 | 2026-07-11T14:05:00 | 대진트레이딩 김과장 | [김과장] 롱비치 건 도착 예정일 좀 알려주세요 [담당자] ETA 7… | — | — |
아래 JSON Schema에 맞는 문서 행을 25건 더 만들어 주세요. {"type":"object","properties":{"doc_id":{"type":"string"},"source":{"type":"string","enum":["email_body","pdf_attachment","messenger"]},"thread_subject":{"type":"string"},"received_at":{"type":"string"},"sender":{"type":"string"},"text":{"type":"string"},"ref_raw":{"type":["string","null"]},"attach_of":{"type":["string","null"]}},"required":["doc_id","source","thread_subject","received_at","sender","text"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 같은 선하증권 번호가 문서마다 붙여쓰기와 하이픈과 공백으로 다르게 적힌 행, 둘째 스캔 글자가 깨져 숫자 0 과 영문 O 와 숫자 1 과 영문 l 이 뒤바뀐 행, 셋째 text 가 빈 문자열이라 추출이 실패한 첨부 행, 넷째 ref_raw 가 null 이고 본문에도 식별자가 없어 무엇을 가리키는지 특정되지 않는 메신저 행, 다섯째 thread_subject 는 같은데 실제로는 다른 선적을 말하는 행, 여섯째 회신 인용문으로 이전 메일 본문이 통째로 다시 들어와 중복되는 행, 일곱째 선하증권 번호 대신 인보이스 번호로만 선적을 가리키는 행. 형식을 깨뜨리는 시도는 3건까지 별도로 만들어 주세요.{"type":"object","properties":{"doc_id":{"type":"string"},"source":{"type":"string","enum":["email_body","pdf_attachment","messenger"]},"thread_subject":{"type":"string"},"received_at":{"type":"string"},"sender":{"type":"string"},"text":{"type":"string"},"ref_raw":{"type":["string","null"]},"attach_of":{"type":["string","null"]}},"required":["doc_id","source","thread_subject","received_at","sender","text"]}각 문서에서 뽑아낸 항목 값과 그 근거가 된 원문 인용입니다. CLI가 정규화한 값으로 선적을 묶고, 한 건에 묶인 뒤에는 값이 어긋나는 지점을 찾는 대조 입력이 됩니다.
| doc_id | field | value_raw | value_norm | span |
|---|---|---|---|---|
| D-001 | bl_no | COSU6301234560 | COSU6301234560 | B/L No. COSU6301234560 |
| D-002 | bl_no | COSU-630-1234560 | COSU6301234560 | B/L NO : COSU-630-1234560 |
| D-001 | container_no | MSCU 123456-7 | MSCU1234567 | 컨테이너는 MSCU 123456-7 한 대입니다 |
| D-002 | etd | 10-JUL-2026 | 2026-07-10 | ETD : 10-JUL-2026 |
| D-001 | etd | 7월 10일 | — | 7월 10일 부산 출항 건 |
아래 JSON Schema에 맞는 추출 항목 행을 30건 더 만들어 주세요. {"type":"object","properties":{"doc_id":{"type":"string"},"field":{"type":"string","enum":["bl_no","container_no","invoice_no","amount","etd","eta","pol","pod","shipper"]},"value_raw":{"type":"string"},"value_norm":{"type":["string","null"]},"span":{"type":["string","null"]}},"required":["doc_id","field","value_raw"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 같은 선적의 같은 항목인데 문서마다 값이 다른 행, 둘째 연도가 빠져 있거나 형식이 제각각이라 value_norm 이 null 인 날짜 행, 셋째 통화가 서로 다른 금액 행, 넷째 스캔 글자가 깨져 정규화가 실패한 식별자 행, 다섯째 span 이 null 이라 근거가 없는 행, 여섯째 span 문자열이 원문에 실재하지 않는 지어낸 행, 일곱째 shipper 이름이 약칭과 정식 상호로 갈리는 행. 형식을 깨뜨리는 시도는 3건까지 별도로 만들어 주세요.{"type":"object","properties":{"doc_id":{"type":"string"},"field":{"type":"string","enum":["bl_no","container_no","invoice_no","amount","etd","eta","pol","pod","shipper"]},"value_raw":{"type":"string"},"value_norm":{"type":["string","null"]},"span":{"type":["string","null"]}},"required":["doc_id","field","value_raw"]}| LLM | 3주차에 메일 본문과 메신저 대화에서 미결 요청 문장을 뽑는 보조로만 씁니다. 선적 건 묶기와 값 대조는 규칙이 판정하고, 언어 모델의 출력은 원문 대조를 통과해야 문서에 실립니다. |
| Claude | 미결 요청 추출의 본 모델로 쓰고, 개발 중에는 Claude Code 로 식별자 정규화 규칙의 경계 사례 테스트를 먼저 써 두고 구현합니다. |
| LangChain | 3주차 추출 단계에서 긴 메일 스레드를 나누고 프롬프트와 출력 형식 강제를 한 체인으로 묶는 데 씁니다. |
| LlamaIndex | 3주차에 선적 건별로 첨부 서류 텍스트를 인덱싱해, 인계 문서의 인용이 어느 문서 몇 번째 줄에서 왔는지 되짚게 합니다. 1주차 범위에서는 문서 수가 적어 인덱스 없이 파일을 직접 읽습니다. |
| AWS | 새 인프라를 세우지 않습니다. 도구는 담당자 노트북 터미널에서 돌지만, 실제 도입 때 첨부 파일이 S3 에 쌓이는 경우를 대비해 입력을 읽는 부분을 로컬 폴더와 객체 스토리지 둘 다로 갈아끼울 수 있게 경계만 나눠 둡니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차에서 한 건에 묶인 문서 사이의 어긋난 값을 잡습니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 3주차에서 미결 요청을 인용해 뽑고 도구를 MCP 서버로 감쌉니다.
인수인계 문서를 이루는 항목 한 건입니다. 어느 규칙과 어느 원문 인용을 근거로 그 항목이 문서에 실렸는지 담아, 다음 담당자가 의심스러운 줄을 만나면 바로 원문까지 되짚게 합니다.
{"type":"object","properties":{"shipment_key":{"type":["string","null"]},"section":{"type":"string","enum":["identity","progress","mismatch","open_request","unlinked","needs_review"]},"basis":{"type":"string","enum":["rule_id_link","rule_field_diff","rule_thread_link","llm_extract"]},"headline":{"type":"string"},"evidence":{"type":"array","items":{"type":"object","properties":{"doc_id":{"type":"string"},"field":{"type":["string","null"]},"quote":{"type":"string"}},"required":["doc_id","quote"]}},"needs_human_review":{"type":"boolean"},"hedge":{"type":["string","null"]}},"required":["shipment_key","section","basis","headline","evidence","needs_human_review"]}[
{
"shipment_key": "COSU6301234560",
"section": "identity",
"basis": "rule_id_link",
"headline": "부산에서 롱비치로 가는 선적 한 건에 문서 다섯 개가 묶였습니다",
"evidence": [
{
"doc_id": "D-001",
"field": "text",
"quote": "B/L No. COSU6301234560"
},
{
"doc_id": "D-002",
"field": "text",
"quote": "B/L NO : COSU-630-1234560"
}
],
"needs_human_review": false,
"hedge": null
},
{
"shipment_key": "COSU6301234560",
"section": "mismatch",
"basis": "rule_field_diff",
"headline": "인보이스 금액과 화주가 말한 발주 금액이 90 달러 다릅니다",
"evidence": [
{
"doc_id": "D-004",
"field": "value_norm",
"quote": "TOTAL AMOUNT : USD 12,450.00"
},
{
"doc_id": "D-003",
"field": "value_norm",
"quote": "저희 발주서는 USD 12,540 입니다"
}
],
"needs_human_review": true,
"hedge": "어느 쪽이 맞는지는 발주서 원본을 봐야 정해집니다. 도구는 두 값이 다르다는 사실까지만 판정합니다."
},
{
"shipment_key": "COSU6301234560",
"section": "open_request",
"basis": "llm_extract",
"headline": "원산지증명서 사본을 금요일까지 보내 달라는 요청에 아직 회신하지 않았습니다",
"evidence": [
{
"doc_id": "D-009",
"field": null,
"quote": "이번 주 금요일까지 회신 부탁드립니다"
}
],
"needs_human_review": true,
"hedge": "메일 본문 문장 기준이라 이미 전화로 처리됐을 수 있어 다음 담당자가 확인해야 합니다."
},
{
"shipment_key": null,
"section": "unlinked",
"basis": "rule_thread_link",
"headline": "호치민 도착일을 묻는 대화가 어느 선적인지 규칙으로 정해지지 않았습니다",
"evidence": [
{
"doc_id": "D-012",
"field": null,
"quote": "[김과장] 호치민 건 ETA 언제인가요"
}
],
"needs_human_review": true,
"hedge": "대화에 선하증권 번호도 컨테이너 번호도 없어 근거가 약한 연결은 붙이지 않고 남겨 둡니다."
}
][자동 검증] 1. 항목 JSON 이 위 스키마를 통과하는지 확인합니다. section 이 정해진 여섯 값 밖이면 그 항목을 버립니다. 2. evidence 의 quote 가 해당 doc_id 원문에 실재하는 문자열인지 대조합니다. 실재하지 않으면 그 항목을 needs_review 절로 돌립니다. 지어낸 인용이 인계 문서에 실리면 다음 담당자가 원문을 찾다가 시간을 버립니다. 3. basis 가 rule_id_link 인 항목은 evidence 의 값들이 정규화 후 같은 값으로 모이는지 다시 계산해 확인합니다. 여기서 어긋나면 서로 다른 선적이 한 건으로 합쳐진 것이라 가장 먼저 막습니다. 4. basis 가 rule_field_diff 인 항목은 두 값의 단위와 통화가 같은지 확인하고, 다르면 판정하지 않고 needs_review 절로 돌립니다. 5. basis 가 llm_extract 인 항목은 needs_human_review 가 true 인지 확인합니다. 문장에서 뽑은 요청은 사람이 최종 확인해야 인계 문서에 남습니다. 6. shipment_key 가 null 이 아니면 어느 문서에서도 정규화되지 않은 값인지 확인합니다. 어디에도 없는 식별자는 지어낸 것으로 보고 버립니다. 7. 같은 폴더로 두 번 실행한 인계 문서가 문자 그대로 같은지 비교합니다. 결정론이 깨지면 규칙에 순서 의존 버그가 들어간 것입니다. [수동 검증] 1. 생성된 인계 문서 한 부를 후임 담당자 관점에서 읽고, 이것만 보고 업무를 이어받을 수 있는지 봅니다. 2. 미연결 목록에 남은 조각을 하나씩 열어, 사람이면 붙일 수 있었던 연결인지 확인하고 규칙을 다듬습니다. 3. 묶인 선적 건 중 하나를 골라 원문 메일을 처음부터 읽고, 문서에 빠진 중요한 사건이 없는지 대조합니다.