Vertical Bar
경력근거: D3.js, Plotly 등 데이터 시각화 라이브러리 사용 경험
※ 실제 산출물이 아닌 AI 예측 시안입니다.
Vertical Bar는 Oracle NetSuite를 쓰는 북미 엔터프라이즈를 상대로 프로세스 분석을 파는 회사이고, 공고는 고객 요구를 기술 솔루션으로 옮기는 일과 D3.js·Plotly 시각화를 함께 요구합니다. ERP 로그는 같은 활동이 모듈마다 다른 이름으로 찍히기 때문에 표기를 맞춘 뒤라야 경로가 이어지고, 그 순서가 서야 시각화가 의미를 갖습니다. 이 앞뒤 관계는 남의 ERP 로그를 받아 분석하는 회사에서만 문제가 됩니다. 공고 업무는 아키텍처 설계부터 조직 문화까지 걸쳐 있는데 1주 범위에서 고를 수 있는 것은 하나라 로그 대조 축을 골랐습니다. 이 포트폴리오는 원문 표기를 표준 이름으로 맞추고 순서를 규칙으로 대조해 근거를 보여주는 기술을 보여줍니다. NetSuite 모듈 구조나 회계 절차 자체의 전문성은 주장하지 않습니다. 그 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
북미 엔터프라이즈 고객과 소통해 비즈니스 요구사항을 기술적 솔루션으로 전환하는 일이 주요 업무입니다. 업무 흐름이 표준에서 벗어난 지점을 짚어 주는 화면이 그 전환의 결과물 하나입니다.
기술 역량에 D3.js와 Plotly 같은 데이터 시각화 라이브러리 사용 경험이 따로 적혀 있습니다. 실행 경로와 표준 경로를 겹쳐 그리는 일이 그 역량이 쓰이는 자리입니다.
초기 MVP부터 상용 SaaS까지 핵심 기능을 직접 개발하는 일이 업무로 적혀 있습니다. 이 화면은 한 사람이 1주에 끝내는 크기의 MVP로 잡았습니다.
ERP에서 내려받은 실행 로그와 표준 단계표를 대조해, 어느 건이 어느 단계에서 표준 흐름을 벗어났는지 사유별로 보여주는 관리자 페이지입니다. 판정은 케이스별 활동 순서를 표준 단계 번호와 견주는 대조로 정합니다.
고객사의 구매 승인 흐름을 들여다보는 프로세스 분석 담당자입니다. ERP에서 내려받은 로그를 엑셀에 붙여 케이스 번호로 정렬한 다음 활동 이름을 눈으로 훑어 어디서 절차가 어긋났는지 찾는데, 같은 활동이 모듈마다 다른 이름으로 찍혀 있어 세는 것부터 어긋납니다.
표준 단계에 번호가 매겨져 있으면, 어느 케이스가 어디서 벗어났는지는 사람이 로그를 훑지 않아도 순서 대조로 갈라집니다.
실행 로그와 표준 단계표를 올리면 케이스별로 활동을 순서대로 이어 붙여 건너뛴 단계와 뒤바뀐 순서를 사유별로 보여주는 관리자 페이지입니다. ERP 연동은 두지 않고 내려받은 파일만 읽습니다.
케이스들이 실제로 지나간 경로를 D3.js로 그리고 표준 경로를 위에 겹쳐, 어느 갈래로 얼마나 새는지 굵기로 보여주는 화면입니다.
활동 사전과 표준 단계표를 파일로 분리해 고객사마다 다른 절차를 같은 판정으로 돌리고, AWS에 올려 운영합니다.
담당자가 실행 로그 60행과 표준 단계표를 올려 벗어난 건이 사유별로 나뉜 화면을 보는 것을 시연합니다.
결측·중복·미등록 활동이 섞인 입력 30종을 올려도 화면이 깨지지 않고, 판정하지 못한 케이스는 사유가 붙어 남습니다.
시각이 비어 있는 행이 있는 케이스는 정상으로 세지 않고 시각 누락으로 따로 답니다. 순서를 재지 못한 것을 문제없음으로 만들지 않습니다.
판정과 손으로 만든 정답표를 맞춰 사유별로 어긋난 건을 셉니다. 단, 로그와 정답을 모두 작성자가 만든 합성 기준이라 성능 측정이 아니라 데모 조건 동작입니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 더미데이터 자가채점이며, 실제 판정 정확도는 Vertical Bar 고객사의 실제 ERP 로그와 실제 표준 절차가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
활동 이름이 모듈마다 다른 건과 시각이 빈 건, 표준 단계표에 없는 활동을 로그에 일부러 심었습니다. 정답을 만든 사람이 데이터도 만든 이상, 이런 건이 없으면 채점 결과는 아무것도 증명하지 못합니다.
표준에서 벗어났다고 해서 잘못이라고 단정할 수 없습니다. 긴급 건은 절차를 건너뛰는 것이 규정일 수 있으므로, 판정은 표준과 다르다는 사실까지만 읽어야 합니다.
실제 도입하면 고객사마다 표준 절차가 다릅니다. 표준 단계표를 고객과 합의해 채우는 일이 도입 단계의 실제 과제입니다.
ERP 모듈별 권한과 회계 마감 규칙은 이 도구의 범위 밖입니다. 로그에 찍힌 순서만 보므로, 왜 그렇게 흘렀는지의 해석은 담당자가 합니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
ERP에서 내려받은 구매 승인 실행 로그입니다. 활동 이름이 모듈마다 대소문자·띄어쓰기로 흔들리고, 사전에 없는 신규 활동과 같은 이벤트 번호가 두 번 들어온 건, 시각이 비어 있는 행을 일부러 섞었습니다.
| event_no | case_no | activity_raw | occurred_at | actor_role | source_module |
|---|---|---|---|---|---|
| EV-001 | PO-2026-001 | Create PR | 2026-08-03T09:10 | requester | Procurement |
| EV-002 | PO-2026-001 | Approve PR | 2026-08-03T11:20 | manager | Procurement |
| EV-003 | PO-2026-001 | Issue PO | 2026-08-04T09:00 | buyer | Procurement |
| EV-004 | PO-2026-001 | Receive Goods | 2026-08-09T14:00 | warehouse | Inventory |
| EV-005 | PO-2026-002 | create pr | 2026-08-04T10:00 | requester | Procurement |
아래 JSON Schema에 맞는 ERP 실행 로그 행을 더 만들어 주세요. {"type":"object","properties":{"event_no":{"type":"string"},"case_no":{"type":"string"},"activity_raw":{"type":"string"},"occurred_at":{"type":["string","null"]},"actor_role":{"type":"string"},"source_module":{"type":"string"}},"required":["event_no","case_no","activity_raw","occurred_at","actor_role","source_module"]} 한 case_no에 활동이 세 건 이상 이어지도록 만들어 주세요. 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 미매핑으로 activity_raw가 사전에 없어 activity_id가 null인 행, 둘째 표기흔들림으로 같은 활동을 Create PR과 create pr와 CREATE PR처럼 대소문자만 다르게 적은 행, 셋째 중복으로 같은 event_no가 두 행에 있는 재적재 건, 넷째 결측으로 occurred_at이 null이라 순서를 잴 수 없는 행. 표준대로 흐른 정상 케이스가 전체의 절반쯤 되게 해 주세요. 또한 형식 깨뜨리기 시도 3건을 별도 파일로 만들어 case_no를 빼거나 occurred_at에 숫자를 넣어 스키마 검증이 걸러내는지 확인하게 해 주세요.{"type":"object","properties":{"event_no":{"type":"string"},"case_no":{"type":"string"},"activity_raw":{"type":"string"},"occurred_at":{"type":["string","null"]},"actor_role":{"type":"string"},"source_module":{"type":"string"}},"required":["event_no","case_no","activity_raw","occurred_at","actor_role","source_module"]}고객사가 정한 구매 승인 표준 단계표입니다. 각 활동에 순서 번호가 매겨져 있고, 건너뛰어도 되는 선택 단계를 표시했습니다.
| process_code | step_no | activity_id | activity_name | is_optional |
|---|---|---|---|---|
| P2P | 1 | create_pr | 구매요청 생성 | false |
| P2P | 2 | approve_pr | 구매요청 승인 | false |
| P2P | 3 | issue_po | 발주서 발행 | false |
| P2P | 4 | receive_goods | 입고 확인 | false |
| P2P | 5 | match_invoice | 송장 대조 | true |
아래 JSON Schema에 맞는 표준 단계 행을 더 만들어 주세요. {"type":"object","properties":{"process_code":{"type":"string"},"step_no":{"type":"integer"},"activity_id":{"type":"string"},"activity_name":{"type":"string"},"is_optional":{"type":"boolean"}},"required":["process_code","step_no","activity_id","activity_name","is_optional"]} activity_name은 한글로 적어 주세요. 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라. 첫째 경계극단으로 단계가 하나뿐인 짧은 프로세스와 단계가 여덟인 긴 프로세스, 둘째 선택 단계가 연달아 두 개인 프로세스. 또한 형식 깨뜨리기 시도 3건을 별도 파일로 만들어 step_no에 문자열을 넣거나 activity_id를 빼서 스키마 검증이 걸러내는지 확인하게 해 주세요.{"type":"object","properties":{"process_code":{"type":"string"},"step_no":{"type":"integer"},"activity_id":{"type":"string"},"activity_name":{"type":"string"},"is_optional":{"type":"boolean"}},"required":["process_code","step_no","activity_id","activity_name","is_optional"]}| Python | 실행 로그를 케이스별로 묶고 표준 단계와 대조하는 판정 본체입니다. |
| TypeScript | 판정 결과의 형태를 화면 쪽에서도 타입으로 묶어 사유 값이 늘어날 때 빠뜨린 화면을 컴파일 단계에서 잡습니다. |
| React | 사유별로 나뉜 판정 결과와 근거를 담당자가 보는 관리자 화면입니다. |
| D3.js | 케이스들이 실제로 지나간 경로와 표준 경로를 겹쳐 그리는 2주차 시각화입니다. |
| Plotly | 단계별 소요 시간 분포를 상자 그림으로 보여주는 보조 화면입니다. |
| PostgreSQL | 활동 사전과 표준 단계표, 판정 이력을 저장합니다. |
| NoSQL | 원본 실행 로그를 손대지 않은 모양 그대로 쌓아 두는 자리입니다. 컬럼이 고객사마다 달라 스키마를 미리 못 박습니다. |
| AWS | 3주차부터 판정기와 화면을 AWS로 배포해 운영합니다. |
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 실제 경로와 표준 경로를 겹쳐 그립니다.
✓ pass → 시각화까지가 한 덩어리입니다. 더 키우려면 표준 단계표를 고객사별로 분리해 AWS에 올립니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · case_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
on_path표준대로step_skipped단계 건너뜀out_of_order순서 뒤바뀜timestamp_missing시각 누락activity_unmapped활동 미매핑duplicate_event이벤트 중복케이스 한 건이 표준 흐름을 따랐는지의 판정 결과입니다. 벗어났다면 어느 단계에서 어떻게 벗어났는지를 근거와 함께 담습니다.
{"type":"object","properties":{"case_no":{"type":"string"},"verdict":{"type":"string","enum":["on_path","step_skipped","out_of_order","timestamp_missing","activity_unmapped","duplicate_event"]},"observed_step_count":{"type":["integer","null"]},"required_step_count":{"type":["integer","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{},"quote":{"type":["string","null"]}},"required":["field","value"]}},"next_action":{"type":"string"}},"required":["case_no","verdict","observed_step_count","required_step_count","evidence","next_action"]}[
{
"case_no": "PO-2026-001",
"verdict": "on_path",
"observed_step_count": 4,
"required_step_count": 4,
"evidence": [
{
"field": "activity_raw",
"agg": "count",
"value": 4
},
{
"field": "step_no",
"value": 4
},
{
"field": "occurred_at",
"value": "2026-08-09T14:00"
}
],
"next_action": "표준대로 흘렀습니다. 기준선 케이스로 남깁니다."
},
{
"case_no": "PO-2026-006",
"verdict": "on_path",
"observed_step_count": 4,
"required_step_count": 4,
"evidence": [
{
"field": "activity_raw",
"agg": "count",
"value": 4
},
{
"field": "step_no",
"value": 4
},
{
"field": "occurred_at",
"value": "2026-08-14T15:00"
}
],
"next_action": "표준대로 흘렀습니다. 기준선 케이스로 남깁니다."
},
{
"case_no": "PO-2026-002",
"verdict": "step_skipped",
"observed_step_count": 3,
"required_step_count": 4,
"evidence": [
{
"field": "activity_raw",
"value": "Receive Goods"
},
{
"field": "activity_name",
"value": "구매요청 승인"
},
{
"field": "step_no",
"value": 2
}
],
"next_action": "구매요청 승인 없이 발주가 나갔습니다. 승인 없이 발주가 가능한 경로가 열려 있는지 확인합니다."
},
{
"case_no": "PO-2026-003",
"verdict": "out_of_order",
"observed_step_count": 4,
"required_step_count": 4,
"evidence": [
{
"field": "activity_raw",
"value": "Issue PO",
"quote": "Issue PO"
},
{
"field": "occurred_at",
"value": "2026-08-05T13:00"
},
{
"field": "step_no",
"value": 3
}
],
"next_action": "발주가 승인보다 먼저 찍혔습니다. 사후 승인이 관행인지 담당자에게 확인합니다."
},
{
"case_no": "PO-2026-005",
"verdict": "timestamp_missing",
"observed_step_count": 3,
"required_step_count": 4,
"evidence": [
{
"field": "event_no",
"value": "EV-016"
},
{
"field": "occurred_at",
"value": null
}
],
"next_action": "시각이 비어 있어 순서를 재지 못했습니다. 정상으로 세지 않고 로그를 다시 내려받습니다."
},
{
"case_no": "PO-2026-004",
"verdict": "activity_unmapped",
"observed_step_count": 2,
"required_step_count": 4,
"evidence": [
{
"field": "activity_raw",
"value": "Vendor Risk Review",
"quote": "Vendor Risk"
},
{
"field": "activity_raw",
"value": "Approve PR"
}
],
"next_action": "사전에 없는 활동입니다. 사전에 추가하기 전에는 단계 집계에서 빼 둡니다."
},
{
"case_no": "PO-2026-003",
"verdict": "duplicate_event",
"observed_step_count": 4,
"required_step_count": 4,
"evidence": [
{
"field": "event_no",
"agg": "count",
"value": 2
}
],
"next_action": "같은 이벤트 번호가 두 번 적재됐습니다. 앞 건으로만 판정하고 뒤 건을 로그에서 뺍니다."
}
][자동 검증 · 1주차] 1. verdict가 enum 여섯 값 안에 있는지 확인합니다. 2. case_no·verdict·observed_step_count·required_step_count·evidence·next_action이 모두 있는지 확인합니다. 3. step_no가 1 이상 8 이하인지 확인합니다. 4. occurred_at이 null인 행이 있으면 verdict가 timestamp_missing인지 확인합니다. 5. evidence의 field가 두 입력 표의 컬럼에 실재하는지 확인합니다. 6. evidence의 quote가 그 행 activity_raw의 일부인지 확인합니다. 7. next_action이 존댓말로 끝나는지 확인합니다. [판정 규칙] 1. 활동 이름은 대소문자와 띄어쓰기, 구분자를 없애 소문자로 모은 뒤 standard_process_steps의 activity_id와 맞춥니다. 맞는 것이 없으면 미매핑으로 남기고 그 행은 나머지 판정에서 뺍니다. 2. 같은 행 번호가 두 번 들어오면 앞 건만 남기고 뒤 건은 적재 단계에서 사유와 함께 뺍니다. [수동 검증] 1. 단계를 건너뛴 케이스가 긴급 처리 규정에 따른 것인지 담당자가 읽어 확인합니다. 2. PO-2026-002처럼 표기가 흔들렸지만 사전으로 맞춰진 활동은 미매핑 사유를 달지 않는 것이 맞는지 확인합니다. [평가 러너 · 2주차] 1. 손으로 만든 정답표와 맞춰 사유별로 어긋난 건을 세고 어느 사유에서 많이 틀리는지 나눕니다. 2. 로그와 정답을 작성자가 만든 합성 기준이라 이 수치는 성능 측정이 아니라 데모 조건 동작입니다.
근거: D3.js, Plotly 등 데이터 시각화 라이브러리 사용 경험