힉스의 CUSME SHOES는 발 측정 데이터와 3D 파츠 데이터를 같은 제품 안에서 다루고 제조 지식그래프를 신규 기능으로 올리고 있어서, 추천이 취향 점수가 아니라 실제로 만들 수 있는 조합인지에 걸립니다. 라스트와 파츠가 호환되지 않으면 아무리 잘 맞는 치수여도 제조가 안 되므로, 추천과 제조 제약이 한 그래프에서 함께 풀려야 하는 구조는 힉스의 맞춤 제조 맥락에서 나옵니다. 이 포트폴리오가 보여주는 것은 도메인 관계를 그래프로 모델링해 탐색하고 그 경로를 근거로 API에 실어 내는 기술이며, 신발 제조나 족부 계측 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
발 측정 데이터를 관리하고 그 데이터로 만든 추천 로직을 서비스 API로 연결해 운영하는 일을 주요 업무로 둡니다.
제조 지식그래프 같은 신규 기능의 데이터 적재와 조회 백엔드를 직접 개발하고 스키마를 설계합니다.
익숙하지 않은 도메인을 빠르게 익혀 제품에 적용하고 요구사항이 덜 정해진 상태에서도 구현 방향을 잡는 일을 요구합니다.
힉스가 CUSME SHOES에서 발 측정 데이터로 맞춤 신발을 만들고 제조 지식그래프로 파츠를 관리하는 맥락에서, 발 길이·볼폭·발등높이를 받아 라스트와 파츠·소재의 호환 관계를 담은 그래프를 타고 실제로 만들 수 있는 신발 구성을 찾아 그 경로를 근거로 돌려주는 추천 API와 확인 화면입니다. 발 측정값과 파츠 카탈로그는 실 데이터 대신 더미를 가정합니다.
발이 넓어 늘 반 치수 크게 신다가 뒤꿈치가 벗겨지던 손님에게 어떤 라스트와 파츠 조합을 권해야 할지 매번 선배에게 물어보던 매장 담당자가, 치수를 넣으면 만들 수 있는 조합과 그 이유를 바로 받아보고 싶은 사람입니다.
라스트와 파츠, 소재의 호환 관계를 그래프로 두면 발 치수에서 시작해 실제로 제조 가능한 조합만 남기고 그 경로를 근거로 보여줄 수 있다는 가정입니다.
발 치수를 넣으면 라스트·파츠·소재 호환 그래프를 타고 제조 가능한 신발 구성을 찾아 각 조합이 선택된 경로를 근거로 돌려주는 REST API와, 그 결과를 펼쳐 보는 확인 화면이 도는 완결품입니다. 3D 파츠 파일 처리는 파츠 메타데이터를 가정하고 시작합니다.
1주차 API를 AWS로 배포해 운영하고, 기대 조합을 붙인 발 치수 표본으로 추천 일치율과 폴백 발동률을 재 볼폭 미측정 같은 결측에서 어떤 조합이 나오는지 정리합니다.
2주차 배포 위에 파츠 카탈로그를 수천 건 규모로 늘려 그래프 조회 인덱스를 적용하고, 적용 전후 응답 시간을 나란히 보여주며 카탈로그가 바뀔 때 그래프를 다시 채우는 적재 흐름을 자동화합니다.
발 치수를 넣으면 그래프 탐색부터 제조 가능한 조합과 경로 근거, 설명 문장까지 한 응답에 담기는 과정을 시연합니다.
추천된 조합이 호환 간선을 실제로 만족하는지 조합별로 대조해 만들 수 없는 조합이 섞이지 않음을 보여주되, 파츠 카탈로그와 기대 조합을 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
볼폭 미측정·극단 사이즈·단종 파츠·파츠명 표기 흔들림 30종을 넣어도 API가 멈추지 않고 조합이나 사유를 돌려주는 강건성을 보여줍니다.
파츠 카탈로그를 수천 건으로 늘렸을 때 인덱스 적용 전후 응답 시간 변화를 나란히 보여줍니다.
위 수치는 더미데이터 자가채점이며, 실제 추천 만족도와 제조 적합성은 힉스의 실 파츠 카탈로그와 실 발 측정 데이터, 실 제조 담당자가 있어야 측정 가능합니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아니므로, 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 게 당연하므로, 볼폭 미측정·극단 사이즈·단종 파츠·표기 흔들림 같은 경계 케이스를 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
이 포트폴리오가 보여주는 것은 도메인 관계를 그래프로 모델링해 탐색하고 경로를 근거로 내는 기술이며, 신발 제조나 족부 계측 도메인 전문성은 주장하지 않습니다. 조합 선택은 그래프 탐색이 하고 LLM은 설명 문장만 맡습니다.
치수가 맞는 조합이 곧 잘 맞는 신발인 것은 아니어서 착용감은 측정값만으로 결정되지 않으므로, 추천 결과에 착용 만족을 단정하는 문구를 쓰지 않습니다.
실제 도입 때는 파츠가 단종되고 라스트가 개정되어 호환 간선이 수시로 바뀌므로, 카탈로그 변경을 그래프에 반영하는 적재 운영을 도입 단계 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
추천 입력이 되는 발 측정 기록으로, 볼폭 미측정·극단 사이즈·좌우 편차 같은 하드 케이스를 일부러 섞어 폴백과 그래프 탐색이 제대로 도는지 확인합니다.
| scan_id | customer_id | side | length_mm | width_mm | instep_mm | arch_type_raw | arch_type_id | preferred_part_raw |
|---|---|---|---|---|---|---|---|---|
| S-001 | C-01 | L | 258 | 101.5 | 74 | 보통 | NORMAL | 쿠션 미드솔 |
| S-002 | C-01 | R | 261.5 | 104 | 75.5 | 보통 | NORMAL | 쿠션 미드솔 |
| S-003 | C-02 | L | 243 | — | 68 | 평발 | FLAT | — |
| S-004 | C-02 | R | 243.5 | — | — | 평발 | FLAT | — |
| S-005 | C-03 | L | 310 | 118 | 88 | 요족 | CAVUS | 와이드 라스트 |
다음 JSON Schema 에 맞는 발 측정 행을 더 생성하세요: {"type":"object","properties":{"scan_id":{"type":"string"},"customer_id":{"type":"string"},"side":{"type":"string","enum":["L","R"]},"length_mm":{"type":"number"},"width_mm":{"type":["number","null"]},"instep_mm":{"type":["number","null"]},"arch_type_raw":{"type":"string"},"arch_type_id":{"type":["string","null"]},"preferred_part_raw":{"type":["string","null"]}},"required":["scan_id","customer_id","side","length_mm","arch_type_raw"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라: width_mm 가 null 이라 볼폭 폴백이 필요한 결측, width_mm 와 instep_mm 가 함께 null 인 이중 결측, length_mm 가 215 나 310 처럼 사이즈 구간 양 끝인 극단값, 같은 customer_id 의 좌우 측정값이 크게 어긋나는 편차(한쪽 130, 다른 쪽 76), arch_type_id 가 null 이라 아치 유형 마스터에 매핑 안 된 미등록 유형, preferred_part_raw 가 같은 파츠의 표기 흔들림(경량 아웃솔 X9 를 경량아웃솔X9 로)이라 정규화가 필요한 경우. 좌우를 한 쌍으로 만들고, 형식 깨뜨리기 시도(스키마 타입 위반) 3건은 데이터가 아니라 별도 목록으로 표시하세요.{"type":"object","properties":{"scan_id":{"type":"string"},"customer_id":{"type":"string"},"side":{"type":"string","enum":["L","R"]},"length_mm":{"type":"number"},"width_mm":{"type":["number","null"]},"instep_mm":{"type":["number","null"]},"arch_type_raw":{"type":"string"},"arch_type_id":{"type":["string","null"]},"preferred_part_raw":{"type":["string","null"]}},"required":["scan_id","customer_id","side","length_mm","arch_type_raw"]}라스트와 파츠, 소재의 호환 관계를 담은 그래프 간선으로, 단종 파츠와 상충 조합 같은 하드 케이스를 섞어 탐색이 만들 수 없는 조합을 걸러 내는지 확인합니다.
| edge_id | from_node | from_type | to_node | to_type | relation | min_length_mm | max_length_mm | discontinued |
|---|---|---|---|---|---|---|---|---|
| G-001 | LAST-STD-260 | last | PART-MID-CUSH | part | fits | 250 | 269.9 | false |
| G-002 | LAST-WIDE-260 | last | PART-MID-CUSH | part | fits | 250 | 269.9 | false |
| G-003 | LAST-WIDE-310 | last | PART-OUT-X9 | part | fits | 300 | 320 | false |
| G-004 | PART-MID-CUSH | part | MAT-EVA | material | requires | — | — | false |
| G-005 | PART-OUT-X9 | part | MAT-RUBBER | material | requires | — | — | false |
다음 JSON Schema 에 맞는 제조 호환 간선 행을 더 생성하세요: {"type":"object","properties":{"edge_id":{"type":"string"},"from_node":{"type":"string"},"from_type":{"type":"string","enum":["last","part","material"]},"to_node":{"type":"string"},"to_type":{"type":"string","enum":["last","part","material"]},"relation":{"type":"string","enum":["fits","requires","conflicts"]},"min_length_mm":{"type":["number","null"]},"max_length_mm":{"type":["number","null"]},"discontinued":{"type":"boolean"}},"required":["edge_id","from_node","from_type","to_node","to_type","relation","discontinued"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라: discontinued 가 true 라 탐색에서 빠져야 하는 단종 파츠 간선, relation 이 conflicts 라 함께 못 쓰는 조합, min_length_mm 과 max_length_mm 이 딱 맞닿아 경계 치수에서 어느 라스트를 고를지 갈리는 구간, min_length_mm 이 null 이라 치수 제약이 없는 간선, 어떤 라스트에서도 닿지 않아 고아로 남는 파츠 노드, 특정 치수 구간에 fits 간선이 하나도 없어 추천 조합이 나오지 않는 구멍. 노드 종류를 골고루 섞고, 형식 깨뜨리기 시도(스키마 타입 위반) 3건은 데이터가 아니라 별도 목록으로 표시하세요.{"type":"object","properties":{"edge_id":{"type":"string"},"from_node":{"type":"string"},"from_type":{"type":"string","enum":["last","part","material"]},"to_node":{"type":"string"},"to_type":{"type":"string","enum":["last","part","material"]},"relation":{"type":"string","enum":["fits","requires","conflicts"]},"min_length_mm":{"type":["number","null"]},"max_length_mm":{"type":["number","null"]},"discontinued":{"type":"boolean"}},"required":["edge_id","from_node","from_type","to_node","to_type","relation","discontinued"]}| Python | 그래프 탐색과 추천 규칙, 적재 배치를 구현합니다. |
| FastAPI | 발 치수를 받아 추천 조합과 경로 근거를 돌려주는 REST API를 구현합니다. |
| PostgreSQL | 라스트·파츠·소재 노드와 호환 간선을 저장하고 조회 인덱스로 응답 시간을 줄입니다. |
| 제조 지식그래프 | 라스트와 파츠, 소재의 호환 관계를 노드와 간선으로 모델링해 제조 가능한 조합만 남깁니다. |
| React | 추천 조합과 선택 경로, 폴백 표시를 펼쳐 보는 확인 화면을 구현합니다. |
| AWS | 2주차부터 추천 API를 AWS로 배포하고 운영합니다. |
| LLM API(Claude) | 그래프가 고른 경로가 왜 그 조합을 만드는지 매장 담당자가 읽을 설명 문장으로 요약하고, 조합 선택은 그래프 탐색이 합니다. |
| JSON Schema | 발 측정값과 추천 결과의 모양을 강제해 필드 누락을 막고 더미 증강에 씁니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차에서 AWS 배포와 추천 일치율 측정을 붙입니다.
✓ pass → 여기서 멈춰도 검증된 완결입니다. 더 키우려면 3주차에서 카탈로그 확장과 조회 성능을 다룹니다.
발 측정 한 건에 대한 추천 조합과 그 조합이 선택된 경로를 함께 내보내, 만들 수 없는 조합이나 근거 없는 추천이 API 밖으로 나가지 않게 합니다.
{"type":"object","properties":{"scan_id":{"type":"string"},"last_id":{"type":["string","null"]},"parts":{"type":"array","items":{"type":"string"}},"path":{"type":"array","items":{"type":"object","properties":{"edge_id":{"type":"string"},"relation":{"type":"string","enum":["fits","requires","conflicts"]}},"required":["edge_id","relation"]}},"fallbacks":{"type":"array","items":{"type":"string","enum":["width_estimated","arch_default","no_candidate"]}},"explanation":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["scan_id","last_id","parts","path","fallbacks","explanation"]}[
{
"scan_id": "S-001",
"last_id": "LAST-STD-260",
"parts": [
"PART-MID-CUSH",
"MAT-EVA"
],
"path": [
{
"edge_id": "G-001",
"relation": "fits"
},
{
"edge_id": "G-004",
"relation": "requires"
}
],
"fallbacks": [],
"explanation": "발 길이 258mm가 표준 라스트 260의 적용 구간에 들어가고, 쿠션 미드솔이 그 라스트에 맞으며 EVA 소재를 함께 요구해 이 조합이 나왔습니다.",
"hedge": null
},
{
"scan_id": "S-003",
"last_id": "LAST-FLAT-243",
"parts": [
"PART-ARCH-SUP",
"MAT-TPU"
],
"path": [
{
"edge_id": "G-008",
"relation": "requires"
},
{
"edge_id": "G-009",
"relation": "requires"
}
],
"fallbacks": [
"width_estimated"
],
"explanation": "볼폭이 측정되지 않아 길이 기준 평균 볼폭으로 추정했고, 평발 라스트 243이 아치 서포트를 요구해 TPU 소재까지 이어졌습니다.",
"hedge": "볼폭을 추정값으로 채웠으므로 실제 계측 후 라스트가 바뀔 수 있습니다(합성 기준 추정)."
},
{
"scan_id": "S-007",
"last_id": null,
"parts": [],
"path": [],
"fallbacks": [
"arch_default",
"no_candidate"
],
"explanation": "길이 215mm 구간의 라스트에 연결된 파츠가 단종되어 현재 만들 수 있는 조합이 없습니다.",
"hedge": "아치 유형이 마스터에 없어 기본값으로 처리했고, 단종 파츠를 빼면 후보가 남지 않아 조합을 비웠습니다."
},
{
"scan_id": "S-005",
"last_id": "LAST-WIDE-310",
"parts": [
"PART-OUT-X9",
"MAT-RUBBER"
],
"path": [
{
"edge_id": "G-003",
"relation": "fits"
},
{
"edge_id": "G-005",
"relation": "requires"
}
],
"fallbacks": [],
"explanation": "길이 310mm와 넓은 볼폭이 와이드 라스트 310에 맞고, 경량 아웃솔 X9가 러버 소재를 요구해 이 조합이 확정됐습니다.",
"hedge": null
}
][자동 검증] 1. path 의 각 edge_id 가 호환 그래프에 실제로 있는 간선인지 대조하고, 없는 간선을 인용한 추천을 거릅니다. 2. parts 의 모든 항목이 path 를 따라가면 도달하는 노드인지 확인해, 경로 없이 끼어든 파츠를 걸러 냅니다. 3. discontinued 가 true 인 간선이 path 에 있으면 실패로 표시해 단종 파츠 추천을 막습니다. 4. relation 이 conflicts 인 두 파츠가 parts 에 함께 들어 있으면 불일치로 표시합니다. 5. width_mm 가 null 인데 fallbacks 에 width_estimated 가 없으면 폴백 누락으로 표시합니다. [수동 검증] 1. 좌우 측정값이 크게 어긋날 때 어느 쪽을 기준으로 라스트를 고를지 제조 담당자가 정합니다. 2. 경계 치수에서 두 라스트가 모두 가능할 때 우선순위를 담당자가 정합니다.