- 발행일
메인 화면의 판독 박스는 서버 설정 셋이 그린다 — 색·임계값·켜짐을 화면 열두 곳이 각자 물었다
메인 화면의 판독 박스는 서버 설정 셋이 그린다
이 글은 2026년 9월에 당시 코드를 다시 보며 정리한 것입니다. 저장소는 퇴사 전에 스냅샷으로 보관한 것이라 커밋 이력이 없고, 경력기술서와 당시 쓴 지원서·이력서에 적은 담당 범위와 코드를 대조해 썼습니다. 날짜는 코드에 남은 시기 단서 기준입니다.
공항 보안 검색 판독 시스템의 메인 화면은 엑스레이 영상 위에 상자를 그리는 화면입니다. AI가 "이 자리에 이 클래스가 이 점수로 있다"고 보내면, 화면이 그 자리에 다각형을 그리고 색을 입히고, 점수가 높으면 소리를 냅니다. 이 글은 그 상자 하나가 그려지기까지 화면이 무엇을 묻는지를 코드로 따라간 기록이에요. 경력기술서 2.3절에 "옵션을 설정으로 뺐다"고 한 줄로 적은 것의 실제 모양이고, 이력서에 제 담당으로 적은 "로그인, 설정, 웹소켓 아이콘" 중 설정이 바로 이 자리입니다.
옵션은 어디서 왔나
2022년 중반에 쓴 지원서의 5월 항목은 이렇게 적고 있습니다. 발주처 이름은 뺐습니다.
추가된 요구사항을 만족을 시킬 수 있도록 설계 (공항 납품처 한 곳)
- 화살표 방향을 기계에 추가 입력
- 옵션을 추가 (레이블 표시, 레이블 위/아래, bbox 추가 등)
- 설정의 thres 값을 직접 작성함으로써 사용자의 보기 편한 시선으로 제작
이 세 줄이 코드 어디에 있는지는 바로 찾힙니다. 화살표 방향은 localStorage의 direction 키와 소켓 메시지 change_rail_left·change_rail_right로, 레이블 표시와 위/아래는 label·label_top 키로, 상자 표시는 roi 키와 BboxOn prop으로, 임계값 직접 입력은 설정 화면의 input type="number"로요. 지원서는 같은 항목에 "너무나 다양한 요구사항 탓에 기본 제품의 틀이 많이 망가져 있어서 확장성은 고려하기 힘들 것으로 판단"이라고도 적었습니다. 설정 항목이 늘어난 배경이 그것이었어요.
상자 하나에 값 셋
src/view/main/mainImage/MainImage.js는 화면이 뜨면 서버 설정을 먼저 받아 둡니다.
const [settingColor, setSettingColor] = useState([]);
useEffect(() => {
axios.get(SETTINGS_URL + '/get_settings')
.then(res => setSettingColor(res.data))
.catch(err => console.log(err));
}, []);
응답은 클래스별 객체의 배열이고, 각 객체에 code·color·thres·check가 있습니다. 그리고 그 배열에서 값을 꺼내는 함수가 셋 있어요.
const setIconBackgroundColor = (icon) => { /* code가 같은 항목의 color */ }
const setThresDisplay = (icon) => { /* code가 같은 항목의 thres */ }
const setToggleDisplay = (icon) => { /* code가 같은 항목의 check */ }
이름은 set…인데 하는 일은 조회입니다. 셋 다 for 문으로 배열을 훑어 code가 맞는 항목의 필드 하나를 돌려줘요. 상자를 그리는 자리에서 이 셋을 한 번에 씁니다.
<polygon key={i}
className="box_top"
points={BboxXY}
data-class={setTopCls}
data-scores={setScore}
style={{
display: setThresDisplay(setTopCls) < setScore && setToggleDisplay(setTopCls) === "on" ? "flex" : "none",
stroke: setIconBackgroundColor(setTopCls)
}}
/>
상자가 보일 조건은 두 개의 AND입니다. 그 클래스의 임계값보다 점수가 높고, 그 클래스가 켜져 있을 것. 색은 세 번째 값이에요. 즉 상자 하나는 서버 설정의 값 셋으로 결정되고, 화면은 그 셋을 조합만 합니다. 설정 화면(Setting.js)에서 색을 고르거나 임계값을 숫자로 적으면 같은 모양 {code: {check, color, thres}}로 modify_settings에 보내고, 저장되면 localStorage에 save_setting을 써서 다른 창이 새로고침하게 합니다.
이 세 값 모델은 SPA로 오면서 만든 게 아닙니다. 옛 서버 템플릿 settings.html에도 "탐지유형색상"이라는 제목 아래 클래스마다 input type="color"와 0~100 range 슬라이더, 켜고 끄는 버튼이 한 칸씩 있었어요. 새 화면은 그 모델을 받아 React로 옮기면서, 지원서에 적은 대로 슬라이더 옆에 숫자 입력을 붙였습니다.
같은 질문을 열두 파일이 각자 한다
문제는 이 조회가 한 곳에 있지 않다는 겁니다. get_settings를 부르는 파일이 열두 개예요. 메인 화면의 위쪽 영상(MainImage.js)과 아래쪽 이력 띠(MainBottom.js)가 각각 부르고, 둘을 감싼 Main.js도 부릅니다. 검색 목록·섬네일·상세·통계·설정·양화면까지 전부 자기 useEffect에서 같은 주소를 칩니다.
그리고 MainBottom.js 517~537행에는 MainImage.js의 조회 함수 셋이 이름까지 같게 한 벌 더 있습니다. 같은 파일 48행을 보면 부모에게서 getSetting이라는 prop으로 설정을 이미 받고 있는데도요. 한 파일 안에 설정이 두 경로로 들어오고, 조회 함수는 다른 파일 것을 복사해 둔 상태였습니다.
이걸 그때 왜 그렇게 뒀는지는 커밋이 없어서 남아 있지 않습니다. 코드에서 읽히는 건 화면을 하나씩 만들 때마다 "설정을 받아서 조회 함수 셋을 만든다"를 반복했다는 것뿐이에요. 지원서 같은 항목에 "페이지마다 데이터를 호출하다 보면 로딩 시간이 길어져서 해결할 수 있는 방법을 모색"이라고 적혀 있는데, 그 로딩에 이 열두 번의 설정 조회가 한몫했을 가능성이 있습니다.
보일지와 울릴지의 경계가 달랐다
상자를 그리는 것 말고, 점수가 높으면 소리를 내는 판정이 MainBottom.js에 있습니다.
const isEmptyBox = (file) => {
let settingCheck = []
getSetting.map((setting) => {
if (setting.check === "on") settingCheck.push(setting.code)
})
let thres;
for (let i = 0; i < file['top'].cls_ids.length; i++) {
const cls = file['top'].cls_ids[i]
for (let n = 0; n < getSetting.length; n++) {
if (getSetting[n].code === cls) thres = getSetting[n].thres;
if (file['top'].scores[i] >= thres && settingCheck.includes(cls)) return true
}
}
return false;
}
같은 값 셋으로 같은 종류의 판정을 하는데, 상자는 thres < score이고 알람은 score >= thres입니다. 점수가 임계값과 정확히 같으면 소리는 나는데 상자는 안 보이는 조건이에요. 점수가 소수라 실제로 같은 값이 나올 일은 드물었겠지만, 두 판정이 같은 규칙이라는 보장이 코드 어디에도 없었습니다. 함수가 한 곳에 있었다면 생기지 않았을 차이입니다. 임계값을 사용자가 숫자로 직접 적게 된 뒤로는 정수 경계에 정확히 걸릴 가능성도 그만큼 커졌을 텐데, 그때 이 차이를 알고 있었는지는 남은 기록이 없습니다.
안쪽 for 문도 지금 보면 이상합니다. thres를 찾는 if와 점수를 비교하는 if가 같은 반복 안에 있어서, 아직 이 클래스의 thres를 못 찾은 순번에서도 이전 클래스의 thres로 비교가 한 번씩 돕니다. 결과가 틀리는 경우가 실제로 있었는지는 확인할 방법이 없습니다.
그때의 선택을 지금 읽으면
경력기술서에 "요구가 올 때마다 분기 코드를 넣는 대신 설정 항목을 늘렸다"고 썼고, 그건 코드가 뒷받침합니다. 클래스 하나가 늘어도 화면 코드를 고칠 필요는 없었어요. 서버 설정 배열에 한 항목이 더 오면 색도 임계값도 켜짐도 따라옵니다. 지원서가 말하는 납품처 요구 셋도 새 분기가 아니라 localStorage 키 하나씩으로 받았습니다.
그런데 설정을 읽는 쪽은 설계가 없었습니다. 값이 한 벌인 건 서버 덕분이고, 화면은 열두 곳에서 각자 물었고, 조회 함수는 복사됐고, 경계 비교는 갈렸어요. 1년차의 저는 "설정으로 빼자"까지는 갔고 "설정을 읽는 자리를 하나로 두자"까지는 못 갔습니다. 지원서에 "확장성은 고려하기 힘들 것으로 판단"이라고 적은 게 그 자리의 고백이었다고 지금은 읽힙니다.
검증
- 날짜 근거:
MainBottom.js86행에 이력 조회의 시작일로2022-04-01 / 00:00이 박혀 있고 주석에 "임의의 날짜"라고 적혀 있습니다. 코드에 남은 시기 단서 중 가장 늦은 것이라 이 글의 날짜로 썼습니다. 스냅샷 커밋은 2022-07-24이고, 지원서의 납품처 요구 항목은 2022년 5월 이후로 적혀 있어 스냅샷이 그 반영 뒤의 상태라는 것과 맞습니다. get_settings호출 파일 12개는grep -rl로 셌습니다.MainImage.js의 조회 함수 셋(2040행)과537행),MainBottom.js의 복제(517MainBottom.js48행의getSettingprop은 파일에서 직접 확인했습니다.- 상자 조건
thres < score(MainImage.js114행)와 알람 조건score >= thres(MainBottom.js258행)는 원문 그대로입니다. Setting.js56~74행에서{code: {check, color, thres}}로modify_settings에 보내고save_setting을 쓰는 것, 247행의input type="number",settings.html의input type="color"·range구조는 확인했습니다.- 지원서 옵션 셋과 코드 키의 대응(
direction·change_rail_*,label·label_top,roi·BboxOn)은index.js·MainHeader.js·MainImage.js에서grep으로 확인했습니다. 지원서·이력서는 원문 PDF를 읽고 옮겼고 회사·발주처 이름만 뺐습니다.
| 항목 | 경력기술서 2.3절 | 지원서·이력서 | 코드 | 판정 |
|---|---|---|---|---|
| 옵션을 설정으로 | 통계 설정·옵션 | 설정이 본인 담당(이력서) | 클래스별 color·thres·check, 화면은 조합만 | 일치 |
| 임계값 직접 입력 | 언급 없음 | "thres 값을 직접 작성"(2022.05, 납품처 요구) | Setting.js input type="number" | 일치 |
| 레이블 표시·위/아래, bbox | 언급 없음 | 옵션 추가(2022.05, 납품처 요구) | label·label_top·roi 키, BboxOn | 일치 |
| 화살표 방향 | 언급 없음 | "화살표 방향을 기계에 추가 입력" | direction 키, change_rail_* 소켓 | 일치 |
| 카메라 위치 설정 | 적음 | 언급 없음 | 해당 코드 없음 | 이 저장소에서 확인 불가 |
| 2D/3D 비디오 판독 설정 | 적음 | 언급 없음 | 해당 코드 없음 | 이 저장소에서 확인 불가 |
남은 것 · 한계
- 커밋 이력이 없습니다. 열두 곳의 호출과 함수 복제 중 어디까지가 제 손인지 이 저장소로는 알 수 없습니다. 이력서가 본인 담당으로 적은 건 로그인·설정·웹소켓 아이콘이고, 지원서가 본인 작업으로 적은 건 납품처 요구 옵션과 임계값 입력입니다. 이 글은 그 범위에서만 "했다"고 썼습니다.
- 경계 비교 차이가 실제 오판을 냈는지 모릅니다. 점수는 소수라 같은 값이 드물었을 것이라는 건 추정이고, 로그도 재현 환경도 없습니다.
- 카메라 위치·2D/3D 설정은 이 코드에도 지원서에도 없습니다. 경력기술서에 적은 그 항목이 다른 저장소에 있었는지, 서버 쪽 설정이었는지는 확인하지 못했습니다.
- 로딩 시간 문제의 원인이 설정 조회였다는 건 추정입니다. 지원서는 "방법을 모색"이라고만 적었고, 무엇을 했는지는 스냅샷에서 구분되지 않습니다.
- 4년 뒤 팔레트에 없는 색은 조용히 안 칠해진다에서는 색이 한 팔레트에서 오지 않으면 화면이 조용히 틀린다는 걸 142곳을 고치며 배웠는데, 여기서는 색이 서버 한 벌에서 오는 대신 읽는 쪽이 열두 벌이었습니다. 같은 문제의 반대편이에요.
관련 글: 산업 AI 솔루션 기업 프런트엔드 2021–2022 · 팔레트에 없는 색은 조용히 안 칠해진다 · 관계도 11개 화면을 렌더러 한 곳에서 바꿨다 · 설정을 localStorage에 두고 소켓 메시지 16갈래로 창끼리 맞췄다 · 클래스 팔레트 20종이 다섯 파일에 복사돼 있고 Redux는 import만 남았다