트릿지
경력근거: 바이어–셀러 네트워크의 상호작용을 자동화하는 Horizontal AI 기능
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고는 농식품 도메인 데이터로 수직 AI 를 만들고 바이어와 셀러의 상호작용을 자동화하라고 적으며, 무엇을 어떻게 풀지 직접 정의하는 자리라고 밝힙니다. 이 포트폴리오는 그 상호작용의 첫 단계를 골랐습니다. 등급과 인증과 포장 단위를 데이터로 두고 비교를 규칙으로 고정해, 자동화가 붙기 전에 사람이 확인할 자리를 만듭니다. 다만 이 포트폴리오는 규격 비교 규칙과 화면 구현을 보여줍니다. 농산물 검역이나 무역 계약 실무에 대한 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
농식품 도메인 데이터를 기반으로 한 수직 AI 시스템을 설계하고 구축한다고 주요업무 첫 줄에 적습니다. 일반 모델을 붙이는 일이 아니라 이 산업의 데이터 모양을 아는 사람이 필요한 자리입니다.
바이어와 셀러 네트워크의 상호작용을 자동화하는 기능을 기획 단계부터 함께 정의하고 프로덕션 수준으로 구현한다고 적습니다. 양쪽 데이터를 맞추는 일이 제품의 중심입니다.
복잡한 도메인 문제를 엔지니어링 관점에서 구조화하고 무엇을 어떻게 풀지 직접 정의하며 실행한다고 적습니다. 주어진 명세를 구현하는 자리가 아닙니다.
무역 담당자가 요청 건을 열면, 바이어가 적은 요구 규격과 셀러들이 보낸 오퍼를 항목별로 맞춰 어긋난 자리를 보여줍니다. 어긋났다는 판정은 규격 비교 규칙이 정하고, 모델은 어느 항목이 왜 어긋났는지 담당자가 읽을 한 줄로 요약합니다.
저는 농산물 소싱을 맡고 있습니다. 바이어가 원하는 등급과 인증과 포장 단위를 적어 보내면 셀러 오퍼를 모아 비교하는데, 오퍼마다 단위와 표기가 달라 엑셀로 정리하다 하루가 갑니다. 급하게 고르다 인증이 없는 셀러를 넘긴 적도 있습니다.
오퍼 비교가 오래 걸리는 이유는 셀러가 많아서가 아니라 단위와 표기가 제각각이라 같은 자리끼리 놓이지 않기 때문입니다. 단위를 통일해 항목별로 맞추면, 어긋난 자리만 남아 연락할 셀러가 좁혀집니다.
무역 담당자가 쓰는 화면 하나가 완결됩니다. 요구 규격과 오퍼를 항목별로 맞춰 단위를 통일하고, 어긋난 자리를 사유와 같이 보여줍니다. 셀러 시스템 연동은 오퍼 파일을 올리는 것으로 대신하고 3주차로 미룹니다.
담당자가 실제로 고른 오퍼 130건과 규칙이 적합으로 본 결과를 맞춰 보고, 규칙이 떨어뜨렸는데 사람이 고른 항목을 유형별로 냅니다.
오퍼가 들어오는 대로 비교가 돌고, 요구를 모두 만족하는 오퍼가 나오면 담당자에게 알립니다. 파일을 올리지 않아도 목록이 최신으로 유지됩니다.
무역 담당자가 요청을 열면 단위가 통일된 오퍼 비교표가 나오고, 어긋난 항목이 요구 값과 같이 보입니다.
인증 표기가 비거나 수량 단위가 다르거나 등록되지 않은 품목 코드가 섞인 오퍼 30종을 올려도 화면이 죽지 않고, 비교할 수 없는 항목은 미판정으로 남습니다.
담당자 선택 130건으로 규칙 판정의 일치율을 냅니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 소싱 적합도는 트릿지의 실 요청과 오퍼 기록이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 인증 결측·단위 불일치·등록되지 않은 품목 코드를 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
규격이 맞았다는 사실이 곧 좋은 오퍼라는 뜻은 아닙니다. 납기와 신뢰도는 다른 축이므로 판정을 결론으로 읽지 않게 요구 값과 오퍼 값을 항상 같이 보여줍니다.
실제 도입에서는 나라마다 등급 체계와 인증 이름이 달라 비교표가 하나로 서지 않습니다. 품목이 늘어나면 규칙을 코드에서 빼 설정으로 옮겨야 하며, 그것은 도입 단계 과제로 분리합니다.
계약 체결과 대금 지급은 계약 권한이 있어야 다룰 수 있어 이 포트폴리오의 범위 밖입니다. 여기서는 오퍼를 비교하는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
바이어가 보낸 요구 규격입니다. 비교의 기준이며 인증 미지정과 수량 단위 흔들림을 의도적으로 포함합니다.
| request_no | item_code_raw | item_name | grade_min | cert_required | quantity | quantity_unit | need_by | destination |
|---|---|---|---|---|---|---|---|---|
| RQ-2001 | AVO-HASS | 아보카도 하스 | A | GlobalGAP | 18 | ton | 2026-09-20 | 부산항 |
| RQ-2002 | BAN-CAV | 바나나 카벤디시 | B | — | 40 | ton | 2026-09-05 | 인천항 |
| RQ-2003 | MAN-KEIT | 망고 키트 | A | 유기농 | — | box | 2026-10-01 | 부산항 |
| RQ-2004 | GAR-PEEL | 깐마늘 | — | HACCP | 5,000 | kg | 2026-09-15 | 평택항 |
| RQ-2005 | ONI-YEL | 양파 | C | — | 25 | ton | 2026-09-28 | 인천항 |
아래 JSON Schema 에 맞는 바이어 요구 행을 더 만들어 주세요. {"type":"object","properties":{"request_no":{"type":"string"},"item_code_raw":{"type":"string"},"item_name":{"type":"string"},"grade_min":{"type":["string","null"],"enum":["A","B","C",null]},"cert_required":{"type":["string","null"]},"quantity":{"type":["number","null"]},"quantity_unit":{"type":"string","enum":["kg","ton","box"]},"need_by":{"type":"string"},"destination":{"type":"string"}},"required":["request_no","item_code_raw","item_name","quantity_unit","need_by","destination"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) quantity 나 grade_min 이 null 인 행, (2) quantity 가 0 인 행과 수천 킬로그램 단위 행, (3) item_code_raw 의 구분자와 대소문자가 흔들린 행, (4) 오퍼가 하나도 없는 품목. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"request_no":{"type":"string"},"item_code_raw":{"type":"string"},"item_name":{"type":"string"},"grade_min":{"type":["string","null"],"enum":["A","B","C",null]},"cert_required":{"type":["string","null"]},"quantity":{"type":["number","null"]},"quantity_unit":{"type":"string","enum":["kg","ton","box"]},"need_by":{"type":"string"},"destination":{"type":"string"}},"required":["request_no","item_code_raw","item_name","quantity_unit","need_by","destination"]}셀러들이 보낸 오퍼입니다. 비교의 대상이며 단위가 다른 수량과 인증 미보유, 요청에 없는 품목 코드를 포함합니다.
| offer_no | request_no | seller_code | item_code_raw | grade | cert_held | quantity | quantity_unit | price_usd | ship_by |
|---|---|---|---|---|---|---|---|---|---|
| OF-3001 | RQ-2001 | SL-11 | AVO-HASS | A | GlobalGAP | 20 | ton | 2.4 | 2026-09-12 |
| OF-3002 | RQ-2001 | SL-12 | AVO-HASS | B | GlobalGAP | 18,000 | kg | 2.1 | 2026-09-18 |
| OF-3003 | RQ-2001 | SL-13 | AVO-HASS | A | — | 22 | ton | 2.2 | 2026-09-10 |
| OF-3004 | RQ-2002 | SL-14 | BAN-CAV | B | HACCP | 40 | ton | 1.1 | 2026-08-30 |
| OF-3005 | RQ-2004 | SL-15 | GAR-PEEL | A | HACCP | 4,800 | kg | 3.4 | 2026-09-11 |
아래 JSON Schema 에 맞는 셀러 오퍼 행을 더 만들어 주세요. {"type":"object","properties":{"offer_no":{"type":"string"},"request_no":{"type":["string","null"]},"seller_code":{"type":"string"},"item_code_raw":{"type":"string"},"grade":{"type":["string","null"],"enum":["A","B","C",null]},"cert_held":{"type":["string","null"]},"quantity":{"type":["number","null"]},"quantity_unit":{"type":"string","enum":["kg","ton","box"]},"price_usd":{"type":["number","null"]},"ship_by":{"type":["string","null"]}},"required":["offer_no","seller_code","item_code_raw","quantity_unit"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) quantity 나 ship_by 가 null 인 행, (2) 요구와 단위가 다른 행(톤과 킬로그램), (3) request_no 가 null 이라 어느 요청의 오퍼인지 모르는 행, (4) 요청에 없는 item_code_raw 표기. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"offer_no":{"type":"string"},"request_no":{"type":["string","null"]},"seller_code":{"type":"string"},"item_code_raw":{"type":"string"},"grade":{"type":["string","null"],"enum":["A","B","C",null]},"cert_held":{"type":["string","null"]},"quantity":{"type":["number","null"]},"quantity_unit":{"type":"string","enum":["kg","ton","box"]},"price_usd":{"type":["number","null"]},"ship_by":{"type":["string","null"]}},"required":["offer_no","seller_code","item_code_raw","quantity_unit"]}| Python | 비교 규칙과 단위 변환을 맡는 언어입니다. 등급·인증·포장 단위를 맞추는 규칙이 여기 있습니다. |
| TypeScript | 무역 담당자가 보는 화면입니다. 요구와 오퍼를 나란히 둡니다. |
| PostgreSQL | 요청과 오퍼를 담습니다. 원문 표기와 정규화 값을 분리한 스키마가 비교의 정확도를 좌우합니다. |
| RAG | 3주차에 셀러가 보낸 설명 문서에서 인증 문구를 찾아 오퍼 값과 맞춰 보는 데 씁니다. |
| LLM | 판정이 끝난 뒤 불일치 사유를 담당자가 읽을 한 줄로 요약합니다. 어긋남 판정은 규칙이 정하고 모델은 여기에 개입하지 않습니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 담당자의 실제 선택과 규칙 판정을 맞춰 보는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 새 오퍼를 받아 바로 비교하고 알리는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
match_code사유 라벨 · offer_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
meets_spec요구 충족grade_below등급 미달cert_missing인증 미보유quantity_short수량 부족record_missing기록 결측request_unmapped요청 미매핑오퍼 한 건의 규격 비교 결과입니다. 사유 코드와 요구 값과 오퍼 값을 함께 남깁니다.
{"type":"object","properties":{"match_id":{"type":"string"},"offer_no":{"type":"string"},"request_no":{"type":["string","null"]},"match_code":{"type":"string","enum":["meets_spec","grade_below","cert_missing","quantity_short","record_missing","request_unmapped"]},"quantity_gap_kg":{"type":["number","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string","enum":["request","offer"]},"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{"type":["string","number","null"]}},"required":["source","field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["match_id","offer_no","match_code","evidence","summary"]}[
{
"match_id": "om_0001",
"offer_no": "OF-3001",
"request_no": "RQ-2001",
"match_code": "meets_spec",
"quantity_gap_kg": null,
"evidence": [
{
"source": "request",
"field": "grade_min",
"agg": null,
"value": "A"
},
{
"source": "offer",
"field": "grade",
"agg": null,
"value": "A"
}
],
"summary": "등급과 인증과 수량이 모두 요구를 채웁니다.",
"hedge": null
},
{
"match_id": "om_0002",
"offer_no": "OF-3004",
"request_no": "RQ-2002",
"match_code": "meets_spec",
"quantity_gap_kg": null,
"evidence": [
{
"source": "request",
"field": "quantity",
"agg": null,
"value": 40
},
{
"source": "offer",
"field": "quantity",
"agg": null,
"value": 40
}
],
"summary": "요구 수량과 오퍼 수량이 같고 등급도 맞습니다.",
"hedge": null
},
{
"match_id": "om_0003",
"offer_no": "OF-3002",
"request_no": "RQ-2001",
"match_code": "grade_below",
"quantity_gap_kg": null,
"evidence": [
{
"source": "request",
"field": "grade_min",
"agg": null,
"value": "A"
},
{
"source": "offer",
"field": "grade",
"agg": null,
"value": "B"
}
],
"summary": "요구 등급은 A 인데 오퍼는 B 라 그대로 넘길 수 없습니다.",
"hedge": null
},
{
"match_id": "om_0004",
"offer_no": "OF-3003",
"request_no": "RQ-2001",
"match_code": "cert_missing",
"quantity_gap_kg": null,
"evidence": [
{
"source": "request",
"field": "cert_required",
"agg": null,
"value": "GlobalGAP"
},
{
"source": "offer",
"field": "cert_held",
"agg": null,
"value": null
}
],
"summary": "요구한 인증을 셀러가 갖고 있지 않아 확인이 필요합니다.",
"hedge": null
},
{
"match_id": "om_0005",
"offer_no": "OF-3005",
"request_no": "RQ-2004",
"match_code": "quantity_short",
"quantity_gap_kg": 200,
"evidence": [
{
"source": "request",
"field": "quantity",
"agg": null,
"value": 5000
},
{
"source": "offer",
"field": "quantity",
"agg": null,
"value": 4800
}
],
"summary": "요구보다 200kg 적어 나머지를 다른 셀러에서 채워야 합니다.",
"hedge": null
},
{
"match_id": "om_0006",
"offer_no": "OF-3006",
"request_no": "RQ-2004",
"match_code": "record_missing",
"quantity_gap_kg": null,
"evidence": [
{
"source": "offer",
"field": "quantity",
"agg": null,
"value": null
},
{
"source": "offer",
"field": "ship_by",
"agg": null,
"value": null
}
],
"summary": "수량과 선적 예정일이 비어 있어 비교할 수 없습니다.",
"hedge": null
},
{
"match_id": "om_0007",
"offer_no": "OF-3008",
"request_no": null,
"match_code": "request_unmapped",
"quantity_gap_kg": null,
"evidence": [
{
"source": "offer",
"field": "request_no",
"agg": null,
"value": null
},
{
"source": "offer",
"field": "item_code_raw",
"agg": null,
"value": "MAN-KEIT"
}
],
"summary": "어느 요청에 대한 오퍼인지 적혀 있지 않아 붙이지 못했습니다.",
"hedge": "합성 기준 판정이라 품목으로 짐작해 붙일 수도 있으나 담당자 확인이 먼저입니다."
},
{
"match_id": "om_0008",
"offer_no": "OF-3010",
"request_no": "RQ-2010",
"match_code": "meets_spec",
"quantity_gap_kg": null,
"evidence": [
{
"source": "offer",
"field": "item_code_raw",
"agg": null,
"value": "KIW-GOLD"
},
{
"source": "request",
"field": "item_code_raw",
"agg": null,
"value": "KIW-GLD"
}
],
"summary": "품목 코드 표기가 달랐지만 정규화 후 같은 품목으로 붙었고 나머지 조건도 맞습니다.",
"hedge": null
},
{
"match_id": "om_0009",
"offer_no": "OF-3011",
"request_no": "RQ-2009",
"match_code": "grade_below",
"quantity_gap_kg": null,
"evidence": [
{
"source": "request",
"field": "grade_min",
"agg": null,
"value": "B"
},
{
"source": "offer",
"field": "grade",
"agg": null,
"value": "C"
}
],
"summary": "수량은 맞지만 등급이 한 단계 낮습니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. match_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. match_id·offer_no·match_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. quantity 가 0 이상이고 price_usd 가 0 이상인지 확인합니다. 4. evidence 의 field 이름이 요청과 오퍼 데이터에 실재하는 컬럼인지 대조합니다. 5. agg 가 없는 evidence 의 value 가 그 request_no 또는 offer_no 행의 값과 같은지 확인합니다. 6. quantity_gap_kg 이 킬로그램으로 통일한 요구 수량과 오퍼 수량의 차이와 같은지 다시 계산합니다. 한쪽이 비면 quantity_gap_kg 도 null 인지 확인합니다. 7. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 담당자가 실제로 고른 오퍼 130건과 규칙이 적합으로 본 결과를 비교해 일치율과 규칙이 떨어뜨린 유형을 냅니다. [수동 검증 · 사람이 봅니다] 1. 등급이 한 단계 낮지만 가격이 크게 싼 오퍼를 어떻게 볼지 표본 20건을 눈으로 확인합니다. 2. 요청 번호가 없는 오퍼를 품목으로 짐작해 붙일지는 운영 기준을 따라 담당자가 정합니다.