나니아랩스
경력근거: 제조업의 설계 도메인에 특화된 버티컬 AI 솔루션을 제공합니다
※ 실제 산출물이 아닌 AI 예측 시안입니다.
나니아랩스는 '제조업의 설계 도메인에 특화된 버티컬 AI 솔루션'을 만들고, 공고는 '설계·시뮬레이션 데이터에 적합한 파이프라인과 인터페이스를 설계합니다'를 주요 업무 첫 줄에 둡니다. 이 화면은 그 파이프라인의 입구인 계측값 정규화와 요구 기준 판정을 React·TypeScript·FastAPI 로 만들어 보여줍니다. 구조해석과 CAE 도메인 전문성은 주장하지 않습니다. 실제 축 목록과 허용값은 나니아랩스 고객사의 설계 규격 문서가 있어야 정해집니다.
'제조업의 설계 도메인에 특화된 버티컬 AI 솔루션'이 회사의 정체성입니다. 설계 후보를 요구 기준으로 판정하는 화면이 그 제품의 앞단입니다.
주요 업무 첫 줄이 '설계·시뮬레이션 데이터에 적합한 파이프라인과 인터페이스'입니다. 계측값 적재와 단위 환산이 그 파이프라인의 입구입니다.
우대사항이 'React/Next.js + TypeScript, FastAPI + RDB' 풀스택 경험을 지목합니다. 판정 규칙은 TypeScript, 적재와 환산은 FastAPI 로 나눠 만듭니다.
설계 후보의 시뮬레이션 계측값을 받아 축 표기를 규격표 별칭에 맞추고 단위를 기준 단위로 환산한 뒤, 상한 축과 하한 축을 나눠 여유율을 계산해 판정 사유별로 후보를 보여줍니다. 판정은 규격표와 계수표가 정한 규칙이 내립니다. 모델이 쓰는 것은 가장 부족한 조건과 그 수치를 담은 요약 문장 하나뿐입니다.
나니아랩스 AI Product Engineer 입니다. 설계자가 시뮬레이션 결과를 엑셀로 옮겨 놓고 요구 조건마다 손으로 여유를 따지던 일을 제품 화면 하나로 옮기려는 사람입니다.
계측값이 축마다 다른 단위로 들어와도, 환산 계수표와 규격표를 읽어 규칙으로 여유율을 계산하면 후보별 판정과 여유가 가장 모자란 조건이 정해집니다.
계측 파일을 올리면 축 표기를 규격표 별칭에 맞추고 단위를 환산해, 후보마다 여유율과 여유가 가장 모자란 조건을 판정 사유별로 보여주는 화면입니다. 사내 CAE 연동 없이 CSV 내려받기 파일을 입력으로 삼습니다.
설계 엔지니어 2명이 매긴 판정과 도구 판정의 일치도를 재고, 규격표를 화면에서 고쳐 같은 계측으로 판정을 다시 돌립니다.
부족한 조건의 설계 변수를 한 단계 바꿔 다시 판정하는 사이클을 자동으로 한 바퀴 돌립니다.
설계자가 계측 파일을 올리면 후보마다 여유율과 여유가 가장 모자란 조건이 몇 초 안에 나옵니다.
단위가 섞이고 값이 빈 계측 30종을 넣어도 판정이 멈추지 않고 사유를 붙인 보류로 남습니다.
판정 근거로 인용한 수치가 실제 계측 행·규격표 행과 같은 비율 100%. 어긋나면 그 판정을 화면에서 막습니다
측정: 근거 대조 규칙, 표본: 판정 항목 60건
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 작성자가 만든 더미데이터 자가채점이며, 실제 설계 판정 정확도는 나니아랩스 실측 데이터와 실 검수자가 있어야 잽니다. 이 포트폴리오는 그 측정을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실측 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 결측 계측·경계값·중복 계측 번호·축 표기 흔들림·미등록 단위를 더미에 일부러 섞어 쉬운 문제만 푸는 게 아님을 보입니다.
이 포트폴리오가 보여주는 것은 규칙 엔진과 화면 구현입니다. 구조해석·CAE 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
규격을 지켰다는 판정이 제조 가능하다는 뜻은 아닙니다. 이 화면은 규격표가 정한 축까지만 말하고 제조 가부는 단정하지 않습니다.
실제 도입에서는 축 목록·허용값·가중치가 고객사마다 다릅니다. 규격표와 계수표를 실데이터로 다시 채우는 것이 도입 단계 과제입니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
설계 후보의 시뮬레이션 계측값을 원문 표기 그대로 담습니다. 축 표기와 단위가 파일마다 흔들리고, 시뮬이 끝나지 않은 행은 값이 비어 있습니다
| meas_no | candidate_id | axis_raw | value | unit_raw | run_status | part_name | measured_at |
|---|---|---|---|---|---|---|---|
| M-2026-0301 | CAND-101 | 최대응력 | 268.4 | MPa | ok | 브래킷 상부 | 2026-08-11 |
| M-2026-0302 | CAND-101 | 중량 | 2.62 | kg | ok | 브래킷 상부 | 2026-08-11 |
| M-2026-0303 | CAND-103 | 최대 응력 | 214 | MPa | ok | — | 2026-08-11 |
| M-2026-0304 | CAND-103 | 중량 | 3,000 | g | ok | 브래킷 하부 | 2026-08-11 |
| M-2026-0305 | CAND-104 | 최대응력 | 21.6 | kgf/mm2 | ok | 브래킷 상부 | 2026-08-12 |
{"type":"object","properties":{"meas_no":{"type":"string"},"candidate_id":{"type":"string"},"axis_raw":{"type":"string"},"value":{"type":["number","null"],"minimum":0},"unit_raw":{"type":"string"},"run_status":{"type":"string","enum":["ok","partial","failed"]},"part_name":{"type":["string","null"]},"measured_at":{"type":"string"}},"required":["meas_no","candidate_id","axis_raw","value","unit_raw","run_status","part_name","measured_at"]} 쉬운 데이터만 만들지 말 것. 다음 하드 케이스를 의도적으로 섞어라: value 가 null 인 시뮬 미완료 행과 part_name 이 null 인 행(결측), value 가 0 이거나 규격표 허용값과 정확히 같은 행(경계값), 같은 meas_no 가 두 줄인 행(중복 계측), 같은 축을 '최대응력'·'최대 응력'·'Max Stress' 로 적은 행(표기 흔들림), 규격표 별칭에 없는 axis_raw 와 계수표에 없는 unit_raw(미매핑). 형식 깨뜨리기 시도 3건도 별도로 만들어라.{"type":"object","properties":{"meas_no":{"type":"string"},"candidate_id":{"type":"string"},"axis_raw":{"type":"string"},"value":{"type":["number","null"],"minimum":0},"unit_raw":{"type":"string"},"run_status":{"type":"string","enum":["ok","partial","failed"]},"part_name":{"type":["string","null"]},"measured_at":{"type":"string"}},"required":["meas_no","candidate_id","axis_raw","value","unit_raw","run_status","part_name","measured_at"]}프로젝트가 정한 설계 요구 기준표입니다. 축마다 상한인지 하한인지, 허용값과 기준 단위, 종합 점수 가중치, 미달 시 후보 전체를 막는 축인지를 담습니다. 계측 행에는 이 값이 들어가지 않고 판정할 때 읽어 씁니다
| axis_id | axis_label | axis_alias_raw | direction | limit_value | base_unit | axis_weight | gate_axis |
|---|---|---|---|---|---|---|---|
| max_stress | 최대응력 | 최대 응력 | upper | 250 | MPa | 0.25 | true |
| mass | 중량 | 중량(kg) | upper | 3 | kg | 0.2 | true |
| unit_cost | 원가 | 제조원가 | upper | 12,000 | 원 | 0.15 | false |
| safety_factor | 안전계수 | 안전 계수 | lower | 1.5 | 배 | 0.15 | true |
| max_disp | 최대변위 | 최대 변위 | upper | 1.5 | mm | 0.08 | false |
{"type":"object","properties":{"axis_id":{"type":"string"},"axis_label":{"type":"string"},"axis_alias_raw":{"type":"string"},"direction":{"type":"string","enum":["upper","lower"]},"limit_value":{"type":"number"},"base_unit":{"type":"string"},"axis_weight":{"type":"number"},"gate_axis":{"type":"boolean"}},"required":["axis_id","axis_label","axis_alias_raw","direction","limit_value","base_unit","axis_weight","gate_axis"]} 쉬운 데이터만 만들지 말 것. 축은 응력·중량·원가·변위·안전계수·고유진동수·열변형·피로수명·온도·강성·좌굴처럼 실제 구조 설계 요구에서 골라라. direction 은 upper 와 lower 를 섞고, axis_alias_raw 는 설계팀이 문서에 실제로 쓰는 띄어쓰기·괄호 표기로 적어라. axis_weight 합이 1.0 이 되게 맞추고, gate_axis 는 소수만 true 로 둬라. 형식 깨뜨리기 시도 3건도 별도로 만들어라.{"type":"object","properties":{"axis_id":{"type":"string"},"axis_label":{"type":"string"},"axis_alias_raw":{"type":"string"},"direction":{"type":"string","enum":["upper","lower"]},"limit_value":{"type":"number"},"base_unit":{"type":"string"},"axis_weight":{"type":"number"},"gate_axis":{"type":"boolean"}},"required":["axis_id","axis_label","axis_alias_raw","direction","limit_value","base_unit","axis_weight","gate_axis"]}단위 환산 계수표입니다. 계측 파일의 원문 단위 표기를 규격표 기준 단위로 바꾸는 계수를 담습니다. 계수표에 없는 단위는 환산이 되지 않아 판정이 미뤄집니다
| unit_raw | unit_code | quantity_kind | base_unit | to_base_factor |
|---|---|---|---|---|
| MPa | MPa | stress | MPa | 1 |
| N/mm2 | MPa | stress | MPa | 1 |
| kgf/mm2 | MPa | stress | MPa | 9.807 |
| kg | kg | mass | kg | 1 |
| g | kg | mass | kg | 0.001 |
{"type":"object","properties":{"unit_raw":{"type":"string"},"unit_code":{"type":"string"},"quantity_kind":{"type":"string"},"base_unit":{"type":"string"},"to_base_factor":{"type":"number"}},"required":["unit_raw","unit_code","quantity_kind","base_unit","to_base_factor"]} 쉬운 데이터만 만들지 말 것. 같은 물리량을 가리키는 서로 다른 원문 표기를 함께 넣어라(MPa 와 N/mm2, kg 와 g, mm 와 um, 원 과 천원). to_base_factor 는 1 이 아닌 값을 절반 이상 두고, quantity_kind 가 다른데 표기가 비슷한 단위도 섞어라. 계수표에 일부러 넣지 않을 단위(psi·ksi 등)는 계측 쪽에만 두어 미매핑이 생기게 하라. 형식 깨뜨리기 시도 3건도 별도로 만들어라.{"type":"object","properties":{"unit_raw":{"type":"string"},"unit_code":{"type":"string"},"quantity_kind":{"type":"string"},"base_unit":{"type":"string"},"to_base_factor":{"type":"number"}},"required":["unit_raw","unit_code","quantity_kind","base_unit","to_base_factor"]}| TypeScript | 여유율 계산과 판정 규칙을 타입으로 고정하고 응답 형식을 런타임에 거릅니다 |
| React | 판정 사유별로 나눈 후보 목록과 근거 상세를 그립니다 |
| Next.js | 화면 셸과 계측 파일 업로드 라우트를 맡습니다 |
| FastAPI | 계측 파일을 받아 환산과 판정 결과를 돌려주는 `POST /evaluate` 를 맡습니다 |
| Python | 계측 CSV 적재와 축 표기 정규화, 여유율 재계산 스크립트를 맡습니다 |
| MLOps | 규격표 버전과 계수표 버전을 실행마다 남겨 같은 판정을 다시 만들 수 있게 합니다 |
| LLM | 판정이 끝난 뒤 가장 부족한 조건을 설계자용 한 문장으로 적습니다 |
✓ pass → 그대로 멈춰도 완성품입니다. 더 키우려면 2주차 사람 판정 대조로 갑니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 3주차 설계 변수 제안 루프로 갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · candidate_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
meets기준 충족below_limit기준 미달needs_rerun재계측 필요axis_unmapped축 미매핑unit_unmapped단위 미매핑duplicate_measurement계측 중복후보 하나의 판정 카드입니다. 판정 사유와 여유율, 그 계산에 쓰인 근거 행을 담습니다
{"type":"object","properties":{"candidate_id":{"type":"string"},"verdict":{"type":"string","enum":["meets","below_limit","needs_rerun","axis_unmapped","unit_unmapped","duplicate_measurement"]},"binding_axis_id":{"type":["string","null"],"description":"여유율이 가장 낮은 축의 axis_id 입니다."},"value_base":{"type":["number","null"],"description":"환산값 = value x to_base_factor. 규격표의 base_unit 으로 맞춘 계산값입니다."},"margin_pct":{"type":["number","null"],"description":"여유율(%). direction 이 upper 이면 (limit_value - value_base) / limit_value x 100, lower 이면 (value_base - limit_value) / limit_value x 100 입니다."},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"value":{"type":["string","number","null"]},"agg":{"type":"string","enum":["count","sum","avg","min","max"]}},"required":["field","value"]}},"summary":{"type":"string"}},"required":["candidate_id","verdict","binding_axis_id","value_base","margin_pct","evidence","summary"]}[
{
"candidate_id": "CAND-104",
"verdict": "meets",
"binding_axis_id": "max_stress",
"value_base": 211.82,
"margin_pct": 15.3,
"evidence": [
{
"field": "meas_no",
"value": "M-2026-0305"
},
{
"field": "value",
"value": 21.6
},
{
"field": "unit_raw",
"value": "kgf/mm2"
},
{
"field": "to_base_factor",
"value": 9.80665
},
{
"field": "limit_value",
"value": 250
}
],
"summary": "최대응력이 환산 뒤 211.8MPa 라 허용값 250MPa 아래이고, 세 축 모두 규격표를 지켜 여유가 가장 적은 축이 최대응력입니다."
},
{
"candidate_id": "CAND-101",
"verdict": "below_limit",
"binding_axis_id": "max_stress",
"value_base": 268.4,
"margin_pct": -7.4,
"evidence": [
{
"field": "meas_no",
"value": "M-2026-0301"
},
{
"field": "value",
"value": 268.4
},
{
"field": "to_base_factor",
"value": 1
},
{
"field": "limit_value",
"value": 250
}
],
"summary": "최대응력 268.4MPa 가 허용값 250MPa 를 7.4% 넘겨 이 후보는 기준에 못 미칩니다."
},
{
"candidate_id": "CAND-103",
"verdict": "meets",
"binding_axis_id": "mass",
"value_base": 3,
"margin_pct": 0,
"evidence": [
{
"field": "meas_no",
"value": "M-2026-0304"
},
{
"field": "value",
"value": 3000
},
{
"field": "unit_raw",
"value": "g"
},
{
"field": "to_base_factor",
"value": 0.001
},
{
"field": "limit_value",
"value": 3
}
],
"summary": "중량 3000g 을 환산하면 3.0kg 로 허용값과 같아 여유율이 0% 인 경계 후보입니다."
},
{
"candidate_id": "CAND-107",
"verdict": "needs_rerun",
"binding_axis_id": null,
"value_base": null,
"margin_pct": null,
"evidence": [
{
"field": "meas_no",
"value": "M-2026-0310"
},
{
"field": "run_status",
"value": "failed"
},
{
"field": "meas_no",
"value": "M-2026-0311"
},
{
"field": "value",
"value": 0
},
{
"field": "limit_value",
"value": 1.5
}
],
"summary": "최대응력 계측이 비어 있고 안전계수가 0 으로 남아 여유율을 계산하지 못하므로 재계측으로 돌립니다."
},
{
"candidate_id": "CAND-105",
"verdict": "duplicate_measurement",
"binding_axis_id": null,
"value_base": null,
"margin_pct": null,
"evidence": [
{
"field": "meas_no",
"value": "M-2026-0308"
},
{
"field": "meas_no",
"agg": "count",
"value": 2
},
{
"field": "value",
"value": 233.1
}
],
"summary": "같은 계측 번호 M-2026-0308 이 두 줄이라 어느 줄을 쓸지 정하기 전까지 판정을 미룹니다."
},
{
"candidate_id": "CAND-108",
"verdict": "axis_unmapped",
"binding_axis_id": null,
"value_base": null,
"margin_pct": null,
"evidence": [
{
"field": "meas_no",
"value": "M-2026-0312"
},
{
"field": "axis_raw",
"value": "좌굴 여유"
},
{
"field": "value",
"value": 1.12
}
],
"summary": "'좌굴 여유' 표기가 규격표의 어느 축 별칭과도 맞지 않아 이 계측을 판정에서 뺍니다."
},
{
"candidate_id": "CAND-109",
"verdict": "unit_unmapped",
"binding_axis_id": null,
"value_base": null,
"margin_pct": null,
"evidence": [
{
"field": "meas_no",
"value": "M-2026-0314"
},
{
"field": "unit_raw",
"value": "psi"
},
{
"field": "value",
"value": 34.8
}
],
"summary": "단위 psi 가 계수표에 없어 최대응력을 MPa 로 환산하지 못하므로 판정을 미룹니다."
}
][자동 검증 · 매 실행] 1. verdict 가 meets·below_limit·needs_rerun·axis_unmapped·unit_unmapped·duplicate_measurement 여섯 값 안에 있는지 봅니다. 2. candidate_id·verdict·binding_axis_id·value_base·margin_pct·evidence·summary 가 모든 판정 카드에 있는지 봅니다. 3. margin_pct 가 -100 이상 100 이하인지 봅니다. 4. value_base 를 value x to_base_factor 로, margin_pct 를 direction 이 upper 이면 (limit_value - value_base) / limit_value x 100, lower 이면 (value_base - limit_value) / limit_value x 100 으로 다시 계산해 값이 같은지 봅니다. 5. evidence 의 field 가 sim_measurements·requirement_spec·unit_scale_catalog 의 컬럼에 실재하는지 봅니다. 6. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 행 값과 같은지 봅니다. 여러 행에서 센 수치는 agg 에 count 를 적습니다. 7. axis_raw 가 규격표 별칭 어디에도 닿지 않으면 verdict 는 axis_unmapped 이고, unit_raw 가 계수표에 없으면 verdict 는 unit_unmapped 인지 봅니다. 중복 계측 번호와 결측 계측도 같은 방식으로 사유가 갈라집니다. 8. summary 가 존댓말 한 문장으로 끝나는지 봅니다. [평가 러너 · 2주차] 설계 엔지니어 2명이 손으로 매긴 판정과 도구 판정의 일치도(Cohen's κ: 우연히 맞은 몫을 뺀 일치 정도, 0~1)를 잽니다. [수동 검증 · 사람이 봅니다] 1. 여유율이 0% 근처인 경계 후보를 통과로 볼지 보류로 볼지는 설계 리드가 정합니다. 2. 규격표에 없는 축이 새 요구인지 오탈자인지는 표본 20건을 눈으로 확인합니다.